Merging the repositories of openwhisk-cli and historic go-whisk-cli.
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1bf987c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+*.js
+javascript/
+wsk
+scripts
+Godeps/_workspace
diff --git a/.idea/libraries/GOPATH__openwhisk_cli_.xml b/.idea/libraries/GOPATH__openwhisk_cli_.xml
new file mode 100644
index 0000000..a0e31a5
--- /dev/null
+++ b/.idea/libraries/GOPATH__openwhisk_cli_.xml
@@ -0,0 +1,18 @@
+<component name="libraryTable">
+  <library name="GOPATH &lt;openwhisk-cli&gt;">
+    <CLASSES>
+      <root url="file://$USER_HOME$/go/src/gopkg.in" />
+      <root url="file://$USER_HOME$/go/src/github.com" />
+      <root url="file://$USER_HOME$/go/src/golang.org" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES>
+      <root url="file://$USER_HOME$/go/src/gopkg.in" />
+      <root url="file://$USER_HOME$/go/src/github.com" />
+      <root url="file://$USER_HOME$/go/src/golang.org" />
+    </SOURCES>
+    <excluded>
+      <root url="file://$PROJECT_DIR$" />
+    </excluded>
+  </library>
+</component>
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..99168e9
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,34 @@
+# Contributing to go-whisk-cli
+
+## Proposing new features
+
+If you would like to implement a new feature, please [raise an issue](https://github.ibm.com/BlueMix-Fabric/go-whisk-cli) before sending a pull request so the feature can be discussed.
+This is to avoid you spending your valuable time working on a feature that the project developers are not willing to accept into the code base.
+
+## Fixing bugs
+
+If you would like to fix a bug, please [raise an issue](https://github.ibm.com/BlueMix-Fabric/go-whisk-cli) before sending a pull request so it can be discussed.
+If the fix is trivial or non controversial then this is not usually necessary.
+
+## Merge approval
+
+The project maintainers use LGTM (Looks Good To Me) in comments on the code review to
+indicate acceptance. A change requires LGTMs from two of the maintainers of each
+component affected.
+
+## Communication
+Please use [Slack channel #whisk-users](https://cloudplatform.slack.com/messages/whisk_cli).
+## Setup
+Project was written with `Go v1.5`.  It has a dependency on [go-whisk](https://github.ibm.com/BlueMix-Fabric/go-whisk), which has to be manually resolved (because of GHE).
+
+## Testing
+
+This repository needs unit tests.
+
+Please provide information that helps the developer test any changes they make before submitting.
+
+Should pass the cli integration test defined in the [main whisk project](https://github.rtp.raleigh.ibm.com/whisk-development/openwhisk/blob/master/tests/src/common/WskCli.java).
+
+## Coding style guidelines
+
+Use idomatic go.  (try to) Document exported functions.
diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index 16f6be8..9c0fd26 100644
--- a/Godeps/Godeps.json
+++ b/Godeps/Godeps.json
@@ -75,11 +75,11 @@
 		},
 		{
 			"ImportPath": "github.com/openwhisk/openwhisk-client-go/whisk",
-			"Rev": "c91a7494986d5f45b62003a2ab19bb0fd8ddeb7f"
+			"Rev": "5c216065673c15e35e0baa8feca9b986a6b73517"
 		},
         {
             "ImportPath": "github.com/openwhisk/openwhisk-client-go/wski18n",
-            "Rev": "c91a7494986d5f45b62003a2ab19bb0fd8ddeb7f"
+            "Rev": "5c216065673c15e35e0baa8feca9b986a6b73517"
         }
 	]
-}
\ No newline at end of file
+}
diff --git a/commands/property.go b/commands/property.go
index 0ec5f52..ed6b49e 100644
--- a/commands/property.go
+++ b/commands/property.go
@@ -499,4 +499,3 @@
 
     return nil
 }
-
diff --git a/notes.md b/notes.md
new file mode 100644
index 0000000..0c5cca6
--- /dev/null
+++ b/notes.md
@@ -0,0 +1,736 @@
+
+## Tasks
+- [X] Environment variables
+  + what are they?
+  + unclear ...
+- [x] sdk
+- [x] apibuild + cliversion
+- [x] props variable, revisited
+- [x] Edge ?
+-
+
+---
+
++ parse toml file into properties and constants
++
+
+## Notes
+
+Now testing...  first, read through the tests and make sure that they look like they should pass...
+- [ ] make a list of potentially failing tests -->
+  + [x] activation poll
+    + add method
+      + in python app, `poll` wraps `console`
+        + console
+          + if no `since-`flags have been passed, then return fetch most recent (activation.list({limit:1,}))
+          + else, once a second do:
+            +
+
+    + add flags
+      + sinceSeconds [int ?]
+      + sinceMinutes
+      + sinceHours
+      + sinceDays
+      + exit int
+
+  + [ ] namespace list --> type ?
+  + [ ] add package refresh command / client action
+    + `refresh` command
+      + just takes an optional namespace argument
+    + service
+      +
+  + [x] action create  --> add flags [ and maybe add to client as well ]
+    + [ ] memory
+    + [ ] sequence
+    + [ ] param
+    + [ ] annotation
+    + [ ] timeout
+
+- [ ] fix or approve tests in list
+
+---
+- [x] fails gracelessly when given invalid url as apiHOST
+
+- [x] edge broken
+  - needs protocol / https
+
+
+- [x] set property broken
+
+- [x] (unable to assign apihost with flag)
+
+
+- [x] apibuild does not print out
+--> client.Info.Get()
+    --> info is blank --> print out response body
+
+---
+
+
+
+[x] Props -> namespace --> need to make a request to "/namespaces" first to get a list of legal namespaces, then confirm that requested is legal.
+
+
+---
+
+Need to consider how props is being stored ... --> need to have a single global props, with defaults
+
+
+top-level properties struct
+
+on init, load properties from .wskprops, environment, flags.
+
+initialize client config from properties.
+
+
+
+getProperties --> print out Properties --> according to flags set.
+
+setProperties -->
+
+  readProps
+
+  according to flags, update props
+  write props
+
+
+unsetProperties -->
+
+  readProps
+  delete relevant ones.
+  writeProps
+
+
+readProps -> map[string]string, ok
+writeProps(map[string]string) -> ok
+
+
+---
+
+
+Check that it is up to date...
+Anything known to be missing?
+
+SDK --> simple enough...  just do it.
+
+What's the deal with apibuild, and cliversion ?
+
+Do a side-by-side comparision of wsk versions.  should be the same, except formatting
+
+Anything else I'm missing?
+
+Environment variables.
+WHISK_APIVERSION
+WHISK_AUTH
+WHISK_etc..
+WHISK_
+
+
+
+
+---
+
+Updates to the client / command line api
+
+
+client changes:
+[X] add namespace get / list --> modify to be current (list is list triggers etc for current, get is list of namespaces)
+
+Get namespace contents:
+
+wsk namespace get --> GET /v1/namespaces/_
+wsk namespace get wilsonth@us.ibm.com --> GET /v1/namespaces/_/wilsonth--
+
+Get list of namespaces available
+
+wsk namespace list -> GET /v1/namespaces
+
+So ... --> update namespace client.
+
+
+new top-level flags:
+- `--apihost`: whisk api host
+- `--apiversion`: whisk api version
+
+[X] add `PropertyCmd`
+
+
+Need to remove persistent global flags and re-add. (auth etc.)
+
+- use: "work with whisk properties"
+- set
+  + -u, --auth
+  + --apihost
+  + --apiversion
+  + --namespace
+
+
+- get
+  + -u, --auth
+  + --apihost
+  + --apiversion
+  + --cliversion
+  + --apibuild
+  + --namespace
+  + --all
+
+Namespace --> api is `:443/api/{apivesion}/namespaces/{namespace}`
+
+[x] remove top-level commands:
+- auth
+- list
+- whoami
+- health
+- clean
+- namespace
+- version
+
+remove top-level flags:
+
+- --auth (added to local level ?)
+-
+
+
+
+Implemenmt sdk command
+- install
+  - component {docker, swift, iOS}
+
+---
+
+Parsing params, annotations, and action#invoke payload --> as json data
+
+params and annotations --> attempt to parse as json into map[string]interface{}.  if it fails, then throw error
+
+payload is the same except for that if it is not valid json then obj is created "{payload: arg}".
+
+What about response object ??  Will also be a map[string]interface{} ?? 
+
+To start: --> change action invoke :payload to a map[string]interface{} and see if it breaks.
+
+Ok mostly working ...
+
+--> need to add it back in to
+- [x] trigger
+- [x] package
+- [x] rule ?
+
+
+What does trigger#fire return?  {id: "id"}
+
+
+
+---
+Order of variables: flags -> env -> .wsk
+
+1. load flags, env, and .wsk (props)
+2. for each value, check and assign in order
+3. initialize client + other variables that depend on values
+
+config:
+- `auth`
+- `namespace`
+- `edge`
+
+- [X] load props and env. variables in main init.  Write a top-level persistent pre-run function to read the command line variables.
+
+- [X] add setter functions
+  + [X] `auth`
+  + [X] `namespace`
+
+---
+
+package.bind ...
+
+```python
+
+def bind(self, args, props):
+        url = 'https://%(url)s/api/v1/%(namespace)s/packages/%(name)s' % {
+            'url': props['api'],
+            'namespace': urllib.quote(args.namespace),
+            'name': self.getSafeName(args.name)
+        }
+        split = args.package.split(':')
+        binding = {}
+        if (len(split) == 1):
+            binding = { 'name': split[0], 'namespace': args.namespace}
+        elif (len(split) == 2):
+            binding = { 'name': split[1], 'namespace': split[0]}
+        else:
+            print 'package name malformed. name or namespace/name allowed'
+            sys.exit(1)
+
+        payload = {
+            'name': args.name,
+            'binding': binding,
+            'annotations': getAnnotations(args),
+            'parameters': getParams(args)
+        }
+        args.shared = False
+        self.addPublish(payload, args)
+        headers= {
+            'Content-Type': 'application/json'
+        }
+        res = request('PUT', url, json.dumps(payload), headers, auth=args.auth, verbose=args.verbose)
+
+        resBody = res.read()
+        result = json.loads(resBody)
+
+        if res.status == httplib.OK:
+            print 'ok: created binding %(name)s ' % {'name': args.name }
+            return 0
+        else:
+            print 'error: ' + result['error']
+            return res.status
+```
+
+---
+
+
+```python
+
+def create(self, args, props, update):
+        exe = self.getExec(args, props)
+        if args.pipe:
+            if args.param is None:
+                args.param = []
+            args.param.append([ '_actions', json.dumps(self.csvToList(args.artifact))])
+
+        validExe = exe is not None and ('image' in exe or 'code' in exe)
+        if update or validExe: # if create action, then exe must be valid
+            payload = {
+               'name': args.name,
+               'annotations': getAnnotations(args),
+               'parameters': getParams(args),
+               'limits' : self.getLimits(args)
+            }
+            if validExe:
+                payload['exec'] = exe
+            self.addPublish(payload, args)
+            return self.put(args, props, update, json.dumps(payload))
+        else:
+            print 'the artifact "%s" is not a valid file. If this is a docker image, use --docker.' % args.artifact
+            return 2
+
+
+# creates { code: "js code", image: "docker image", initializer: "base64 encoded string" }
+# where code and image are mutually exclusive and initializer is optional
+def getExec(self, args, props):
+    exe = {}
+    if args.docker:
+        exe['image'] = args.artifact
+    elif args.copy:
+        existingAction = args.artifact
+        exe = self.getActionExec(args, props, existingAction)
+    elif args.pipe:
+        args2 = copy.copy(args) # shallow copy of args object
+        args2.namespace = 'client.system'
+        pipeAction = 'common/pipe'
+        exe = self.getActionExec(args2, props, pipeAction)
+    elif args.artifact is not None and os.path.isfile(args.artifact):
+        exe['code'] = open(args.artifact, 'rb').read()
+    if args.lib:
+        exe['initializer'] = base64.b64encode(args.lib.read())
+    return exe
+
+def getActionExec(self, args, props, name):
+    res = self.Get(args, props, name)
+    resBody = res.read()
+    if res.status == httplib.OK:
+        execField = json.loads(resBody)['exec']
+    else:
+        execField = None
+    return execField
+
+```
+
+
+Action Create:
+
+```golang
+
+  if flags.docker
+    exec.image = artifact // what artifact ?
+
+  else if flags.copy
+    -> actions.Get(actionName), copy exec
+
+  else if flags.pipe
+    -> (copy args)
+    -> client.Config.Namespace = "client.system"
+    -> actionName = "common/pipe"
+    -> actions.Get(actionName), copy exec
+
+  else if artifact != "" && os.FileExists(artifact)
+    -> exec.code = os.ReadFile(artifact)
+
+  if flags.lib
+    -> exec.init = base64.Encode(flag.lib.read())  // lib is gzipped or tar file.
+
+
+
+
+```
+
+
+---
+
+Thinking about how to persist data in between wsk calls.  The way that the python version does it is to write to a file on disk.  What other ways are there to do this?
+- How does github cli do this ?
+
+- Start working on command ...
+  + fill out methods.
+    + first need to create a reference to the whisk...  Top-level variable. --> parse flags, then assign
+
+## To do's
+
+- [X] actionInvokeCmd --> parse payload properly.
+
+- [ ] better error responses
+  + read resp.Body for message.
+
+- [ ] Add support for environment variables
+  - EDGE_HOST
+  - CLI_API_HOST
+  - WHISK_VERSION_DATE
+
+- [x] create action
+  + see above
+- [ ] verbose
+- [ ] Verbose (with a writer or something fancy)
+  + how to avoid putting this on the client ?
+- [X] params / annotations
+- [X] positional arguments
+- [ ] SDK
+
+
+- [X] finish all simple methods
+  + [x] package
+  + [X] rule
+  + [X] trigger
+
+- [ ] Figure out how to properly define positional arguments with cobra / pflags.
+
+- [ ] Implement verbose mode to help with debugging.
+
+- [X] finish complex methods
+  + [X] Action.Create with exec and all flags
+  + [X] Action.Invoke with params
+
+- Local install
+  - vagrant.
+  - test everything locally, include how in docs.
+  - Debug locally.
+
+- review how other cli packages store props (to disk)
+  + hugo
+  + github
+
+- Cmd
+  + implement loadConfig + updateConfig
+  + add basic Client methods
+    + auth
+    + clean
+    + version
+  + add verbose
+  + add arguments
+  + add flags
+    + top-level
+    + sub-cmd-level
+
+  + add messages
+  + add functions (link up with stubbed out client + props)
+- Client
+  + [X] stub out methods for all services (with arguments)
+  + [X] complete services
+  + [X] complete request method for Client ()
+  + [X] figure out what namespaces is about
+  + [X] add auth to Client
+
+
+
+## Solutions
+
+
+
+
+<!--
+
+NONE OF THIS MATTERS!  stuff is reloaded every time the
+ - watching props
+  + have a loadPropsFromFile function
+    + if file missing, use default
+  + have an update prop(s) function
+    + updates the file.
+  + have a watch propsFile function -> updates client when it detects a change. -->
+
+- auth
+  + include token in Client struct (base64 encoded?)
+  + Add Auth header in *whisk.Request
+- verbose
+  + include bool in Client struct
+  + print out in *whisk.Do
+- BUT ALSO!!!
+  + need to store on disk so that it is the same in between invocations.  This is done in cmd --> initialized the client based on contents of .wskprops
+
+## Code Samples From whisk *python
+
+---
+
+`wskitem.py`
+
+```python
+def put(self, args, props, update, payload):
+  url = 'https://%(url)s/%(service)s/v1/%(namespace)s/%(collection)s/%(name)s%(update)s' % {
+      'url': props[self.service],
+      'service': self.service,
+      'namespace': urllib.quote(args.namespace),
+      'collection': self.collection,
+      'name': self.getSafeName(args.name),
+      'update': '?overwrite=true' if update else ''
+  }
+
+  headers= {
+      'Content-Type': 'application/json'
+  }
+
+  res = request('PUT', url, payload, headers, auth=args.auth, verbose=args.verbose)
+  resBody = res.read()
+
+  if res.status == httplib.OK:
+      print 'ok: %(mode)s %(item)s %(name)s' % { 'mode': 'updated' if update else 'created', 'item': self.name, 'name': args.name }
+      return 0
+  else:
+      result = json.loads(resBody)
+      print 'error: ' + result['error']
+      return res.status
+```
+
+- This gives the url structure for resource requests
+- `request` is defined in `wskutil.py`
+
+---
+
+`wskutil.py`
+
+```python
+def request(method, urlString, body = "", headers = {}, auth = None, verbose = False):
+    url = urlparse(urlString)
+    if url.scheme == 'http':
+        conn = httplib.HTTPConnection(url.netloc)
+    else:
+        if hasattr(ssl, '_create_unverified_context'):
+            conn = httplib.HTTPSConnection(url.netloc, context=ssl._create_unverified_context())
+        else:
+            conn = httplib.HTTPSConnection(url.netloc)
+
+    if auth != None:
+        auth = base64.encodestring(auth).replace('\n', '')
+        headers['Authorization'] = 'Basic %s' % auth
+
+    if verbose:
+        print "========"
+        print "REQUEST:"
+        print "%s %s" % (method, urlString)
+        print "Headers sent:"
+        print json.dumps(headers, indent=4)
+        if body != "":
+            print "Body sent:"
+            print body
+
+    conn.request(method, urlString, body, headers)
+    res = conn.getresponse()
+    body = res.read()
+
+    # patch the read to return just the body since the normal read
+    # can only be done once
+    res.read = lambda: body
+
+    if verbose:
+        print "--------"
+        print "RESPONSE:"
+        print "Got response with code %s" % res.status
+        print "Body received:"
+        print res.read()
+        print "========"
+
+    return res
+```
+
+- Shows auth scheme --> just base64 encode and use basic authorization for requests
+
+
+## Code samples from hugo
+
+---
+
+from `commands/hugo.go`
+
+```go
+
+// Execute adds all child commands to the root command HugoCmd and sets flags appropriately.
+func Execute() {
+	HugoCmd.SetGlobalNormalizationFunc(helpers.NormalizeHugoFlags)
+
+	HugoCmd.SilenceUsage = true
+
+	AddCommands()
+
+	if c, err := HugoCmd.ExecuteC(); err != nil {
+		if isUserError(err) {
+			c.Println("")
+			c.Println(c.UsageString())
+		}
+
+		return
+	}
+}
+
+// AddCommands adds child commands to the root command HugoCmd.
+func AddCommands() {
+	HugoCmd.AddCommand(serverCmd)
+	HugoCmd.AddCommand(versionCmd)
+	HugoCmd.AddCommand(configCmd)
+	HugoCmd.AddCommand(checkCmd)
+	HugoCmd.AddCommand(benchmarkCmd)
+	HugoCmd.AddCommand(convertCmd)
+	HugoCmd.AddCommand(newCmd)
+	HugoCmd.AddCommand(listCmd)
+	HugoCmd.AddCommand(undraftCmd)
+	HugoCmd.AddCommand(importCmd)
+
+	HugoCmd.AddCommand(genCmd)
+	genCmd.AddCommand(genautocompleteCmd)
+	genCmd.AddCommand(gendocCmd)
+	genCmd.AddCommand(genmanCmd)
+}
+
+```
+
+- use a top-level execute function like this to wrap Cmd.Execute().
+- Add all the commands in one place in the main function --> makes it much easier to see what's going on vs. in lots of different init files --> also easier to update / edit etc.
+
+```go
+
+
+
+
+// Flags that are to be added to commands.
+var BuildWatch, IgnoreCache, Draft, Future, UglyURLs, CanonifyURLs, Verbose, Logging, VerboseLog, DisableRSS, DisableSitemap, DisableRobotsTXT, PluralizeListTitles, PreserveTaxonomyNames, NoTimes, ForceSync bool
+var Source, CacheDir, Destination, Theme, BaseURL, CfgFile, LogFile, Editor string
+
+func initCoreCommonFlags(cmd *cobra.Command) {
+	cmd.Flags().BoolVarP(&Draft, "buildDrafts", "D", false, "include content marked as draft")
+	cmd.Flags().BoolVarP(&Future, "buildFuture", "F", false, "include content with publishdate in the future")
+	cmd.Flags().BoolVar(&DisableRSS, "disableRSS", false, "Do not build RSS files")
+	cmd.Flags().BoolVar(&DisableSitemap, "disableSitemap", false, "Do not build Sitemap file")
+	cmd.Flags().BoolVar(&DisableRobotsTXT, "disableRobotsTXT", false, "Do not build Robots TXT file")
+	cmd.Flags().StringVarP(&Source, "source", "s", "", "filesystem path to read files relative from")
+	cmd.Flags().StringVarP(&CacheDir, "cacheDir", "", "", "filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/")
+	cmd.Flags().BoolVarP(&IgnoreCache, "ignoreCache", "", false, "Ignores the cache directory for reading but still writes to it")
+	cmd.Flags().StringVarP(&Destination, "destination", "d", "", "filesystem path to write files to")
+	cmd.Flags().StringVarP(&Theme, "theme", "t", "", "theme to use (located in /themes/THEMENAME/)")
+	cmd.Flags().BoolVar(&UglyURLs, "uglyURLs", false, "if true, use /filename.html instead of /filename/")
+	cmd.Flags().BoolVar(&CanonifyURLs, "canonifyURLs", false, "if true, all relative URLs will be canonicalized using baseURL")
+	cmd.Flags().StringVarP(&BaseURL, "baseURL", "b", "", "hostname (and path) to the root, e.g. http://spf13.com/")
+	cmd.Flags().StringVar(&CfgFile, "config", "", "config file (default is path/config.yaml|json|toml)")
+	cmd.Flags().StringVar(&Editor, "editor", "", "edit new content with this editor, if provided")
+	cmd.Flags().BoolVar(&nitro.AnalysisOn, "stepAnalysis", false, "display memory and timing of different steps of the program")
+	cmd.Flags().BoolVar(&PluralizeListTitles, "pluralizeListTitles", true, "Pluralize titles in lists using inflect")
+	cmd.Flags().BoolVar(&PreserveTaxonomyNames, "preserveTaxonomyNames", false, `Preserve taxonomy names as written ("Gérard Depardieu" vs "gerard-depardieu")`)
+	cmd.Flags().BoolVarP(&ForceSync, "forceSyncStatic", "", false, "Copy all files when static is changed.")
+	// For bash-completion
+	validConfigFilenames := []string{"json", "js", "yaml", "yml", "toml", "tml"}
+	cmd.Flags().SetAnnotation("config", cobra.BashCompFilenameExt, validConfigFilenames)
+	cmd.Flags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
+	cmd.Flags().SetAnnotation("cacheDir", cobra.BashCompSubdirsInDir, []string{})
+	cmd.Flags().SetAnnotation("destination", cobra.BashCompSubdirsInDir, []string{})
+	cmd.Flags().SetAnnotation("theme", cobra.BashCompSubdirsInDir, []string{"themes"})
+}
+```
+
+- Parse variables like this.
+
+---
+
+
+## Thoughts
+
+---
+
+Now... -> fill out command functions.
+
+Perhaps start with a different function... with fewer flags ??
+
+
+Need to figure out how flags will be done...
+
+Can put in cmd.init() functions ??  yeah ...
+
+would be really nice if i could test this ...
+
+
+---
+
+How to set namespace properly... ?
+
+stored in .wskprops, initialized in whisk.
+client offers namespaceService.List() only.
+
+---
+
+Review other python cli commands that are not listed in swagger doc (e.g. namespaces, sdk )
+
+---
+What does "clean" do ??
+
+---
+
+What am I doing with Config / props ??
+
+What is the requirement?
+> read .wsk config into map[string]string
+> write map[string]string to file (configurable)
+
+
+---
+
+
+current issue:
+Optional parameters... should not be listed in url params.  If not there, then don't print.
+e.g. How to deal with activationsListOptions .since and .upto
+
+possible solution: use pointers.  if pointer is nil, then ignore.
+
+
+```go
+func addRouteOptions(route string, options interface{}) (string, error) {
+	v := reflect.ValueOf(options)
+	if v.Kind() == reflect.Ptr && v.IsNil() {
+		return route, nil
+	}
+
+	u, err := url.Parse(route)
+	if err != nil {
+		return route, err
+	}
+
+	qs, err := query.Values(options)
+	if err != nil {
+		return route, err
+	}
+
+	u.RawQuery = qs.Encode()
+	return u.String(), nil
+}
+```
+- How does go-querystring/query.Values() work ?? has options ??
+  + want to take all non-nil values from options and write key=value in url.  Should ignore nil values (like for pointers and empty structs.)
+
+
+Already does this!  using the tags... --> omit empty.  Anything to worry about then ... ?
+
+
+- consider using pointers to structs.
+-
+- for now, just flag and skip anything difficult.
diff --git a/tools/travis/install_openwhisk.sh b/tools/travis/install_openwhisk.sh
index 56d3799..ae0d5ed 100755
--- a/tools/travis/install_openwhisk.sh
+++ b/tools/travis/install_openwhisk.sh
@@ -16,7 +16,7 @@
 docker info
 
 # Ansible
-pip install --user ansible==2.1.2.0
+pip install --user ansible==2.3.0.0
 
 # Clone the OpenWhisk code
 git clone --depth 3 https://github.com/openwhisk/openwhisk.git