You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by cb...@apache.org on 2017/06/08 11:42:09 UTC
[incubator-openwhisk] branch master updated: Reverting CLI changes
to restore master
This is an automated email from the ASF dual-hosted git repository.
cbickel pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk.git
The following commit(s) were added to refs/heads/master by this push:
new ade8699 Reverting CLI changes to restore master
ade8699 is described below
commit ade86999d58c1aeef4791f20c7116f02dedc2751
Author: Markus Thoemmes <ma...@de.ibm.com>
AuthorDate: Thu Jun 8 12:34:40 2017 +0200
Reverting CLI changes to restore master
---
ansible/files/package-versions.ini | 2 -
ansible/group_vars/all | 44 -
ansible/roles/cli/files/createContentJson.sh | 69 -
ansible/roles/cli/tasks/deploy.yml | 29 +-
ansible/roles/cli/tasks/download_cli.yml | 2 -
ansible/roles/cli/tasks/download_openwhisk_cli.yml | 69 -
tools/cli/Dockerfile | 40 +
tools/cli/README.md | 15 +
tools/cli/build.gradle | 93 ++
tools/cli/build.sh | 105 ++
tools/cli/go-whisk-cli/Godeps/Godeps.json | 85 ++
tools/cli/go-whisk-cli/Godeps/Readme | 5 +
tools/cli/go-whisk-cli/commands/action.go | 922 ++++++++++++
tools/cli/go-whisk-cli/commands/activation.go | 363 +++++
tools/cli/go-whisk-cli/commands/api.go | 1585 ++++++++++++++++++++
tools/cli/go-whisk-cli/commands/commands.go | 214 +++
tools/cli/go-whisk-cli/commands/flags.go | 131 ++
tools/cli/go-whisk-cli/commands/namespace.go | 126 ++
tools/cli/go-whisk-cli/commands/package.go | 581 +++++++
tools/cli/go-whisk-cli/commands/property.go | 486 ++++++
tools/cli/go-whisk-cli/commands/rule.go | 455 ++++++
tools/cli/go-whisk-cli/commands/sdk.go | 245 +++
tools/cli/go-whisk-cli/commands/trigger.go | 566 +++++++
tools/cli/go-whisk-cli/commands/util.go | 1069 +++++++++++++
tools/cli/go-whisk-cli/commands/wsk.go | 60 +
tools/cli/go-whisk-cli/main.go | 105 ++
tools/cli/go-whisk-cli/wski18n/detection.go | 42 +
tools/cli/go-whisk-cli/wski18n/i18n.go | 147 ++
.../go-whisk-cli/wski18n/resources/de_DE.all.json | 0
.../go-whisk-cli/wski18n/resources/en_US.all.json | 1466 ++++++++++++++++++
.../go-whisk-cli/wski18n/resources/es_ES.all.json | 0
.../go-whisk-cli/wski18n/resources/fr_FR.all.json | 6 +
.../go-whisk-cli/wski18n/resources/it_IT.all.json | 0
.../go-whisk-cli/wski18n/resources/ja_JA.all.json | 0
.../go-whisk-cli/wski18n/resources/ko_KR.all.json | 0
.../go-whisk-cli/wski18n/resources/pt_BR.all.json | 0
.../wski18n/resources/zh_Hans.all.json | 0
.../wski18n/resources/zh_Hant.all.json | 0
tools/cli/go-whisk/whisk/action.go | 213 +++
tools/cli/go-whisk/whisk/activation.go | 195 +++
tools/cli/go-whisk/whisk/api.go | 586 ++++++++
tools/cli/go-whisk/whisk/client.go | 600 ++++++++
tools/cli/go-whisk/whisk/info.go | 68 +
tools/cli/go-whisk/whisk/namespace.go | 104 ++
tools/cli/go-whisk/whisk/package.go | 211 +++
tools/cli/go-whisk/whisk/rule.go | 186 +++
tools/cli/go-whisk/whisk/sdk.go | 74 +
tools/cli/go-whisk/whisk/shared.go | 36 +
tools/cli/go-whisk/whisk/trace.go | 93 ++
tools/cli/go-whisk/whisk/trigger.go | 188 +++
tools/cli/go-whisk/whisk/util.go | 66 +
tools/cli/go-whisk/whisk/wskerror.go | 172 +++
tools/cli/go-whisk/wski18n/detection.go | 42 +
tools/cli/go-whisk/wski18n/i18n.go | 147 ++
.../cli/go-whisk/wski18n/resources/de_DE.all.json | 0
.../cli/go-whisk/wski18n/resources/en_US.all.json | 126 ++
.../cli/go-whisk/wski18n/resources/es_ES.all.json | 0
.../cli/go-whisk/wski18n/resources/fr_FR.all.json | 0
.../cli/go-whisk/wski18n/resources/it_IT.all.json | 0
.../cli/go-whisk/wski18n/resources/ja_JA.all.json | 0
.../cli/go-whisk/wski18n/resources/ko_KR.all.json | 0
.../cli/go-whisk/wski18n/resources/pt_BR.all.json | 0
.../go-whisk/wski18n/resources/zh_Hans.all.json | 0
.../go-whisk/wski18n/resources/zh_Hant.all.json | 0
64 files changed, 12033 insertions(+), 201 deletions(-)
diff --git a/ansible/files/package-versions.ini b/ansible/files/package-versions.ini
deleted file mode 100644
index 2464ea7..0000000
--- a/ansible/files/package-versions.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[openwhisk-cli]
-git_tag=latest
diff --git a/ansible/group_vars/all b/ansible/group_vars/all
index b0540f8..59ef946 100644
--- a/ansible/group_vars/all
+++ b/ansible/group_vars/all
@@ -1,7 +1,6 @@
mode: deploy
prompt_user: true
openwhisk_home: "{{ lookup('env', 'OPENWHISK_HOME')|default(playbook_dir + '/..', true) }}"
-openwhisk_cli_home: "{{ lookup('env', 'OPENWHISK_CLI')|default(playbook_dir + '/../../incubator-openwhisk-cli', true) }}"
exclude_logs_from: []
# This whisk_api_localhost_name_default is used to configure nginx to permit vanity URLs for web actions
@@ -229,46 +228,3 @@ catalog_repos:
location: "{{ openwhisk_home }}/../openwhisk-catalog"
version: "HEAD"
repo_update: "no"
-
-openwhisk_cli_tag: "{{ lookup('ini', 'git_tag section=openwhisk-cli file={{ openwhisk_home }}/ansible/files/package-versions.ini') }}"
-
-# The openwhisk_cli is used to determine how to install the OpenWhisk CLI. The
-# installation_mode can be specified into two modes: remote and local.
-# The mode remote means to download the available binaries from the releases page
-# of the official openwhisk cli repository. The mode local means to get the binaries
-# from a local directory. The default value for openwhisk is remote.
-#
-# The name specifies the package name of the binaries in remote mode.
-#
-# The dest_name specifies the package name of the binaries in Nginx in remote mode.
-#
-# The location specifies the official website where Openwhisk CLI is hosted in
-# remote mode or location to save the binaries of the OpenWhisk CLI in local mode.
-#
-
-openwhisk_cli:
- installation_mode: remote
- name: OpenWhisk_CLI
- dest_name: OpenWhisk_CLI
- location: "https://github.com/apache/incubator-openwhisk-cli/releases/download/{{ openwhisk_cli_tag }}"
-
-# If you would like to use the binaries available locally, e.g. the binaries generated in the development
-# environment, you can uncomment the following section to use the local mode. Please be advised that the
-# binaries are generated and the location is set correctly before running the ansible scripts.
-#
-#openwhisk_cli:
-# installation_mode: local
-# location: "{{ openwhisk_cli_home }}/bin"
-
-# The list of operating systems for which openwhisk cli binaries are downloaded,
-# if the installation_mode is remote.
-cli_os:
- - linux
- - mac
- - windows
-
-# The list of architectures for which openwhisk cli binaries are downloaded,
-# if the installation_mode is remote.
-cli_arc:
- - amd64
- - 386
diff --git a/ansible/roles/cli/files/createContentJson.sh b/ansible/roles/cli/files/createContentJson.sh
deleted file mode 100755
index 6171568..0000000
--- a/ansible/roles/cli/files/createContentJson.sh
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/bin/bash
-
-set +x
-set -e
-
-get_compressed_name() {
- local os=$1
- local arch=$2
- local product_name=$3
-
- if [ $arch = amd64 ]; then
- comp_name="$product_name-$os";
- elif [ $arch = 386 ]; then
- comp_name="$product_name-$os-32bit";
- else
- comp_name="$product_name-$os-$arch";
- fi
-
- echo $comp_name;
-};
-
-get_binary_path() {
- local comp_name=$1
- local os=$2
- local arch=$3
-
- if [ $os = "linux" ]; then
- comp_name="$comp_name.tgz"
- else
- comp_name="$comp_name.zip"
- fi
- echo $os/$arch/$comp_name;
-};
-
-create_cli_packages() {
- local dirIndex="{\"cli\":{"
-
- for platform in $platforms; do
- dirIndex="$dirIndex\"$platform\":{"
-
- for arch in $archs; do
- comp_name=$(get_compressed_name $platform $arch $zip_name)
- comp_path=$(get_binary_path $comp_name $platform $arch)
-
- if [ $arch = $default_arch ]; then
- dirIndex="$dirIndex\"default\":{\"path\":\"$comp_path\"},";
- fi
-
- dirIndex="$dirIndex\"$arch\":{\"path\":\"$comp_path\"},";
- done
-
- dirIndex="$(echo $dirIndex | rev | cut -c2- | rev)"
- dirIndex="$dirIndex},";
- done
-
- dirIndex="$(echo $dirIndex | rev | cut -c2- | rev)"
- dirIndex="$dirIndex}}"
-
- echo $dirIndex > $PATH_CONTENT_JSON/content.json
-};
-
-archs="386 amd64";
-default_arch="amd64"
-
-PATH_CONTENT_JSON=$1
-BINARY_TAG=$2
-platforms=$3
-zip_name=$4
-create_cli_packages
diff --git a/ansible/roles/cli/tasks/deploy.yml b/ansible/roles/cli/tasks/deploy.yml
index eee6ead..1bf872d 100644
--- a/ansible/roles/cli/tasks/deploy.yml
+++ b/ansible/roles/cli/tasks/deploy.yml
@@ -6,24 +6,23 @@
path: "{{ cli.nginxdir }}"
state: directory
-- set_fact:
- cli_installation_mode="{{ openwhisk_cli.installation_mode }}"
-
- include: docker_login.yml
-- name: "download the binaries from the release page of OpenWhisk CLI"
- include: download_openwhisk_cli.yml
- with_nested:
- - "{{ cli_os }}"
- - "{{ cli_arc }}"
- when: cli_installation_mode == "remote"
+- name: "pull the {{ docker_image_tag }} image of cli"
+ shell: "docker pull {{ docker_registry }}{{ docker_image_prefix }}/cli:{{ docker_image_tag }}"
+ when: docker_registry != ""
+
+- name: "run Docker"
+ shell: "docker run --name cli {{ docker_registry }}{{ docker_image_prefix }}/cli:{{ docker_image_tag }}"
+
+- name: "copy all the cli binaries into nginx"
+ shell: "docker cp cli:/src/github.com/go-whisk-cli/build/. '{{ cli.nginxdir }}'"
+ ignore_errors: yes
-- name: "generate content.json for Nginx CLI directory"
- local_action: shell "./createContentJson.sh" "{{ cli.nginxdir }}" "{{ openwhisk_cli_tag }}" "{{ cli_os | join(" ") }}" "{{ openwhisk_cli.dest_name }}" chdir="{{ openwhisk_home }}/ansible/roles/cli/files"
- when: cli_installation_mode == "remote"
+- name: "stop Docker"
+ shell: "docker stop cli"
-- name: "copy the binaries from a local directory to Nginx"
- copy: src={{ openwhisk_cli.location }}/ dest={{ cli.nginxdir }}/
- when: cli_installation_mode == "local"
+- name: "remove cli container after publishing"
+ shell: "docker rm cli"
- include: download_cli.yml
diff --git a/ansible/roles/cli/tasks/download_cli.yml b/ansible/roles/cli/tasks/download_cli.yml
index 27a2735..df18b3d 100644
--- a/ansible/roles/cli/tasks/download_cli.yml
+++ b/ansible/roles/cli/tasks/download_cli.yml
@@ -9,7 +9,6 @@
dest="{{ openwhisk_home }}/bin/wsk"
mode=0755
validate_certs=False
- force=True
when: "'environments/mac' not in inventory_dir"
- name: "download cli (mac) to openwhisk home at {{ openwhisk_home }}"
@@ -19,5 +18,4 @@
dest="{{ openwhisk_home }}/bin/wsk"
mode=0755
validate_certs=False
- force=True
when: "'environments/mac' in inventory_dir"
diff --git a/ansible/roles/cli/tasks/download_openwhisk_cli.yml b/ansible/roles/cli/tasks/download_openwhisk_cli.yml
deleted file mode 100644
index bf55d6b..0000000
--- a/ansible/roles/cli/tasks/download_openwhisk_cli.yml
+++ /dev/null
@@ -1,69 +0,0 @@
----
-
-# Download the cli to Nginx CLI directory
-
-- name: "set the basic variables os, arc and suffix"
- set_fact:
- os="{{ item[0] }}"
- arc="{{ item[1] }}"
- suffix="zip"
-
-- name: "change the suffix to tgz if the operating system is Linux"
- set_fact:
- suffix="tgz"
- when: os == "linux"
-
-- name: "set the file name to download"
- set_fact:
- source_name="{{ openwhisk_cli.name }}-{{ openwhisk_cli_tag }}-{{ os }}-{{ arc }}.{{ suffix }}"
-
-- name: "set the destination file name locally after download for 32 bit"
- set_fact:
- dest_name="{{ openwhisk_cli.dest_name }}-{{ os }}-32bit.{{ suffix }}"
- when: arc == "386"
-
-- name: "set the destination file name locally after download for 64 bit"
- set_fact:
- dest_name="{{ openwhisk_cli.dest_name }}-{{ os }}.{{ suffix }}"
- when: arc == "amd64"
-
-- name: "set the http header used to download the binary packages"
- set_fact:
- headers="{{ openwhisk_cli.headers }}"
- when: openwhisk_cli.headers is defined
-
-- name: "set the http header to empty"
- set_fact:
- headers=""
- when: openwhisk_cli.headers is not defined
-
-- name: "set the instance volume_dir"
- set_fact:
- instance: "{{instances | selectattr('name', 'equalto', 'db') | list | first}}"
- volume_dir: "{{ instance.volume.fsmount | default( '/mnt/' + group_names|first, true ) }}:/usr/local/var/lib/couchdb"
- when: (block_device is defined) and (block_device in disk_status.stdout)
-
-- name: "ensure Nginx cli directory for ({{ os }}) {{ arc }} exists"
- file:
- path: "{{ cli.nginxdir }}/{{ os }}/{{ arc }}"
- state: directory
-
-- name: "download cli ({{ os }}) {{ arc }} to Nginx cli directory"
- get_url:
- url: "{{ openwhisk_cli.location }}/{{ source_name }}"
- dest: "{{ cli.nginxdir }}/{{ os }}/{{ arc }}/{{ dest_name }}"
- headers: "{{ headers }}"
- mode: 0755
-
-- name: "unarchive cli for Mac and Windows"
- unarchive:
- src: "{{ cli.nginxdir }}/{{ os }}/{{ arc }}/{{ dest_name }}"
- dest: "{{ cli.nginxdir }}/{{ os }}/{{ arc }}/"
- remote_src: True
- when: os != "linux"
-
-- name: "unarchive cli for Linux"
- command: tar -zxvf {{ dest_name }}
- args:
- chdir: "{{ cli.nginxdir }}/{{ os }}/{{ arc }}/"
- when: os == "linux"
diff --git a/tools/cli/Dockerfile b/tools/cli/Dockerfile
new file mode 100644
index 0000000..186b4d5
--- /dev/null
+++ b/tools/cli/Dockerfile
@@ -0,0 +1,40 @@
+FROM golang:1.8
+
+# Install zip
+RUN apt-get -y update && \
+ apt-get -y install zip
+
+ENV GOPATH=/
+
+# Download and install tools
+RUN echo "Installing the godep tool"
+RUN go get github.com/tools/godep
+
+RUN echo "Installing the go-bindata tool"
+RUN go get github.com/jteeuwen/go-bindata/...
+
+ADD . /src/github.com/
+
+# Load all of the dependencies from the previously generated/saved godep generated godeps.json file
+RUN echo "Restoring Go dependencies"
+RUN cd /src/github.com/go-whisk-cli && /bin/godep restore -v
+
+# Collect all translated strings into single .go module
+RUN echo "Generating i18n Go module"
+RUN cd /src/github.com/go-whisk-cli && /bin/go-bindata -pkg wski18n -o wski18n/i18n_resources.go wski18n/resources
+RUN cd /src/github.com/go-whisk && /bin/go-bindata -pkg wski18n -o wski18n/i18n_resources.go wski18n/resources
+
+# Generate a Go package dependency list
+# NOTE: Currently, the 'go list' command will not work against the current Go CLI non-standard file structure
+#RUN cd /src/github.com/go-whisk-cli && go list -f '{{join .Deps "\n"}}' > ../../../wsk.deps.out
+RUN cd /src/github.com/go-whisk-cli && echo "Dependencies list requires restructuring the GO CLI packages" > ../../../wsk.deps.out
+
+# All of the Go CLI binaries will be placed under a build folder
+RUN mkdir /src/github.com/go-whisk-cli/build
+
+ARG CLI_OS
+ARG CLI_ARCH
+
+# Build the Go wsk CLI binaries and compress resultant binaries
+RUN chmod +x /src/github.com/build.sh
+RUN cd /src/github.com/ && ./build.sh
diff --git a/tools/cli/README.md b/tools/cli/README.md
new file mode 100644
index 0000000..44779d8
--- /dev/null
+++ b/tools/cli/README.md
@@ -0,0 +1,15 @@
+To build the OpenWhisk CLI run the following command from the OpenWhisk home directory:
+
+$ gradle :tools:cli:distDocker
+
+Multiple binaries are produced in a Docker container during the build process. One of those binaries is copied from the
+Docker container to the local file system in the following directory: ../../bin/wsk. This binary will be platform
+specific, it will only run on the operating system, and CPU architecture that matches the build machine.
+
+Currently the build process is only supported on Linux, and Mac operating systems running on an x86 CPU architecture.
+
+To get CLI command help, execute the following command:
+
+$ wsk --help
+
+To get CLI command debug information, include the -d, or --debug flag when executing a command.
diff --git a/tools/cli/build.gradle b/tools/cli/build.gradle
new file mode 100644
index 0000000..deda2a6
--- /dev/null
+++ b/tools/cli/build.gradle
@@ -0,0 +1,93 @@
+ext.dockerImageName = "cli"
+ext.dockerContainerName = "cli"
+ext.dockerBuildArgs = getDockerBuildArgs()
+apply from: '../../gradle/docker.gradle'
+
+
+task removeBinary(type: Delete) {
+ delete "${projectDir}/../../bin/wsk"
+ delete "${projectDir}/../../bin/mac"
+ delete "${projectDir}/../../bin/linux"
+ delete "${projectDir}/../../bin/windows"
+}
+
+task distBinary(dependsOn: [removeBinary, distDocker]) {
+ doLast {
+ run(dockerBinary + ["rm", "-f", dockerContainerName], true)
+ run(dockerBinary + ["run", "--name", dockerContainerName, dockerTaggedImageName])
+
+ // Copy all Go binaries from Docker into openwhisk/bin folder
+ run(dockerBinary + ["cp", dockerContainerName +
+ ":/src/github.com/go-whisk-cli/build/.", "${projectDir}/../../bin"])
+
+ run(dockerBinary + ["rm", "-f", dockerContainerName])
+ }
+}
+
+task dumpOSInfo {
+ doLast {
+ println "os.name = "+getOsName()
+ println "os.arch = "+getOsArch()
+ println "go.name = "+mapOsNameToGoName(getOsName())
+ println "go.arch = "+mapOsArchToGoArch(getOsArch())
+ }
+}
+
+task copyCLIShortcut(type: Copy, dependsOn: [distBinary, dumpOSInfo]) {
+ String go_osname = mapOsNameToGoName(getOsName())
+ String go_osarch = mapOsArchToGoArch(getOsArch())
+ String from_path_wsk = "${projectDir}/../../bin/${go_osname}/${go_osarch}/wsk"
+ String to_path_dir = "${projectDir}/../../bin"
+
+ from from_path_wsk
+ into to_path_dir
+}
+
+pushImage.finalizedBy copyCLIShortcut
+
+// Returns the Go CLI docker build args
+def getDockerBuildArgs() {
+ String local_os = mapOsNameToGoName(getOsName())
+ String local_arch = mapOsArchToGoArch(getOsArch())
+ def res = []
+
+ if(!project.hasProperty('crossCompileCLI') || project.crossCompileCLI == "false") {
+ res = ["CLI_OS=${local_os}", "CLI_ARCH=${local_arch}"]
+ } else {
+ res = ["CLI_OS=mac linux windows", "CLI_ARCH=386 amd64"]
+ }
+
+ return res
+}
+
+def run(cmd, ignoreError = false) {
+ println("Executing '${cmd.join(" ")}'")
+ def proc = cmd.execute()
+ proc.waitFor()
+ if(!ignoreError && proc.exitValue() != 0) {
+ println("Command '${cmd.join(" ")}' failed with exitCode ${proc.exitValue()}")
+ }
+}
+
+def getOsName() {
+ return System.properties['os.name']
+}
+
+def getOsArch() {
+ return System.properties['os.arch']
+}
+
+def mapOsNameToGoName(String osname) {
+ String osname_l = osname.toLowerCase()
+ if (osname_l.contains("nux") || osname.contains("nix")) return "linux"
+ if (osname_l.contains("mac")) return "mac"
+ if (osname_l.contains("windows")) return "windows"
+ return osname_l
+}
+
+def mapOsArchToGoArch(String osarch) {
+ String osarch_l = osarch.toLowerCase()
+ if (osarch_l.contains("x86_64") || osarch_l == "amd64") return "amd64"
+ if (osarch_l.contains("i386") || osarch_l.contains("x86_32")) return "386"
+ return osarch_l
+}
diff --git a/tools/cli/build.sh b/tools/cli/build.sh
new file mode 100644
index 0000000..f4aeb42
--- /dev/null
+++ b/tools/cli/build.sh
@@ -0,0 +1,105 @@
+#!/bin/bash
+
+set +x
+set -e
+
+get_bin_name () {
+ local os=$1
+ local bin="wsk"
+
+ if [ $os = "windows" ]; then
+ bin="${bin}.exe";
+ fi
+
+ echo $bin;
+};
+
+build_cli () {
+ local os=$1
+ local arch=$2
+ local bin=$3
+
+ echo "Building for OS '$os' and architecture '$arch'"
+
+ if [ $os = "mac" ]; then
+ export GOOS=darwin;
+ else
+ export GOOS=$os;
+ fi
+
+ export GOARCH=$arch
+
+ cd /src/github.com/go-whisk-cli
+ go build -ldflags "-X main.CLI_BUILD_TIME=`date -u '+%Y-%m-%dT%H:%M:%S%:z'`" -v -o build/$os/$arch/$bin main.go;
+};
+
+get_compressed_name() {
+ local os=$1
+ local arch=$2
+ local product_name="OpenWhisk_CLI"
+
+ if [ $arch = amd64 ]; then
+ comp_name="$product_name-$os";
+ elif [ $arch = 386 ]; then
+ comp_name="$product_name-$os-32bit";
+ else
+ comp_name="$product_name-$os-$arch";
+ fi
+
+ echo $comp_name;
+};
+
+compress_binary() {
+ local comp_name=$1
+ local bin=$2
+ local os=$3
+ local arch=$4
+
+ cd build/$os/$arch
+
+ if [ $os = "linux" ]; then
+ comp_name="$comp_name.tgz"
+ tar -cvzf $comp_name $bin >/dev/null 2>&1;
+ else
+ comp_name="$comp_name.zip"
+ zip $comp_name $bin >/dev/null 2>&1;
+ fi
+
+ cd ../../..
+ echo $os/$arch/$comp_name;
+};
+
+create_cli_packages() {
+ local dirIndex="{\"cli\":{"
+
+ for platform in $platforms; do
+ dirIndex="$dirIndex\"$platform\":{"
+
+ for arch in $archs; do
+ bin=$(get_bin_name $platform)
+ build_cli $platform $arch $bin
+ comp_name=$(get_compressed_name $platform $arch)
+ comp_path=$(compress_binary $comp_name $bin $platform $arch)
+
+ if [ $arch = $default_arch ]; then
+ dirIndex="$dirIndex\"default\":{\"path\":\"$comp_path\"},";
+ fi
+
+ dirIndex="$dirIndex\"$arch\":{\"path\":\"$comp_path\"},";
+ done
+
+ dirIndex="$(echo $dirIndex | rev | cut -c2- | rev)"
+ dirIndex="$dirIndex},";
+ done
+
+ dirIndex="$(echo $dirIndex | rev | cut -c2- | rev)"
+ dirIndex="$dirIndex}}"
+
+ echo $dirIndex > ./build/content.json
+};
+
+platforms="$CLI_OS"
+archs="$CLI_ARCH";
+default_arch="amd64"
+
+create_cli_packages
diff --git a/tools/cli/go-whisk-cli/Godeps/Godeps.json b/tools/cli/go-whisk-cli/Godeps/Godeps.json
new file mode 100644
index 0000000..ab146c9
--- /dev/null
+++ b/tools/cli/go-whisk-cli/Godeps/Godeps.json
@@ -0,0 +1,85 @@
+{
+ "ImportPath": "go-whisk-cli",
+ "GoVersion": "go1.6",
+ "GodepVersion": "v74",
+ "Packages": [
+ "go-whisk-cli"
+ ],
+ "Deps": [
+ {
+ "ImportPath": "github.com/cloudfoundry/jibber_jabber",
+ "Rev": "bcc4c8345a21301bf47c032ff42dd1aae2fe3027"
+ },
+ {
+ "ImportPath": "github.com/fatih/color",
+ "Comment": "v0.1-19-g87d4004",
+ "Rev": "87d4004f2ab62d0d255e0a38f1680aa534549fe3"
+ },
+ {
+ "ImportPath": "github.com/google/go-querystring/query",
+ "Rev": "9235644dd9e52eeae6fa48efd539fdc351a0af53"
+ },
+ {
+ "ImportPath": "github.com/hokaccha/go-prettyjson",
+ "Rev": "f75235bd99dad4e98ff360db8372d5c0ef1d054a"
+ },
+ {
+ "ImportPath": "github.com/inconshreveable/mousetrap",
+ "Rev": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
+ },
+ {
+ "ImportPath": "github.com/mattn/go-colorable",
+ "Comment": "v0.0.5",
+ "Rev": "9056b7a9f2d1f2d96498d6d146acd1f9d5ed3d59"
+ },
+ {
+ "ImportPath": "github.com/mattn/go-isatty",
+ "Rev": "56b76bdf51f7708750eac80fa38b952bb9f32639"
+ },
+ {
+ "ImportPath": "github.com/mitchellh/go-homedir",
+ "Rev": "1111e456ffea841564ac0fa5f69c26ef44dafec9"
+ },
+ {
+ "ImportPath": "github.com/nicksnyder/go-i18n/i18n",
+ "Comment": "v1.4.0",
+ "Rev": "37e5c2de3e03e4b82693e3fcb4a6aa2cc4eb07e3"
+ },
+ {
+ "ImportPath": "github.com/nicksnyder/go-i18n/i18n/bundle",
+ "Comment": "v1.4.0",
+ "Rev": "37e5c2de3e03e4b82693e3fcb4a6aa2cc4eb07e3"
+ },
+ {
+ "ImportPath": "github.com/nicksnyder/go-i18n/i18n/language",
+ "Comment": "v1.4.0",
+ "Rev": "37e5c2de3e03e4b82693e3fcb4a6aa2cc4eb07e3"
+ },
+ {
+ "ImportPath": "github.com/nicksnyder/go-i18n/i18n/translation",
+ "Comment": "v1.4.0",
+ "Rev": "37e5c2de3e03e4b82693e3fcb4a6aa2cc4eb07e3"
+ },
+ {
+ "ImportPath": "github.com/spf13/cobra",
+ "Rev": "1238ba19d24b0b9ceee2094e1cb31947d45c3e86"
+ },
+ {
+ "ImportPath": "github.com/spf13/pflag",
+ "Rev": "367864438f1b1a3c7db4da06a2f55b144e6784e0"
+ },
+ {
+ "ImportPath": "golang.org/x/sys/unix",
+ "Rev": "7f918dd405547ecb864d14a8ecbbfe205b5f930f"
+ },
+ {
+ "ImportPath": "gopkg.in/yaml.v2",
+ "Rev": "cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b"
+ },
+ {
+ "ImportPath": "github.com/ghodss/yaml",
+ "Comment": "v1.0.0",
+ "Rev": "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tools/cli/go-whisk-cli/Godeps/Readme b/tools/cli/go-whisk-cli/Godeps/Readme
new file mode 100644
index 0000000..4cdaa53
--- /dev/null
+++ b/tools/cli/go-whisk-cli/Godeps/Readme
@@ -0,0 +1,5 @@
+This directory tree is generated automatically by godep.
+
+Please do not edit.
+
+See https://github.com/tools/godep for more information.
diff --git a/tools/cli/go-whisk-cli/commands/action.go b/tools/cli/go-whisk-cli/commands/action.go
new file mode 100644
index 0000000..6e2a558
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/action.go
@@ -0,0 +1,922 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package commands
+
+import (
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "path/filepath"
+ "io"
+ "strings"
+
+ "../../go-whisk/whisk"
+ "../wski18n"
+
+ "github.com/fatih/color"
+ "github.com/spf13/cobra"
+ "github.com/mattn/go-colorable"
+)
+
+const MEMORY_LIMIT = 256
+const TIMEOUT_LIMIT = 60000
+const LOGSIZE_LIMIT = 10
+const ACTIVATION_ID = "activationId"
+const WEB_EXPORT_ANNOT = "web-export"
+const RAW_HTTP_ANNOT = "raw-http"
+const FINAL_ANNOT = "final"
+
+var actionCmd = &cobra.Command{
+ Use: "action",
+ Short: wski18n.T("work with actions"),
+}
+
+var actionCreateCmd = &cobra.Command{
+ Use: "create ACTION_NAME ACTION",
+ Short: wski18n.T("create a new action"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var action *whisk.Action
+ var err error
+
+ if whiskErr := checkArgs(
+ args,
+ 2,
+ 2,
+ "Action create",
+ wski18n.T("An action name and action are required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ if action, err = parseAction(cmd, args, false); err != nil {
+ return actionParseError(cmd, args, err)
+ }
+
+ if _, _, err = client.Actions.Insert(action, false); err != nil {
+ return actionInsertError(action, err)
+ }
+
+ printActionCreated(action.Name)
+
+ return nil
+ },
+}
+
+var actionUpdateCmd = &cobra.Command{
+ Use: "update ACTION_NAME [ACTION]",
+ Short: wski18n.T("update an existing action, or create an action if it does not exist"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var action *whisk.Action
+ var err error
+
+ if whiskErr := checkArgs(
+ args,
+ 1,
+ 2,
+ "Action update",
+ wski18n.T("An action name is required. An action is optional.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ if action, err = parseAction(cmd, args, false); err != nil {
+ return actionParseError(cmd, args, err)
+ }
+
+ if _, _, err = client.Actions.Insert(action, true); err != nil {
+ return actionInsertError(action, err)
+ }
+
+ printActionUpdated(action.Name)
+
+ return nil
+ },
+}
+
+var actionInvokeCmd = &cobra.Command{
+ Use: "invoke ACTION_NAME",
+ Short: wski18n.T("invoke action"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+ var parameters interface{}
+ var qualifiedName QualifiedName
+ var paramArgs []string
+
+ if whiskErr := checkArgs(
+ args,
+ 1,
+ 1,
+ "Action invoke",
+ wski18n.T("An action name is required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ if qualifiedName, err = parseQualifiedName(args[0]); err != nil {
+ return parseQualifiedNameError(args[0], err)
+ }
+
+ client.Namespace = qualifiedName.namespace
+ paramArgs = flags.common.param
+
+ if len(paramArgs) > 0 {
+ if parameters, err = getJSONFromStrings(paramArgs, false); err != nil {
+ return getJSONFromStringsParamError(paramArgs, false, err)
+ }
+ }
+ if flags.action.result {flags.common.blocking = true}
+
+ res, _, err := client.Actions.Invoke(
+ qualifiedName.entityName,
+ parameters,
+ flags.common.blocking,
+ flags.action.result)
+
+ return handleInvocationResponse(qualifiedName, parameters, res, err)
+ },
+}
+
+func handleInvocationResponse(
+ qualifiedName QualifiedName,
+ parameters interface{},
+ result map[string]interface{},
+ err error) (error) {
+ if err == nil {
+ printInvocationMsg(
+ qualifiedName.namespace,
+ qualifiedName.entityName,
+ getValueFromJSONResponse(ACTIVATION_ID, result),
+ result,
+ color.Output)
+ } else {
+ if !flags.common.blocking {
+ return handleInvocationError(err, qualifiedName.entityName, parameters)
+ } else {
+ if isBlockingTimeout(err) {
+ printBlockingTimeoutMsg(
+ qualifiedName.namespace,
+ qualifiedName.entityName,
+ getValueFromJSONResponse(ACTIVATION_ID, result))
+ } else if isApplicationError(err) {
+ printInvocationMsg(
+ qualifiedName.namespace,
+ qualifiedName.entityName,
+ getValueFromJSONResponse(ACTIVATION_ID, result),
+ result,
+ colorable.NewColorableStderr())
+ } else {
+ return handleInvocationError(err, qualifiedName.entityName, parameters)
+ }
+ }
+ }
+
+ return err
+}
+
+var actionGetCmd = &cobra.Command{
+ Use: "get ACTION_NAME [FIELD_FILTER]",
+ Short: wski18n.T("get action"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+ var field string
+ var action *whisk.Action
+ var qualifiedName QualifiedName
+
+ if whiskErr := checkArgs(args, 1, 2, "Action get", wski18n.T("An action name is required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ if len(args) > 1 {
+ field = args[1]
+
+ if !fieldExists(&whisk.Action{}, field) {
+ return invalidFieldFilterError(field)
+ }
+ }
+
+ if qualifiedName, err = parseQualifiedName(args[0]); err != nil {
+ return parseQualifiedNameError(args[0], err)
+ }
+
+ client.Namespace = qualifiedName.namespace
+
+ if action, _, err = client.Actions.Get(qualifiedName.entityName); err != nil {
+ return actionGetError(qualifiedName.entityName, err)
+ }
+
+ if flags.common.summary {
+ printSummary(action)
+ } else {
+ if len(field) > 0 {
+ printActionGetWithField(qualifiedName.entityName, field, action)
+ } else {
+ printActionGet(qualifiedName.entityName, action)
+ }
+ }
+
+ return nil
+ },
+}
+
+var actionDeleteCmd = &cobra.Command{
+ Use: "delete ACTION_NAME",
+ Short: wski18n.T("delete action"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var qualifiedName QualifiedName
+ var err error
+
+ if whiskErr := checkArgs(
+ args,
+ 1,
+ 1,
+ "Action delete",
+ wski18n.T("An action name is required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ if qualifiedName, err = parseQualifiedName(args[0]); err != nil {
+ return parseQualifiedNameError(args[0], err)
+ }
+
+ client.Namespace = qualifiedName.namespace
+
+ if _, err = client.Actions.Delete(qualifiedName.entityName); err != nil {
+ return actionDeleteError(qualifiedName.entityName, err)
+ }
+
+ printActionDeleted(qualifiedName.entityName)
+
+ return nil
+ },
+}
+
+var actionListCmd = &cobra.Command{
+ Use: "list [NAMESPACE]",
+ Short: wski18n.T("list all actions"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var qualifiedName QualifiedName
+ var actions []whisk.Action
+ var err error
+
+ if len(args) == 1 {
+ if qualifiedName, err = parseQualifiedName(args[0]); err != nil {
+ return parseQualifiedNameError(args[0], err)
+ }
+
+ client.Namespace = qualifiedName.namespace
+ } else if whiskErr := checkArgs(
+ args,
+ 0,
+ 1,
+ "Action list",
+ wski18n.T("An optional namespace is the only valid argument.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ options := &whisk.ActionListOptions{
+ Skip: flags.common.skip,
+ Limit: flags.common.limit,
+ }
+
+ if actions, _, err = client.Actions.List(qualifiedName.entityName, options); err != nil {
+ return actionListError(qualifiedName.entityName, options, err)
+ }
+
+ printList(actions)
+
+ return nil
+ },
+}
+
+func parseAction(cmd *cobra.Command, args []string, update bool) (*whisk.Action, error) {
+ var err error
+ var artifact string
+ var existingAction *whisk.Action
+ var paramArgs []string
+ var annotArgs []string
+ var parameters interface{}
+ var annotations interface{}
+
+ qualifiedName := QualifiedName{}
+
+ if qualifiedName, err = parseQualifiedName(args[0]); err != nil {
+ return nil, parseQualifiedNameError(args[0], err)
+ }
+
+ if len(args) == 2 {
+ artifact = args[1]
+ }
+
+ client.Namespace = qualifiedName.namespace
+ action := new(whisk.Action)
+ action.Name = qualifiedName.entityName
+ action.Namespace = qualifiedName.namespace
+ action.Limits = getLimits(
+ cmd.LocalFlags().Changed(MEMORY_FLAG),
+ cmd.LocalFlags().Changed(LOG_SIZE_FLAG),
+ cmd.LocalFlags().Changed(TIMEOUT_FLAG),
+ flags.action.memory,
+ flags.action.logsize,
+ flags.action.timeout)
+
+ paramArgs = flags.common.param
+ annotArgs = flags.common.annotation
+
+ if len(paramArgs) > 0 {
+ if parameters, err = getJSONFromStrings(paramArgs, true); err != nil {
+ return nil, getJSONFromStringsParamError(paramArgs, true, err)
+ }
+
+ action.Parameters = parameters.(whisk.KeyValueArr)
+ }
+
+ if len(annotArgs) > 0 {
+ if annotations, err = getJSONFromStrings(annotArgs, true); err != nil {
+ return nil, getJSONFromStringsAnnotError(annotArgs, true, err)
+ }
+
+ action.Annotations = annotations.(whisk.KeyValueArr)
+ }
+
+ if flags.action.copy {
+ copiedQualifiedName := QualifiedName{}
+
+ if copiedQualifiedName, err = parseQualifiedName(args[1]); err != nil {
+ return nil, parseQualifiedNameError(args[1], err)
+ }
+
+ client.Namespace = copiedQualifiedName.namespace
+
+ if existingAction, _, err = client.Actions.Get(copiedQualifiedName.entityName); err != nil {
+ return nil, actionGetError(copiedQualifiedName.entityName, err)
+ }
+
+ client.Namespace = qualifiedName.namespace
+ action.Exec = existingAction.Exec
+ action.Parameters = append(action.Parameters, existingAction.Parameters...)
+ action.Annotations = append(action.Annotations, existingAction.Annotations...)
+ } else if flags.action.sequence {
+ action.Exec = new(whisk.Exec)
+ action.Exec.Kind = "sequence"
+ action.Exec.Components = csvToQualifiedActions(artifact)
+ } else if len(artifact) > 0 {
+ action.Exec, err = getExec(args[1], flags.action.kind, flags.action.docker, flags.action.main)
+ }
+
+ if cmd.LocalFlags().Changed(WEB_FLAG) {
+ action.Annotations, err = webAction(flags.action.web, action.Annotations, qualifiedName.entityName, update)
+ }
+
+ whisk.Debug(whisk.DbgInfo, "Parsed action struct: %#v\n", action)
+
+ return action, err
+}
+
+func getExec(artifact string, kind string, isDocker bool, mainEntry string) (*whisk.Exec, error) {
+ var err error
+ var code string
+ var exec *whisk.Exec
+
+ ext := filepath.Ext(artifact)
+ exec = new(whisk.Exec)
+
+ if !isDocker || ext == ".zip" {
+ code, err = readFile(artifact)
+
+ if ext == ".zip" || ext == ".jar" {
+ // Base64 encode the file
+ code = base64.StdEncoding.EncodeToString([]byte(code))
+ exec.Code = &code
+ } else {
+ exec.Code = &code
+ }
+
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "readFile(%s) error: %s\n", artifact, err)
+ return nil, err
+ }
+ }
+
+ if len(kind) > 0 {
+ exec.Kind = kind
+ } else if isDocker {
+ exec.Kind = "blackbox"
+ if ext != ".zip" {
+ exec.Image = artifact
+ } else {
+ exec.Image = "openwhisk/dockerskeleton"
+ }
+ } else if ext == ".swift" {
+ exec.Kind = "swift:default"
+ } else if ext == ".js" {
+ exec.Kind = "nodejs:default"
+ } else if ext == ".py" {
+ exec.Kind = "python:default"
+ } else if ext == ".jar" {
+ exec.Kind = "java:default"
+ } else {
+ if ext == ".zip" {
+ return nil, zipKindError()
+ } else {
+ return nil, extensionError(ext)
+ }
+ }
+
+ // Error if entry point is not specified for Java
+ if len(mainEntry) != 0 {
+ exec.Main = mainEntry
+ } else {
+ if exec.Kind == "java" {
+ return nil, javaEntryError()
+ }
+ }
+
+ return exec, nil
+}
+
+func webAction(webMode string, annotations whisk.KeyValueArr, entityName string, fetch bool) (whisk.KeyValueArr, error){
+ switch strings.ToLower(webMode) {
+ case "yes":
+ fallthrough
+ case "true":
+ return webActionAnnotations(fetch, annotations, entityName, addWebAnnotations)
+ case "no":
+ fallthrough
+ case "false":
+ return webActionAnnotations(fetch, annotations, entityName, deleteWebAnnotations)
+ case "raw":
+ return webActionAnnotations(fetch, annotations, entityName, addRawAnnotations)
+ default:
+ return nil, webInputError(webMode)
+ }
+}
+
+type WebActionAnnotationMethod func(annotations whisk.KeyValueArr) (whisk.KeyValueArr)
+
+func webActionAnnotations(
+ fetchAnnotations bool,
+ annotations whisk.KeyValueArr,
+ entityName string,
+ webActionAnnotationMethod WebActionAnnotationMethod) (whisk.KeyValueArr, error) {
+ var action *whisk.Action
+ var err error
+
+ if annotations != nil || !fetchAnnotations {
+ annotations = webActionAnnotationMethod(annotations)
+ } else {
+ if action, _, err = client.Actions.Get(entityName); err != nil {
+ return nil, actionGetError(entityName, err)
+ } else {
+ annotations = webActionAnnotationMethod(action.Annotations)
+ }
+ }
+
+ return annotations, nil
+}
+
+func addWebAnnotations(annotations whisk.KeyValueArr) (whisk.KeyValueArr) {
+ annotations = deleteWebAnnotationKeys(annotations)
+ annotations = addKeyValue(WEB_EXPORT_ANNOT, true, annotations)
+ annotations = addKeyValue(RAW_HTTP_ANNOT, false, annotations)
+ annotations = addKeyValue(FINAL_ANNOT, true, annotations)
+
+ return annotations
+}
+
+func deleteWebAnnotations(annotations whisk.KeyValueArr) (whisk.KeyValueArr) {
+ annotations = deleteWebAnnotationKeys(annotations)
+ annotations = addKeyValue(WEB_EXPORT_ANNOT, false, annotations)
+ annotations = addKeyValue(RAW_HTTP_ANNOT, false, annotations)
+ annotations = addKeyValue(FINAL_ANNOT, false, annotations)
+
+ return annotations
+}
+
+func addRawAnnotations(annotations whisk.KeyValueArr) (whisk.KeyValueArr) {
+ annotations = deleteWebAnnotationKeys(annotations)
+ annotations = addKeyValue(WEB_EXPORT_ANNOT, true, annotations)
+ annotations = addKeyValue(RAW_HTTP_ANNOT, true, annotations)
+ annotations = addKeyValue(FINAL_ANNOT, true, annotations)
+
+ return annotations
+}
+
+func deleteWebAnnotationKeys(annotations whisk.KeyValueArr) (whisk.KeyValueArr) {
+ annotations = deleteKey(WEB_EXPORT_ANNOT, annotations)
+ annotations = deleteKey(RAW_HTTP_ANNOT, annotations)
+ annotations = deleteKey(FINAL_ANNOT, annotations)
+
+ return annotations
+}
+
+func getLimits(memorySet bool, logSizeSet bool, timeoutSet bool, memory int, logSize int, timeout int) (*whisk.Limits) {
+ var limits *whisk.Limits
+
+ if memorySet || logSizeSet || timeoutSet {
+ limits = new(whisk.Limits)
+
+ if memorySet {
+ limits.Memory = &memory
+ }
+
+ if logSizeSet {
+ limits.Logsize = &logSize
+ }
+
+ if timeoutSet {
+ limits.Timeout = &timeout
+ }
+ }
+
+ return limits
+}
+
+func nestedError(errorMessage string, err error) (error) {
+ return whisk.MakeWskErrorFromWskError(
+ errors.New(errorMessage),
+ err,
+ whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG,
+ whisk.DISPLAY_USAGE)
+}
+
+func nonNestedError(errorMessage string) (error) {
+ return whisk.MakeWskError(
+ errors.New(errorMessage),
+ whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG,
+ whisk.DISPLAY_USAGE)
+}
+
+func actionParseError(cmd *cobra.Command, args []string, err error) (error) {
+ whisk.Debug(whisk.DbgError, "parseAction(%s, %s) error: %s\n", cmd, args, err)
+
+ errMsg := wski18n.T(
+ "Unable to parse action command arguments: {{.err}}",
+ map[string]interface{}{
+ "err": err,
+ })
+
+ return nestedError(errMsg, err)
+}
+
+func actionInsertError(action *whisk.Action, err error) (error) {
+ whisk.Debug(whisk.DbgError, "client.Actions.Insert(%#v, false) error: %s\n", action, err)
+
+ errMsg := wski18n.T(
+ "Unable to create action '{{.name}}': {{.err}}",
+ map[string]interface{}{
+ "name": action.Name,
+ "err": err,
+ })
+
+ return nestedError(errMsg, err)
+}
+
+func parseQualifiedNameError(entityName string, err error) (error) {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", entityName, err)
+
+ errMsg := wski18n.T(
+ "'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{
+ "name": entityName,
+ "err": err,
+ })
+
+ return nestedError(errMsg, err)
+}
+
+func getJSONFromStringsParamError(params []string, keyValueFormat bool, err error) (error) {
+ whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, %t) failed: %s\n", params, keyValueFormat, err)
+
+ errMsg := wski18n.T(
+ "Invalid parameter argument '{{.param}}': {{.err}}",
+ map[string]interface{}{
+ "param": fmt.Sprintf("%#v", params),
+ "err": err,
+ })
+
+ return nestedError(errMsg, err)
+}
+
+func getJSONFromStringsAnnotError(annots []string, keyValueFormat bool, err error) (error) {
+ whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, %t) failed: %s\n", annots, keyValueFormat, err)
+
+ errMsg := wski18n.T(
+ "Invalid annotation argument '{{.annotation}}': {{.err}}",
+ map[string]interface{}{
+ "annotation": fmt.Sprintf("%#v", annots),
+ "err": err,
+ })
+
+ return nestedError(errMsg, err)
+}
+
+func invalidFieldFilterError(field string) (error) {
+ errMsg := wski18n.T(
+ "Invalid field filter '{{.arg}}'.",
+ map[string]interface{}{
+ "arg": field,
+ })
+
+ return nonNestedError(errMsg)
+}
+
+func actionDeleteError(entityName string, err error) (error) {
+ whisk.Debug(whisk.DbgError, "client.Actions.Delete(%s) error: %s\n", entityName, err)
+
+ errMsg := wski18n.T(
+ "Unable to delete action '{{.name}}': {{.err}}",
+ map[string]interface{}{
+ "name": entityName,
+ "err": err,
+ })
+
+ return nestedError(errMsg, err)
+}
+
+func actionGetError(entityName string, err error) (error) {
+ whisk.Debug(whisk.DbgError, "client.Actions.Get(%s) error: %s\n", entityName, err)
+
+ errMsg := wski18n.T(
+ "Unable to get action '{{.name}}': {{.err}}",
+ map[string]interface{}{
+ "name": entityName,
+ "err": err,
+ })
+
+ return nestedError(errMsg, err)
+}
+
+func handleInvocationError(err error, entityName string, parameters interface{}) (error) {
+ whisk.Debug(
+ whisk.DbgError,
+ "client.Actions.Invoke(%s, %s, %t) error: %s\n",
+ entityName, parameters,
+ flags.common.blocking,
+ err)
+
+ errMsg := wski18n.T(
+ "Unable to invoke action '{{.name}}': {{.err}}",
+ map[string]interface{}{
+ "name": entityName,
+ "err": err,
+ })
+
+ return nestedError(errMsg, err)
+}
+
+func actionListError(entityName string, options *whisk.ActionListOptions, err error) (error) {
+ whisk.Debug(whisk.DbgError, "client.Actions.List(%s, %#v) error: %s\n", entityName, options, err)
+
+ errMsg := wski18n.T(
+ "Unable to obtain the list of actions for namespace '{{.name}}': {{.err}}",
+ map[string]interface{}{
+ "name": getClientNamespace(),
+ "err": err,
+ })
+
+ return nestedError(errMsg, err)
+}
+
+func webInputError(arg string) (error) {
+ errMsg := wski18n.T(
+ "Invalid argument '{{.arg}}' for --web flag. Valid input consist of 'yes', 'true', 'raw', 'false', or 'no'.",
+ map[string]interface{}{
+ "arg": arg,
+ })
+
+ return nonNestedError(errMsg)
+}
+
+func zipKindError() (error) {
+ errMsg := wski18n.T("creating an action from a .zip artifact requires specifying the action kind explicitly")
+
+ return nonNestedError(errMsg)
+}
+
+func extensionError(extension string) (error) {
+ errMsg := wski18n.T(
+ "'{{.name}}' is not a supported action runtime",
+ map[string]interface{}{
+ "name": extension,
+ })
+
+ return nonNestedError(errMsg)
+}
+
+func javaEntryError() (error) {
+ errMsg := wski18n.T("Java actions require --main to specify the fully-qualified name of the main class")
+
+ return nonNestedError(errMsg)
+}
+
+func printActionCreated(entityName string) {
+ fmt.Fprintf(
+ color.Output,
+ wski18n.T(
+ "{{.ok}} created action {{.name}}\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ "name": boldString(entityName),
+ }))
+}
+
+func printActionUpdated(entityName string) {
+ fmt.Fprintf(
+ color.Output,
+ wski18n.T(
+ "{{.ok}} updated action {{.name}}\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ "name": boldString(entityName),
+ }))
+}
+
+func printBlockingTimeoutMsg(namespace string, entityName string, activationID interface{}) {
+ fmt.Fprintf(
+ colorable.NewColorableStderr(),
+ wski18n.T(
+ "{{.ok}} invoked /{{.namespace}}/{{.name}}, but the request has not yet finished, with id {{.id}}\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ "namespace": boldString(namespace),
+ "name": boldString(entityName),
+ "id": boldString(activationID),
+ }))
+}
+
+func printInvocationMsg(
+ namespace string,
+ entityName string,
+ activationID interface{},
+ response map[string]interface{},
+ outputStream io.Writer) {
+ if !flags.action.result {
+ fmt.Fprintf(
+ outputStream,
+ wski18n.T(
+ "{{.ok}} invoked /{{.namespace}}/{{.name}} with id {{.id}}\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ "namespace": boldString(namespace),
+ "name": boldString(entityName),
+ "id": boldString(activationID),
+ }))
+ }
+
+ if flags.common.blocking {
+ printJSON(response, outputStream)
+ }
+}
+
+func printActionGetWithField(entityName string, field string, action *whisk.Action) {
+ fmt.Fprintf(
+ color.Output,
+ wski18n.T(
+ "{{.ok}} got action {{.name}}, displaying field {{.field}}\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ "name": boldString(entityName),
+ "field": boldString(field),
+ }))
+
+ printField(action, field)
+}
+
+func printActionGet(entityName string, action *whisk.Action) {
+ fmt.Fprintf(
+ color.Output,
+ wski18n.T("{{.ok}} got action {{.name}}\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ "name": boldString(entityName),
+ }))
+
+ printJSON(action)
+}
+
+func printActionDeleted(entityName string) {
+ fmt.Fprintf(
+ color.Output,
+ wski18n.T(
+ "{{.ok}} deleted action {{.name}}\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ "name": boldString(entityName),
+ }))
+}
+
+// Check if the specified action is a web-action
+func isWebAction(client *whisk.Client, qname QualifiedName) error {
+ var err error = nil
+
+ savedNs := client.Namespace
+ client.Namespace = qname.namespace
+ fullActionName := "/" + qname.namespace + "/" + qname.entityName
+
+ action, _, err := client.Actions.Get(qname.entityName)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Actions.Get(%s) error: %s\n", fullActionName, err)
+ whisk.Debug(whisk.DbgError, "Unable to obtain action '%s' for web action validation\n", fullActionName)
+ err = errors.New(wski18n.T("API action '{{.name}}' does not exist", map[string]interface{}{"name": fullActionName}))
+ } else {
+ err = errors.New(wski18n.T("API action '{{.name}}' is not a web action. Issue 'wsk action update {{.name}} --web true' to convert the action to a web action.",
+ map[string]interface{}{"name": fullActionName}))
+ weVal := getValue(action.Annotations, "web-export")
+ if (weVal == nil) {
+ whisk.Debug(whisk.DbgError, "getValue(annotations, web-export) for action %s found no value\n", fullActionName)
+ } else {
+ var webExport bool
+ var ok bool
+ if webExport, ok = weVal.(bool); !ok {
+ whisk.Debug(whisk.DbgError, "web-export annotation value (%v) is not a boolean\n", weVal)
+ } else if !webExport {
+ whisk.Debug(whisk.DbgError, "web-export annotation value is false\n", weVal)
+ } else {
+ err = nil
+ }
+ }
+ }
+
+ client.Namespace = savedNs
+ return err
+}
+
+func init() {
+ actionCreateCmd.Flags().BoolVar(&flags.action.docker, "docker", false, wski18n.T("treat ACTION as docker image path on dockerhub"))
+ actionCreateCmd.Flags().BoolVar(&flags.action.copy, "copy", false, wski18n.T("treat ACTION as the name of an existing action"))
+ actionCreateCmd.Flags().BoolVar(&flags.action.sequence, "sequence", false, wski18n.T("treat ACTION as comma separated sequence of actions to invoke"))
+ actionCreateCmd.Flags().StringVar(&flags.action.kind, "kind", "", wski18n.T("the `KIND` of the action runtime (example: swift:default, nodejs:default)"))
+ actionCreateCmd.Flags().StringVar(&flags.action.main, "main", "", wski18n.T("the name of the action entry point (function or fully-qualified method name when applicable)"))
+ actionCreateCmd.Flags().IntVarP(&flags.action.timeout, "timeout", "t", TIMEOUT_LIMIT, wski18n.T("the timeout `LIMIT` in milliseconds after which the action is terminated"))
+ actionCreateCmd.Flags().IntVarP(&flags.action.memory, "memory", "m", MEMORY_LIMIT, wski18n.T("the maximum memory `LIMIT` in MB for the action"))
+ actionCreateCmd.Flags().IntVarP(&flags.action.logsize, "logsize", "l", LOGSIZE_LIMIT, wski18n.T("the maximum log size `LIMIT` in MB for the action"))
+ actionCreateCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", nil, wski18n.T("annotation values in `KEY VALUE` format"))
+ actionCreateCmd.Flags().StringVarP(&flags.common.annotFile, "annotation-file", "A", "", wski18n.T("`FILE` containing annotation values in JSON format"))
+ actionCreateCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", nil, wski18n.T("parameter values in `KEY VALUE` format"))
+ actionCreateCmd.Flags().StringVarP(&flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
+ actionCreateCmd.Flags().StringVar(&flags.action.web, "web", "", wski18n.T("treat ACTION as a web action, a raw HTTP web action, or as a standard action; yes | true = web action, raw = raw HTTP web action, no | false = standard action"))
+
+ actionUpdateCmd.Flags().BoolVar(&flags.action.docker, "docker", false, wski18n.T("treat ACTION as docker image path on dockerhub"))
+ actionUpdateCmd.Flags().BoolVar(&flags.action.copy, "copy", false, wski18n.T("treat ACTION as the name of an existing action"))
+ actionUpdateCmd.Flags().BoolVar(&flags.action.sequence, "sequence", false, wski18n.T("treat ACTION as comma separated sequence of actions to invoke"))
+ actionUpdateCmd.Flags().StringVar(&flags.action.kind, "kind", "", wski18n.T("the `KIND` of the action runtime (example: swift:default, nodejs:default)"))
+ actionUpdateCmd.Flags().StringVar(&flags.action.main, "main", "", wski18n.T("the name of the action entry point (function or fully-qualified method name when applicable)"))
+ actionUpdateCmd.Flags().IntVarP(&flags.action.timeout, "timeout", "t", TIMEOUT_LIMIT, wski18n.T("the timeout `LIMIT` in milliseconds after which the action is terminated"))
+ actionUpdateCmd.Flags().IntVarP(&flags.action.memory, "memory", "m", MEMORY_LIMIT, wski18n.T("the maximum memory `LIMIT` in MB for the action"))
+ actionUpdateCmd.Flags().IntVarP(&flags.action.logsize, "logsize", "l", LOGSIZE_LIMIT, wski18n.T("the maximum log size `LIMIT` in MB for the action"))
+ actionUpdateCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", []string{}, wski18n.T("annotation values in `KEY VALUE` format"))
+ actionUpdateCmd.Flags().StringVarP(&flags.common.annotFile, "annotation-file", "A", "", wski18n.T("`FILE` containing annotation values in JSON format"))
+ actionUpdateCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format"))
+ actionUpdateCmd.Flags().StringVarP(&flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
+ actionUpdateCmd.Flags().StringVar(&flags.action.web, "web", "", wski18n.T("treat ACTION as a web action, a raw HTTP web action, or as a standard action; yes | true = web action, raw = raw HTTP web action, no | false = standard action"))
+
+ actionInvokeCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format"))
+ actionInvokeCmd.Flags().StringVarP(&flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
+ actionInvokeCmd.Flags().BoolVarP(&flags.common.blocking, "blocking", "b", false, wski18n.T("blocking invoke"))
+ actionInvokeCmd.Flags().BoolVarP(&flags.action.result, "result", "r", false, wski18n.T("blocking invoke; show only activation result (unless there is a failure)"))
+
+ actionGetCmd.Flags().BoolVarP(&flags.common.summary, "summary", "s", false, wski18n.T("summarize action details"))
+
+ actionListCmd.Flags().IntVarP(&flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of actions from the result"))
+ actionListCmd.Flags().IntVarP(&flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of actions from the collection"))
+
+ actionCmd.AddCommand(
+ actionCreateCmd,
+ actionUpdateCmd,
+ actionInvokeCmd,
+ actionGetCmd,
+ actionDeleteCmd,
+ actionListCmd,
+ )
+}
diff --git a/tools/cli/go-whisk-cli/commands/activation.go b/tools/cli/go-whisk-cli/commands/activation.go
new file mode 100644
index 0000000..04c879a
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/activation.go
@@ -0,0 +1,363 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package commands
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "os/signal"
+ "syscall"
+ "time"
+
+ "../../go-whisk/whisk"
+ "../wski18n"
+
+ "github.com/fatih/color"
+ "github.com/spf13/cobra"
+)
+
+const (
+ PollInterval = time.Second * 2
+ Delay = time.Second * 5
+)
+
+// activationCmd represents the activation command
+var activationCmd = &cobra.Command{
+ Use: "activation",
+ Short: wski18n.T("work with activations"),
+}
+
+var activationListCmd = &cobra.Command{
+ Use: "list [NAMESPACE or NAME]",
+ Short: wski18n.T("list activations"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+ qName := QualifiedName{}
+
+ // Specifying an activation item name filter is optional
+ if len(args) == 1 {
+ whisk.Debug(whisk.DbgInfo, "Activation item name filter '%s' provided\n", args[0])
+ qName, err = parseQualifiedName(args[0])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errStr := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": args[0], "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ ns := qName.namespace
+ if len(ns) == 0 {
+ whisk.Debug(whisk.DbgError, "Namespace '%s' is invalid\n", ns)
+ errStr := wski18n.T("Namespace '{{.name}}' is invalid", map[string]interface{}{"name": ns})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return werr
+ }
+
+ client.Namespace = ns
+ } else if whiskErr := checkArgs(args, 0, 1, "Activation list",
+ wski18n.T("An optional namespace is the only valid argument.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ options := &whisk.ActivationListOptions{
+ Name: qName.entityName,
+ Limit: flags.common.limit,
+ Skip: flags.common.skip,
+ Upto: flags.activation.upto,
+ Since: flags.activation.since,
+ Docs: flags.common.full,
+ }
+ activations, _, err := client.Activations.List(options)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Activations.List() error: %s\n", err)
+ errStr := wski18n.T("Unable to obtain the list of activations for namespace '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": getClientNamespace(), "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ // When the --full (URL contains "?docs=true") option is specified, display the entire activation details
+ if options.Docs == true {
+ printFullActivationList(activations)
+ } else {
+ printList(activations)
+ }
+
+ return nil
+ },
+}
+
+var activationGetCmd = &cobra.Command{
+ Use: "get ACTIVATION_ID [FIELD_FILTER]",
+ Short: wski18n.T("get activation"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var field string
+
+ if whiskErr := checkArgs(args, 1, 2, "Activation get",
+ wski18n.T("An activation ID is required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ if len(args) > 1 {
+ field = args[1]
+
+ if !fieldExists(&whisk.Activation{}, field) {
+ errMsg := wski18n.T("Invalid field filter '{{.arg}}'.", map[string]interface{}{"arg": field})
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+ }
+
+ id := args[0]
+ activation, _, err := client.Activations.Get(id)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Activations.Get(%s) failed: %s\n", id, err)
+ errStr := wski18n.T("Unable to get activation '{{.id}}': {{.err}}",
+ map[string]interface{}{"id": id, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ if flags.common.summary {
+ fmt.Printf(
+ wski18n.T("activation result for /{{.namespace}}/{{.name}} ({{.status}} at {{.time}})\n",
+ map[string]interface{}{
+ "namespace": activation.Namespace,
+ "name": activation.Name,
+ "status": activation.Response.Status,
+ "time": time.Unix(activation.End/1000, 0)}))
+ printJSON(activation.Response.Result)
+ } else {
+
+ if len(field) > 0 {
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} got activation {{.id}}, displaying field {{.field}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "id": boldString(id),
+ "field": boldString(field)}))
+ printField(activation, field)
+ } else {
+ fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got activation {{.id}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "id": boldString(id)}))
+ printJSON(activation)
+ }
+ }
+
+ return nil
+ },
+}
+
+var activationLogsCmd = &cobra.Command{
+ Use: "logs ACTIVATION_ID",
+ Short: wski18n.T("get the logs of an activation"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+
+ if whiskErr := checkArgs(args, 1, 1, "Activation logs",
+ wski18n.T("An activation ID is required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ id := args[0]
+ activation, _, err := client.Activations.Logs(id)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Activations.Logs(%s) failed: %s\n", id, err)
+ errStr := wski18n.T("Unable to get logs for activation '{{.id}}': {{.err}}",
+ map[string]interface{}{"id": id, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ printActivationLogs(activation.Logs)
+ return nil
+ },
+}
+
+var activationResultCmd = &cobra.Command{
+ Use: "result ACTIVATION_ID",
+ Short: "get the result of an activation",
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+
+ if whiskErr := checkArgs(args, 1, 1, "Activation result",
+ wski18n.T("An activation ID is required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ id := args[0]
+ result, _, err := client.Activations.Result(id)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Activations.result(%s) failed: %s\n", id, err)
+ errStr := wski18n.T("Unable to get result for activation '{{.id}}': {{.err}}",
+ map[string]interface{}{"id": id, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ printJSON(result.Result)
+ return nil
+ },
+}
+
+var activationPollCmd = &cobra.Command{
+ Use: "poll [NAMESPACE]",
+ Short: wski18n.T("poll continuously for log messages from currently running actions"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var name string
+ var pollSince int64 // Represents an instant in time (in milliseconds since Jan 1 1970)
+
+ if len(args) == 1 {
+ name = args[0]
+ } else if whiskErr := checkArgs(args, 0, 1, "Activation poll",
+ wski18n.T("An optional namespace is the only valid argument.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt)
+ signal.Notify(c, syscall.SIGTERM)
+ go func() {
+ <-c
+ fmt.Println(wski18n.T("Poll terminated"))
+ os.Exit(1)
+ }()
+ fmt.Println(wski18n.T("Enter Ctrl-c to exit."))
+
+ // Map used to track activation records already displayed to the console
+ reported := make(map[string]bool)
+
+ if flags.activation.sinceSeconds+
+ flags.activation.sinceMinutes+
+ flags.activation.sinceHours+
+ flags.activation.sinceDays ==
+ 0 {
+ options := &whisk.ActivationListOptions{
+ Limit: 1,
+ Docs: true,
+ }
+ activationList, _, err := client.Activations.List(options)
+ if err != nil {
+ whisk.Debug(whisk.DbgWarn, "client.Activations.List() error: %s\n", err)
+ whisk.Debug(whisk.DbgWarn, "Ignoring client.Activations.List failure; polling for activations since 'now'\n")
+ pollSince = time.Now().Unix() * 1000 // Convert to milliseconds
+ } else {
+ if len(activationList) > 0 {
+ lastActivation := activationList[0] // Activation.Start is in milliseconds since Jan 1 1970
+ pollSince = lastActivation.Start + 1 // Use it's start time as the basis of the polling
+ }
+ }
+ } else {
+ pollSince = time.Now().Unix() * 1000 // Convert to milliseconds
+
+ // ParseDuration takes a string like "2h45m15s"; create this duration string from the command arguments
+ durationStr := fmt.Sprintf("%dh%dm%ds",
+ flags.activation.sinceHours + flags.activation.sinceDays*24,
+ flags.activation.sinceMinutes,
+ flags.activation.sinceSeconds,
+ )
+ duration, err := time.ParseDuration(durationStr)
+ if err == nil {
+ pollSince = pollSince - duration.Nanoseconds()/1000/1000 // Convert to milliseconds
+ } else {
+ whisk.Debug(whisk.DbgError, "time.ParseDuration(%s) failure: %s\n", durationStr, err)
+ }
+ }
+
+ fmt.Printf(wski18n.T("Polling for activation logs\n"))
+ whisk.Verbose("Polling starts from %s\n", time.Unix(pollSince/1000, 0))
+ localStartTime := time.Now()
+
+ // Polling loop
+ for {
+ if flags.activation.exit > 0 {
+ localDuration := time.Since(localStartTime)
+ if int(localDuration.Seconds()) > flags.activation.exit {
+ whisk.Debug(whisk.DbgInfo, "Poll time (%d seconds) expired; polling loop stopped\n", flags.activation.exit)
+ return nil
+ }
+ }
+ whisk.Verbose("Polling for activations since %s\n", time.Unix(pollSince/1000, 0))
+ options := &whisk.ActivationListOptions{
+ Name: name,
+ Since: pollSince,
+ Docs: true,
+ Limit: 0,
+ Skip: 0,
+ }
+
+ activations, _, err := client.Activations.List(options)
+ if err != nil {
+ whisk.Debug(whisk.DbgWarn, "client.Activations.List() error: %s\n", err)
+ whisk.Debug(whisk.DbgWarn, "Ignoring client.Activations.List failure; continuing to poll for activations\n")
+ continue
+ }
+
+ for _, activation := range activations {
+ if reported[activation.ActivationID] == true {
+ continue
+ } else {
+ fmt.Printf(
+ wski18n.T("\nActivation: {{.name}} ({{.id}})\n",
+ map[string]interface{}{"name": activation.Name, "id": activation.ActivationID}))
+ printJSON(activation.Logs)
+ reported[activation.ActivationID] = true
+ }
+ }
+ time.Sleep(time.Second * 2)
+ }
+ return nil
+ },
+}
+
+func init() {
+ activationListCmd.Flags().IntVarP(&flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of activations from the result"))
+ activationListCmd.Flags().IntVarP(&flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of activations from the collection"))
+ activationListCmd.Flags().BoolVarP(&flags.common.full, "full", "f", false, wski18n.T("include full activation description"))
+ activationListCmd.Flags().Int64Var(&flags.activation.upto, "upto", 0, wski18n.T("return activations with timestamps earlier than `UPTO`; measured in milliseconds since Th, 01, Jan 1970"))
+ activationListCmd.Flags().Int64Var(&flags.activation.since, "since", 0, wski18n.T("return activations with timestamps later than `SINCE`; measured in milliseconds since Th, 01, Jan 1970"))
+
+ activationGetCmd.Flags().BoolVarP(&flags.common.summary, "summary", "s", false, wski18n.T("summarize activation details"))
+
+ activationPollCmd.Flags().IntVarP(&flags.activation.exit, "exit", "e", 0, wski18n.T("stop polling after `SECONDS` seconds"))
+ activationPollCmd.Flags().IntVar(&flags.activation.sinceSeconds, "since-seconds", 0, wski18n.T("start polling for activations `SECONDS` seconds ago"))
+ activationPollCmd.Flags().IntVar(&flags.activation.sinceMinutes, "since-minutes", 0, wski18n.T("start polling for activations `MINUTES` minutes ago"))
+ activationPollCmd.Flags().IntVar(&flags.activation.sinceHours, "since-hours", 0, wski18n.T("start polling for activations `HOURS` hours ago"))
+ activationPollCmd.Flags().IntVar(&flags.activation.sinceDays, "since-days", 0, wski18n.T("start polling for activations `DAYS` days ago"))
+
+ activationCmd.AddCommand(
+ activationListCmd,
+ activationGetCmd,
+ activationLogsCmd,
+ activationResultCmd,
+ activationPollCmd,
+ )
+}
diff --git a/tools/cli/go-whisk-cli/commands/api.go b/tools/cli/go-whisk-cli/commands/api.go
new file mode 100644
index 0000000..a5cbaea
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/api.go
@@ -0,0 +1,1585 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package commands
+
+import (
+ "bytes"
+ "bufio"
+ "errors"
+ "fmt"
+ "reflect"
+ "strconv"
+ "strings"
+
+ "../../go-whisk/whisk"
+ "../wski18n"
+
+ "github.com/fatih/color"
+ "github.com/ghodss/yaml"
+ "github.com/spf13/cobra"
+ "encoding/json"
+)
+
+const (
+ yamlFileExtension = "yaml"
+ ymlFileExtension = "yml"
+
+ formatOptionYaml = "yaml"
+ formatOptionJson = "json"
+)
+
+//////////////
+// Commands //
+//////////////
+
+var apiExperimentalCmd = &cobra.Command{
+ Use: "api-experimental",
+ Short: wski18n.T("work with APIs (experimental)"),
+}
+
+var apiCmd = &cobra.Command{
+ Use: "api",
+ Short: wski18n.T("work with APIs"),
+}
+
+var apiCreateCmd = &cobra.Command{
+ Use: "create ([BASE_PATH] API_PATH API_VERB ACTION] | --config-file CFG_FILE) ",
+ Short: wski18n.T("create a new API"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+
+ var api *whisk.Api
+ var err error
+
+ if (len(args) == 0 && flags.api.configfile == "") {
+ whisk.Debug(whisk.DbgError, "No swagger file and no arguments\n")
+ errMsg := wski18n.T("Invalid argument(s). Specify a swagger file or specify an API base path with an API path, an API verb, and an action name.")
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return whiskErr
+ } else if (len(args) == 0 && flags.api.configfile != "") {
+ api, err = parseSwaggerApi()
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseSwaggerApi() error: %s\n", err)
+ errMsg := wski18n.T("Unable to parse swagger file: {{.err}}", map[string]interface{}{"err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return whiskErr
+ }
+ } else {
+ if whiskErr := checkArgs(args, 3, 4, "Api create",
+ wski18n.T("Specify a swagger file or specify an API base path with an API path, an API verb, and an action name.")); whiskErr != nil {
+ return whiskErr
+ }
+ api, err = parseApi(cmd, args)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseApi(%s, %s) error: %s\n", cmd, args, err)
+ errMsg := wski18n.T("Unable to parse api command arguments: {{.err}}",
+ map[string]interface{}{"err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return whiskErr
+ }
+ }
+
+ apiCreateReq := new(whisk.ApiCreateRequest)
+ apiCreateReq.ApiDoc = api
+ apiCreateReqOptions := new(whisk.ApiCreateRequestOptions)
+ retApi, _, err := client.Apis.Insert(apiCreateReq, apiCreateReqOptions, whisk.DoNotOverwrite)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Apis.Insert(%#v, false) error: %s\n", api, err)
+ errMsg := wski18n.T("Unable to create API: {{.err}}", map[string]interface{}{"err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_NETWORK,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+
+ if (api.Swagger == "") {
+ baseUrl := retApi.BaseUrl
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} created API {{.path}} {{.verb}} for action {{.name}}\n{{.fullpath}}\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ "path": strings.TrimSuffix(api.GatewayBasePath, "/")+api.GatewayRelPath,
+ "verb": api.GatewayMethod,
+ "name": boldString("/"+api.Action.Namespace+"/"+api.Action.Name),
+ "fullpath": strings.TrimSuffix(baseUrl, "/")+api.GatewayRelPath,
+ }))
+ } else {
+ whisk.Debug(whisk.DbgInfo, "Processing swagger based create API response\n")
+ baseUrl := retApi.BaseUrl
+ for path, _ := range retApi.Swagger.Paths {
+ managedUrl := strings.TrimSuffix(baseUrl, "/")+path
+ whisk.Debug(whisk.DbgInfo, "Managed path: %s\n",managedUrl)
+ for op, opv := range retApi.Swagger.Paths[path] {
+ whisk.Debug(whisk.DbgInfo, "Path operation: %s\n", op)
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} created API {{.path}} {{.verb}} for action {{.name}}\n{{.fullpath}}\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ "path": path,
+ "verb": op,
+ "name": boldString(opv.XOpenWhisk.ActionName),
+ "fullpath": managedUrl,
+ }))
+ }
+ }
+ }
+
+
+ return nil
+ },
+}
+
+//var apiUpdateCmd = &cobra.Command{
+// Use: "update API_PATH API_VERB ACTION",
+// Short: wski18n.T("update an existing API"),
+// SilenceUsage: true,
+// SilenceErrors: true,
+// PreRunE: setupClientConfig,
+// RunE: func(cmd *cobra.Command, args []string) error {
+//
+// if whiskErr := checkArgs(args, 3, 3, "Api update",
+// wski18n.T("An API path, an API verb, and an action name are required.")); whiskErr != nil {
+// return whiskErr
+// }
+//
+// api, err := parseApi(cmd, args)
+// if err != nil {
+// whisk.Debug(whisk.DbgError, "parseApi(%s, %s) error: %s\n", cmd, args, err)
+// errMsg := wski18n.T("Unable to parse API command arguments: {{.err}}", map[string]interface{}{"err": err})
+// whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+// whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+// return whiskErr
+// }
+// sendApi := new(whisk.ApiCreateRequest)
+// sendApi.ApiDoc = api
+//
+// retApi, _, err := client.Apis.Insert(sendApi, true)
+// if err != nil {
+// whisk.Debug(whisk.DbgError, "client.Apis.Insert(%#v, %t, false) error: %s\n", api, err)
+// errMsg := wski18n.T("Unable to update API: {{.err}}", map[string]interface{}{"err": err})
+// whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_NETWORK,
+// whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+// return whiskErr
+// }
+//
+// fmt.Fprintf(color.Output,
+// wski18n.T("{{.ok}} updated API {{.path}} {{.verb}} for action {{.name}}\n{{.fullpath}}\n",
+// map[string]interface{}{
+// "ok": color.GreenString("ok:"),
+// "path": api.GatewayRelPath,
+// "verb": api.GatewayMethod,
+// "name": boldString("/"+api.Action.Name),
+// "fullpath": getManagedUrl(retApi, api.GatewayRelPath, api.GatewayMethod),
+// }))
+// return nil
+// },
+//}
+
+var apiGetCmd = &cobra.Command{
+ Use: "get BASE_PATH | API_NAME",
+ Short: wski18n.T("get API details"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+ var isBasePathArg bool = true
+
+ if whiskErr := checkArgs(args, 1, 1, "Api get",
+ wski18n.T("An API base path or API name is required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ apiGetReq := new(whisk.ApiGetRequest)
+ apiGetReqOptions := new(whisk.ApiGetRequestOptions)
+ apiGetReqOptions.ApiBasePath = args[0]
+ retApi, _, err := client.Apis.Get(apiGetReq, apiGetReqOptions)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Apis.Get(%#v, %#v) error: %s\n", apiGetReq, apiGetReqOptions, err)
+ errMsg := wski18n.T("Unable to get API '{{.name}}': {{.err}}", map[string]interface{}{"name": args[0], "err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+ whisk.Debug(whisk.DbgInfo, "client.Apis.Get returned: %#v\n", retApi)
+
+ var displayResult interface{} = nil
+ if (flags.common.detail) {
+ if (retApi.Apis != nil && len(retApi.Apis) > 0 &&
+ retApi.Apis[0].ApiValue != nil) {
+ displayResult = retApi.Apis[0].ApiValue
+ } else {
+ whisk.Debug(whisk.DbgError, "No result object returned\n")
+ }
+ } else {
+ if (retApi.Apis != nil && len(retApi.Apis) > 0 &&
+ retApi.Apis[0].ApiValue != nil &&
+ retApi.Apis[0].ApiValue.Swagger != nil) {
+ displayResult = retApi.Apis[0].ApiValue.Swagger
+ } else {
+ whisk.Debug(whisk.DbgError, "No swagger returned\n")
+ }
+ }
+ if (displayResult == nil) {
+ var errMsg string
+ if (isBasePathArg) {
+ errMsg = wski18n.T("API does not exist for basepath {{.basepath}}",
+ map[string]interface{}{"basepath": args[0]})
+ } else {
+ errMsg = wski18n.T("API does not exist for API name {{.apiname}}",
+ map[string]interface{}{"apiname": args[0]})
+ }
+
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+ printJSON(displayResult)
+
+ return nil
+ },
+}
+
+var apiDeleteCmd = &cobra.Command{
+ Use: "delete BASE_PATH | API_NAME [API_PATH [API_VERB]]",
+ Short: wski18n.T("delete an API"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ if whiskErr := checkArgs(args, 1, 3, "Api delete",
+ wski18n.T("An API base path or API name is required. An optional API relative path and operation may also be provided.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ apiDeleteReq := new(whisk.ApiDeleteRequest)
+ apiDeleteReqOptions := new(whisk.ApiDeleteRequestOptions)
+ // Is the argument a basepath (must start with /) or an API name
+ if _, ok := isValidBasepath(args[0]); !ok {
+ whisk.Debug(whisk.DbgInfo, "Treating '%s' as an API name; as it does not begin with '/'\n", args[0])
+ apiDeleteReqOptions.ApiBasePath = args[0]
+ } else {
+ apiDeleteReqOptions.ApiBasePath = args[0]
+ }
+
+ if (len(args) > 1) {
+ // Is the API path valid?
+ if whiskErr, ok := isValidRelpath(args[1]); !ok {
+ return whiskErr
+ }
+ apiDeleteReqOptions.ApiRelPath = args[1]
+ }
+ if (len(args) > 2) {
+ // Is the API verb valid?
+ if whiskErr, ok := IsValidApiVerb(args[2]); !ok {
+ return whiskErr
+ }
+ apiDeleteReqOptions.ApiVerb = strings.ToUpper(args[2])
+ }
+
+ _, err := client.Apis.Delete(apiDeleteReq, apiDeleteReqOptions)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Apis.Delete(%#v, %#v) error: %s\n", apiDeleteReq, apiDeleteReqOptions, err)
+ errMsg := wski18n.T("Unable to delete API: {{.err}}", map[string]interface{}{"err": err})
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+
+ if (len(args) == 1) {
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} deleted API {{.basepath}}\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ "basepath": apiDeleteReqOptions.ApiBasePath,
+ }))
+ } else if (len(args) == 2 ) {
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} deleted {{.path}} from {{.basepath}}\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ "path": apiDeleteReqOptions.ApiRelPath,
+ "basepath": apiDeleteReqOptions.ApiBasePath,
+ }))
+ } else {
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} deleted {{.path}} {{.verb}} from {{.basepath}}\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ "path": apiDeleteReqOptions.ApiRelPath,
+ "verb": apiDeleteReqOptions.ApiVerb,
+ "basepath": apiDeleteReqOptions.ApiBasePath,
+ }))
+ }
+
+ return nil
+ },
+}
+
+var fmtString = "%-30s %7s %20s %s\n"
+var apiListCmd = &cobra.Command{
+ Use: "list [[BASE_PATH | API_NAME] [API_PATH [API_VERB]]",
+ Short: wski18n.T("list APIs"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+ var retApiList *whisk.ApiListResponse
+ var retApi *whisk.ApiGetResponse
+ var retApiArray *whisk.RetApiArray
+
+ if whiskErr := checkArgs(args, 0, 3, "Api list",
+ wski18n.T("Optional parameters are: API base path (or API name), API relative path and operation.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ // Get API request body
+ apiGetReq := new(whisk.ApiGetRequest)
+ apiGetReq.Namespace = client.Config.Namespace
+
+ // Get API request options
+ apiGetReqOptions := new(whisk.ApiGetRequestOptions)
+
+ // List API request query parameters
+ apiListReqOptions := new(whisk.ApiListRequestOptions)
+ apiListReqOptions.Limit = flags.common.limit
+ apiListReqOptions.Skip = flags.common.skip
+
+ if (len(args) == 0) {
+ retApiList, _, err = client.Apis.List(apiListReqOptions)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Apis.List(%#v) error: %s\n", apiListReqOptions, err)
+ errMsg := wski18n.T("Unable to obtain the API list: {{.err}}", map[string]interface{}{"err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+ whisk.Debug(whisk.DbgInfo, "client.Apis.List returned: %#v (%+v)\n", retApiList, retApiList)
+ // Cast to a common type to allow for code to print out apilist response or apiget response
+ retApiArray = (*whisk.RetApiArray)(retApiList)
+ } else {
+ // The first argument is either a basepath (must start with /) or an API name
+ apiGetReqOptions.ApiBasePath = args[0]
+ if (len(args) > 1) {
+ // Is the API path valid?
+ if whiskErr, ok := isValidRelpath(args[1]); !ok {
+ return whiskErr
+ }
+ apiGetReqOptions.ApiRelPath = args[1]
+ }
+ if (len(args) > 2) {
+ // Is the API verb valid?
+ if whiskErr, ok := IsValidApiVerb(args[2]); !ok {
+ return whiskErr
+ }
+ apiGetReqOptions.ApiVerb = strings.ToUpper(args[2])
+ }
+
+ retApi, _, err = client.Apis.Get(apiGetReq, apiGetReqOptions)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Apis.Get(%#v, %#v) error: %s\n", apiGetReq, apiGetReqOptions, err)
+ errMsg := wski18n.T("Unable to obtain the API list: {{.err}}", map[string]interface{}{"err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+ whisk.Debug(whisk.DbgInfo, "client.Apis.Get returned: %#v\n", retApi)
+ // Cast to a common type to allow for code to print out apilist response or apiget response
+ retApiArray = (*whisk.RetApiArray)(retApi)
+ }
+
+ // Display the APIs - applying any specified filtering
+ if (flags.common.full) {
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} APIs\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ }))
+
+ for i:=0; i<len(retApiArray.Apis); i++ {
+ printFilteredListApi(retApiArray.Apis[i].ApiValue, (*whisk.ApiOptions)(apiGetReqOptions))
+ }
+ } else {
+ // Dynamically create the output format string based on the maximum size of the
+ // fully qualified action name and the API Name.
+ maxActionNameSize := min(40, max(len("Action"), getLargestActionNameSize(retApiArray, (*whisk.ApiOptions)(apiGetReqOptions))))
+ maxApiNameSize := min(30, max(len("API Name"), getLargestApiNameSize(retApiArray, (*whisk.ApiOptions)(apiGetReqOptions))))
+ fmtString = "%-"+strconv.Itoa(maxActionNameSize)+"s %7s %"+strconv.Itoa(maxApiNameSize+1)+"s %s\n"
+
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} APIs\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ }))
+ fmt.Printf(fmtString, "Action", "Verb", "API Name", "URL")
+
+ for i:=0; i<len(retApiArray.Apis); i++ {
+ printFilteredListRow(retApiArray.Apis[i].ApiValue, (*whisk.ApiOptions)(apiGetReqOptions), maxActionNameSize, maxApiNameSize)
+ }
+ }
+
+ return nil
+ },
+}
+
+/*
+ * Takes an API object (containing one more more single basepath/relpath/operation triplets)
+ * and some filtering configuration. For each API endpoint matching the filtering criteria, display
+ * each endpoint's configuration - one line per configuration property (action name, verb, api name, api gw url)
+ */
+func printFilteredListApi(resultApi *whisk.RetApi, api *whisk.ApiOptions) {
+ baseUrl := strings.TrimSuffix(resultApi.BaseUrl, "/")
+ apiName := resultApi.Swagger.Info.Title
+ basePath := resultApi.Swagger.BasePath
+ if (resultApi.Swagger != nil && resultApi.Swagger.Paths != nil) {
+ for path, _ := range resultApi.Swagger.Paths {
+ whisk.Debug(whisk.DbgInfo, "apiGetCmd: comparing api relpath: %s\n", path)
+ if ( len(api.ApiRelPath) == 0 || path == api.ApiRelPath) {
+ whisk.Debug(whisk.DbgInfo, "apiGetCmd: relpath matches\n")
+ for op, opv := range resultApi.Swagger.Paths[path] {
+ whisk.Debug(whisk.DbgInfo, "apiGetCmd: comparing operation: '%s'\n", op)
+ if ( len(api.ApiVerb) == 0 || strings.ToLower(op) == strings.ToLower(api.ApiVerb)) {
+ whisk.Debug(whisk.DbgInfo, "apiGetCmd: operation matches: %#v\n", opv)
+ var actionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.ActionName
+ fmt.Printf("%s: %s\n", wski18n.T("Action"), actionName)
+ fmt.Printf(" %s: %s\n", wski18n.T("API Name"), apiName)
+ fmt.Printf(" %s: %s\n", wski18n.T("Base path"), basePath)
+ fmt.Printf(" %s: %s\n", wski18n.T("Path"), path)
+ fmt.Printf(" %s: %s\n", wski18n.T("Verb"), op)
+ fmt.Printf(" %s: %s\n", wski18n.T("URL"), baseUrl+path)
+ }
+ }
+ }
+ }
+ }
+}
+
+/*
+ * Takes an API object (containing one more more single basepath/relpath/operation triplets)
+ * and some filtering configuration. For each API matching the filtering criteria, display the API
+ * on a single line (action name, verb, api name, api gw url).
+ *
+ * NOTE: Large action name and api name value will be truncated by their associated max size parameters.
+ */
+func printFilteredListRow(resultApi *whisk.RetApi, api *whisk.ApiOptions, maxActionNameSize int, maxApiNameSize int) {
+ baseUrl := strings.TrimSuffix(resultApi.BaseUrl, "/")
+ apiName := resultApi.Swagger.Info.Title
+ if (resultApi.Swagger != nil && resultApi.Swagger.Paths != nil) {
+ for path, _ := range resultApi.Swagger.Paths {
+ whisk.Debug(whisk.DbgInfo, "apiGetCmd: comparing api relpath: %s\n", path)
+ if ( len(api.ApiRelPath) == 0 || path == api.ApiRelPath) {
+ whisk.Debug(whisk.DbgInfo, "apiGetCmd: relpath matches\n")
+ for op, opv := range resultApi.Swagger.Paths[path] {
+ whisk.Debug(whisk.DbgInfo, "apiGetCmd: comparing operation: '%s'\n", op)
+ if ( len(api.ApiVerb) == 0 || strings.ToLower(op) == strings.ToLower(api.ApiVerb)) {
+ whisk.Debug(whisk.DbgInfo, "apiGetCmd: operation matches: %#v\n", opv)
+ var actionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.ActionName
+ fmt.Printf(fmtString,
+ actionName[0 : min(len(actionName), maxActionNameSize)],
+ op,
+ apiName[0 : min(len(apiName), maxApiNameSize)],
+ baseUrl+path)
+ }
+ }
+ }
+ }
+ }
+}
+
+func getLargestActionNameSize(retApiArray *whisk.RetApiArray, api *whisk.ApiOptions) int {
+ var maxNameSize = 0
+ for i:=0; i<len(retApiArray.Apis); i++ {
+ var resultApi = retApiArray.Apis[i].ApiValue
+ if (resultApi.Swagger != nil && resultApi.Swagger.Paths != nil) {
+ for path, _ := range resultApi.Swagger.Paths {
+ whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: comparing api relpath: %s\n", path)
+ if ( len(api.ApiRelPath) == 0 || path == api.ApiRelPath) {
+ whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: relpath matches\n")
+ for op, opv := range resultApi.Swagger.Paths[path] {
+ whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: comparing operation: '%s'\n", op)
+ if ( len(api.ApiVerb) == 0 || strings.ToLower(op) == strings.ToLower(api.ApiVerb)) {
+ whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: operation matches: %#v\n", opv)
+ var fullActionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.ActionName
+ if (len(fullActionName) > maxNameSize) {
+ maxNameSize = len(fullActionName)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return maxNameSize
+}
+
+func getLargestApiNameSize(retApiArray *whisk.RetApiArray, api *whisk.ApiOptions) int {
+ var maxNameSize = 0
+ for i:=0; i<len(retApiArray.Apis); i++ {
+ var resultApi = retApiArray.Apis[i].ApiValue
+ apiName := resultApi.Swagger.Info.Title
+ if (resultApi.Swagger != nil && resultApi.Swagger.Paths != nil) {
+ for path, _ := range resultApi.Swagger.Paths {
+ whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: comparing api relpath: %s\n", path)
+ if ( len(api.ApiRelPath) == 0 || path == api.ApiRelPath) {
+ whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: relpath matches\n")
+ for op, opv := range resultApi.Swagger.Paths[path] {
+ whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: comparing operation: '%s'\n", op)
+ if ( len(api.ApiVerb) == 0 || strings.ToLower(op) == strings.ToLower(api.ApiVerb)) {
+ whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: operation matches: %#v\n", opv)
+ if (len(apiName) > maxNameSize) {
+ maxNameSize = len(apiName)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return maxNameSize
+}
+
+/*
+ * if # args = 4
+ * args[0] = API base path
+ * args[0] = API relative path
+ * args[1] = API verb
+ * args[2] = Optional. Action name (may or may not be qualified with namespace and package name)
+ *
+ * if # args = 3
+ * args[0] = API relative path
+ * args[1] = API verb
+ * args[2] = Optional. Action name (may or may not be qualified with namespace and package name)
+ */
+func parseApi(cmd *cobra.Command, args []string) (*whisk.Api, error) {
+ var err error
+ var basepath string = "/"
+ var apiname string
+ var basepathArgIsApiName = false;
+
+ api := new(whisk.Api)
+
+ if (len(args) > 3) {
+ // Is the argument a basepath (must start with /) or an API name
+ if _, ok := isValidBasepath(args[0]); !ok {
+ whisk.Debug(whisk.DbgInfo, "Treating '%s' as an API name; as it does not begin with '/'\n", args[0])
+ basepathArgIsApiName = true;
+ }
+ basepath = args[0]
+
+ // Shift the args so the remaining code works with or without the explicit base path arg
+ args = args[1:]
+ }
+
+ // Is the API path valid?
+ if (len(args) > 0) {
+ if whiskErr, ok := isValidRelpath(args[0]); !ok {
+ return nil, whiskErr
+ }
+ api.GatewayRelPath = args[0] // Maintain case as URLs may be case-sensitive
+ }
+
+ // Is the API verb valid?
+ if (len(args) > 1) {
+ if whiskErr, ok := IsValidApiVerb(args[1]); !ok {
+ return nil, whiskErr
+ }
+ api.GatewayMethod = strings.ToUpper(args[1])
+ }
+
+ // Is the specified action name valid?
+ var qName QualifiedName
+ if (len(args) == 3) {
+ qName = QualifiedName{}
+ qName, err = parseQualifiedName(args[2])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[2], err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid action name: {{.err}}",
+ map[string]interface{}{"name": args[2], "err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return nil, whiskErr
+ }
+ if (qName.entityName == "") {
+ whisk.Debug(whisk.DbgError, "Action name '%s' is invalid\n", args[2])
+ errMsg := wski18n.T("'{{.name}}' is not a valid action name.", map[string]interface{}{"name": args[2]})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return nil, whiskErr
+ }
+ }
+
+ if ( len(flags.api.apiname) > 0 ) {
+ if (basepathArgIsApiName) {
+ // Specifying API name as argument AND as a --apiname option value is invalid
+ whisk.Debug(whisk.DbgError, "API is specified as an argument '%s' and as a flag '%s'\n", basepath, flags.api.apiname)
+ errMsg := wski18n.T("An API name can only be specified once.")
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return nil, whiskErr
+ }
+ apiname = flags.api.apiname
+ }
+
+ api.Namespace = client.Config.Namespace
+ api.Action = new(whisk.ApiAction)
+ api.Action.BackendUrl = "https://" + client.Config.Host + "/api/v1/namespaces/" + qName.namespace + "/actions/" + qName.entityName
+ api.Action.BackendMethod = "POST"
+ api.Action.Name = qName.entityName
+ api.Action.Namespace = qName.namespace
+ api.Action.Auth = client.Config.AuthToken
+ api.ApiName = apiname
+ api.GatewayBasePath = basepath
+ if (!basepathArgIsApiName) { api.Id = "API:"+api.Namespace+":"+api.GatewayBasePath }
+
+ whisk.Debug(whisk.DbgInfo, "Parsed api struct: %#v\n", api)
+ return api, nil
+}
+
+func parseSwaggerApi() (*whisk.Api, error) {
+ // Test is for completeness, but this situation should only arise due to an internal error
+ if ( len(flags.api.configfile) == 0 ) {
+ whisk.Debug(whisk.DbgError, "No swagger file is specified\n")
+ errMsg := wski18n.T("A configuration file was not specified.")
+ whiskErr := whisk.MakeWskError(errors.New(errMsg),whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return nil, whiskErr
+ }
+
+ swagger, err:= readFile(flags.api.configfile)
+ if ( err != nil ) {
+ whisk.Debug(whisk.DbgError, "readFile(%s) error: %s\n", flags.api.configfile, err)
+ errMsg := wski18n.T("Error reading swagger file '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": flags.api.configfile, "err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return nil, whiskErr
+ }
+
+ // Parse the JSON into a swagger object
+ swaggerObj := new(whisk.ApiSwagger)
+ err = json.Unmarshal([]byte(swagger), swaggerObj)
+ if ( err != nil ) {
+ whisk.Debug(whisk.DbgError, "JSON parse of `%s' error: %s\n", flags.api.configfile, err)
+ errMsg := wski18n.T("Error parsing swagger file '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": flags.api.configfile, "err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return nil, whiskErr
+ }
+ if (swaggerObj.BasePath == "" || swaggerObj.SwaggerName == "" || swaggerObj.Info == nil || swaggerObj.Paths == nil) {
+ whisk.Debug(whisk.DbgError, "Swagger file is invalid.\n", flags.api.configfile, err)
+ errMsg := wski18n.T("Swagger file is invalid (missing basePath, info, paths, or swagger fields)")
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return nil, whiskErr
+ }
+ if _, ok := isValidBasepath(swaggerObj.BasePath); !ok {
+ whisk.Debug(whisk.DbgError, "Swagger file basePath is invalid.\n", flags.api.configfile, err)
+ errMsg := wski18n.T("Swagger file basePath must start with a leading slash (/)")
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return nil, whiskErr
+ }
+
+ api := new(whisk.Api)
+ api.Namespace = client.Config.Namespace
+ api.Swagger = swagger
+
+ return api, nil
+}
+
+func IsValidApiVerb(verb string) (error, bool) {
+ // Is the API verb valid?
+ if _, ok := whisk.ApiVerbs[strings.ToUpper(verb)]; !ok {
+ whisk.Debug(whisk.DbgError, "Invalid API verb: %s\n", verb)
+ errMsg := wski18n.T("'{{.verb}}' is not a valid API verb. Valid values are: {{.verbs}}",
+ map[string]interface{}{
+ "verb": verb,
+ "verbs": reflect.ValueOf(whisk.ApiVerbs).MapKeys()})
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return whiskErr, false
+ }
+ return nil, true
+}
+
+func hasPathPrefix(path string) (error, bool) {
+ if (! strings.HasPrefix(path, "/")) {
+ whisk.Debug(whisk.DbgError, "path does not begin with '/': %s\n", path)
+ errMsg := wski18n.T("'{{.path}}' must begin with '/'.",
+ map[string]interface{}{
+ "path": path,
+ })
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return whiskErr, false
+ }
+ return nil, true
+}
+
+func isValidBasepath(basepath string) (error, bool) {
+ if whiskerr, ok := hasPathPrefix(basepath); !ok {
+ return whiskerr, false
+ }
+ return nil, true
+}
+
+func isValidRelpath(relpath string) (error, bool) {
+ if whiskerr, ok := hasPathPrefix(relpath); !ok {
+ return whiskerr, false
+ }
+ return nil, true
+}
+
+
+/*
+ * Pull the managedUrl (external API URL) from the API configuration
+ */
+func getManagedUrl(api *whisk.RetApi, relpath string, operation string) (url string) {
+ baseUrl := strings.TrimSuffix(api.BaseUrl, "/")
+ whisk.Debug(whisk.DbgInfo, "getManagedUrl: baseUrl = %s, relpath = %s, operation = %s\n", baseUrl, relpath, operation)
+ for path, _ := range api.Swagger.Paths {
+ whisk.Debug(whisk.DbgInfo, "getManagedUrl: comparing api relpath: %s\n", path)
+ if (path == relpath) {
+ whisk.Debug(whisk.DbgInfo, "getManagedUrl: relpath matches '%s'\n", relpath)
+ for op, _ := range api.Swagger.Paths[path] {
+ whisk.Debug(whisk.DbgInfo, "getManagedUrl: comparing operation: '%s'\n", op)
+ if (strings.ToLower(op) == strings.ToLower(operation)) {
+ whisk.Debug(whisk.DbgInfo, "getManagedUrl: operation matches: %s\n", operation)
+ url = baseUrl+path
+ }
+ }
+ }
+ }
+ return url
+}
+
+/////////////
+// V2 Cmds //
+/////////////
+var apiCreateCmdV2 = &cobra.Command{
+ Use: "create ([BASE_PATH] API_PATH API_VERB ACTION] | --config-file CFG_FILE) ",
+ Short: wski18n.T("create a new API"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var api *whisk.Api
+ var err error
+ var qname *QualifiedName
+
+ if (len(args) == 0 && flags.api.configfile == "") {
+ whisk.Debug(whisk.DbgError, "No swagger file and no arguments\n")
+ errMsg := wski18n.T("Invalid argument(s). Specify a swagger file or specify an API base path with an API path, an API verb, and an action name.")
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return whiskErr
+ } else if (len(args) == 0 && flags.api.configfile != "") {
+ api, err = parseSwaggerApiV2()
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseSwaggerApi() error: %s\n", err)
+ errMsg := wski18n.T("Unable to parse swagger file: {{.err}}", map[string]interface{}{"err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return whiskErr
+ }
+ } else {
+ if whiskErr := checkArgs(args, 3, 4, "Api create",
+ wski18n.T("Specify a swagger file or specify an API base path with an API path, an API verb, and an action name.")); whiskErr != nil {
+ return whiskErr
+ }
+ api, qname, err = parseApiV2(cmd, args)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseApiV2(%s, %s) error: %s\n", cmd, args, err)
+ errMsg := wski18n.T("Unable to parse api command arguments: {{.err}}",
+ map[string]interface{}{"err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return whiskErr
+ }
+
+ // Confirm that the specified action is a web-action
+ err = isWebAction(client, *qname)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "isWebAction(%v) is false: %s\n", qname, err)
+ whiskErr := whisk.MakeWskError(err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return whiskErr
+ }
+ }
+
+ apiCreateReq := new(whisk.ApiCreateRequest)
+ apiCreateReq.ApiDoc = api
+
+ apiCreateReqOptions := new(whisk.ApiCreateRequestOptions)
+ if apiCreateReqOptions.SpaceGuid, err = getUserContextId(); err != nil {
+ return err
+ }
+ if apiCreateReqOptions.AccessToken, err = getAccessToken(); err != nil {
+ return err
+ }
+ apiCreateReqOptions.ResponseType = flags.api.resptype
+ whisk.Debug(whisk.DbgInfo, "AccessToken: %s\nSpaceGuid: %s\nResponsType: %s",
+ apiCreateReqOptions.AccessToken, apiCreateReqOptions.SpaceGuid, apiCreateReqOptions.ResponseType)
+
+ retApi, _, err := client.Apis.InsertV2(apiCreateReq, apiCreateReqOptions, whisk.DoNotOverwrite)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Apis.InsertV2(%#v, false) error: %s\n", api, err)
+ errMsg := wski18n.T("Unable to create API: {{.err}}", map[string]interface{}{"err": err})
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+
+ if (api.Swagger == "") {
+ baseUrl := retApi.BaseUrl
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} created API {{.path}} {{.verb}} for action {{.name}}\n{{.fullpath}}\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ "path": strings.TrimSuffix(api.GatewayBasePath, "/")+api.GatewayRelPath,
+ "verb": api.GatewayMethod,
+ "name": boldString("/"+api.Action.Namespace+"/"+api.Action.Name),
+ "fullpath": strings.TrimSuffix(baseUrl, "/")+api.GatewayRelPath,
+ }))
+ } else {
+ whisk.Debug(whisk.DbgInfo, "Processing swagger based create API response\n")
+ baseUrl := retApi.BaseUrl
+ for path, _ := range retApi.Swagger.Paths {
+ managedUrl := strings.TrimSuffix(baseUrl, "/")+path
+ whisk.Debug(whisk.DbgInfo, "Managed path: %s\n",managedUrl)
+ for op, opv := range retApi.Swagger.Paths[path] {
+ whisk.Debug(whisk.DbgInfo, "Path operation: %s\n", op)
+ var fqActionName string
+ if (len(opv.XOpenWhisk.Package) > 0) {
+ fqActionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.Package+"/"+opv.XOpenWhisk.ActionName
+ } else {
+ fqActionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.ActionName
+ }
+ whisk.Debug(whisk.DbgInfo, "baseUrl %s Path %s Path obj %+v\n", baseUrl, path, opv)
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} created API {{.path}} {{.verb}} for action {{.name}}\n{{.fullpath}}\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ "path": strings.TrimSuffix(retApi.Swagger.BasePath, "/") + path,
+ "verb": op,
+ "name": boldString(fqActionName),
+ "fullpath": managedUrl,
+ }))
+ }
+ }
+ }
+
+
+ return nil
+ },
+}
+
+var apiGetCmdV2 = &cobra.Command{
+ Use: "get BASE_PATH | API_NAME",
+ Short: wski18n.T("get API details"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+ var isBasePathArg bool = true
+
+ if whiskErr := checkArgs(args, 1, 1, "Api get",
+ wski18n.T("An API base path or API name is required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ if ( cmd.LocalFlags().Changed("format") &&
+ strings.ToLower(flags.common.format) != formatOptionYaml &&
+ strings.ToLower(flags.common.format) != formatOptionJson) {
+ errMsg := wski18n.T("Invalid format type: {{.type}}", map[string]interface{}{"type": flags.common.format})
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+
+ apiGetReq := new(whisk.ApiGetRequest)
+ apiGetReqOptions := new(whisk.ApiGetRequestOptions)
+ apiGetReqOptions.ApiBasePath = args[0]
+ if apiGetReqOptions.SpaceGuid, err = getUserContextId(); err != nil {
+ return err
+ }
+ if apiGetReqOptions.AccessToken, err = getAccessToken(); err != nil {
+ return err
+ }
+
+ retApi, _, err := client.Apis.GetV2(apiGetReq, apiGetReqOptions)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Apis.GetV2(%#v, %#v) error: %s\n", apiGetReq, apiGetReqOptions, err)
+ errMsg := wski18n.T("Unable to get API '{{.name}}': {{.err}}", map[string]interface{}{"name": args[0], "err": err})
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+ whisk.Debug(whisk.DbgInfo, "client.Apis.GetV2 returned: %#v\n", retApi)
+
+ var displayResult interface{} = nil
+ if (flags.common.detail) {
+ if (retApi.Apis != nil && len(retApi.Apis) > 0 &&
+ retApi.Apis[0].ApiValue != nil) {
+ displayResult = retApi.Apis[0].ApiValue
+ } else {
+ whisk.Debug(whisk.DbgError, "No result object returned\n")
+ }
+ } else {
+ if (retApi.Apis != nil && len(retApi.Apis) > 0 &&
+ retApi.Apis[0].ApiValue != nil &&
+ retApi.Apis[0].ApiValue.Swagger != nil) {
+ displayResult = retApi.Apis[0].ApiValue.Swagger
+ } else {
+ whisk.Debug(whisk.DbgError, "No swagger returned\n")
+ }
+ }
+ if (displayResult == nil) {
+ var errMsg string
+ if (isBasePathArg) {
+ errMsg = wski18n.T("API does not exist for basepath {{.basepath}}",
+ map[string]interface{}{"basepath": args[0]})
+ } else {
+ errMsg = wski18n.T("API does not exist for API name {{.apiname}}",
+ map[string]interface{}{"apiname": args[0]})
+ }
+
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+
+ if ( cmd.LocalFlags().Changed("format") && strings.ToLower(flags.common.format) == formatOptionYaml ) {
+ var jsonOutputBuffer bytes.Buffer
+ var jsonOutputWriter = bufio.NewWriter(&jsonOutputBuffer)
+ printJSON(displayResult, jsonOutputWriter)
+ jsonOutputWriter.Flush()
+ yamlbytes, err := yaml.JSONToYAML(jsonOutputBuffer.Bytes())
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "yaml.JSONToYAML() error: %s\n", err)
+ errMsg := wski18n.T("Unable to convert API into YAML: {{.err}}", map[string]interface{}{"err": err})
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+ fmt.Println(string(yamlbytes))
+ } else {
+ printJSON(displayResult)
+ }
+
+ return nil
+ },
+}
+
+var apiDeleteCmdV2 = &cobra.Command{
+ Use: "delete BASE_PATH | API_NAME [API_PATH [API_VERB]]",
+ Short: wski18n.T("delete an API"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+
+ if whiskErr := checkArgs(args, 1, 3, "Api delete",
+ wski18n.T("An API base path or API name is required. An optional API relative path and operation may also be provided.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ apiDeleteReq := new(whisk.ApiDeleteRequest)
+ apiDeleteReqOptions := new(whisk.ApiDeleteRequestOptions)
+ if apiDeleteReqOptions.SpaceGuid, err = getUserContextId(); err != nil {
+ return err
+ }
+ if apiDeleteReqOptions.AccessToken, err = getAccessToken(); err != nil {
+ return err
+ }
+
+ // Is the argument a basepath (must start with /) or an API name
+ if _, ok := isValidBasepath(args[0]); !ok {
+ whisk.Debug(whisk.DbgInfo, "Treating '%s' as an API name; as it does not begin with '/'\n", args[0])
+ apiDeleteReqOptions.ApiBasePath = args[0]
+ } else {
+ apiDeleteReqOptions.ApiBasePath = args[0]
+ }
+
+ if (len(args) > 1) {
+ // Is the API path valid?
+ if whiskErr, ok := isValidRelpath(args[1]); !ok {
+ return whiskErr
+ }
+ apiDeleteReqOptions.ApiRelPath = args[1]
+ }
+ if (len(args) > 2) {
+ // Is the API verb valid?
+ if whiskErr, ok := IsValidApiVerb(args[2]); !ok {
+ return whiskErr
+ }
+ apiDeleteReqOptions.ApiVerb = strings.ToUpper(args[2])
+ }
+
+ _, err = client.Apis.DeleteV2(apiDeleteReq, apiDeleteReqOptions)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Apis.DeleteV2(%#v, %#v) error: %s\n", apiDeleteReq, apiDeleteReqOptions, err)
+ errMsg := wski18n.T("Unable to delete API: {{.err}}", map[string]interface{}{"err": err})
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+
+ if (len(args) == 1) {
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} deleted API {{.basepath}}\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ "basepath": apiDeleteReqOptions.ApiBasePath,
+ }))
+ } else if (len(args) == 2 ) {
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} deleted {{.path}} from {{.basepath}}\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ "path": apiDeleteReqOptions.ApiRelPath,
+ "basepath": apiDeleteReqOptions.ApiBasePath,
+ }))
+ } else {
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} deleted {{.path}} {{.verb}} from {{.basepath}}\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ "path": apiDeleteReqOptions.ApiRelPath,
+ "verb": apiDeleteReqOptions.ApiVerb,
+ "basepath": apiDeleteReqOptions.ApiBasePath,
+ }))
+ }
+
+ return nil
+ },
+}
+
+var apiListCmdV2 = &cobra.Command{
+ Use: "list [[BASE_PATH | API_NAME] [API_PATH [API_VERB]]",
+ Short: wski18n.T("list APIs"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+ var retApiList *whisk.ApiListResponseV2
+ var retApi *whisk.ApiGetResponseV2
+ var retApiArray *whisk.RetApiArrayV2
+ var apiPath string
+ var apiVerb string
+
+ if whiskErr := checkArgs(args, 0, 3, "Api list",
+ wski18n.T("Optional parameters are: API base path (or API name), API relative path and operation.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ if (len(args) == 0) {
+ // List API request query parameters
+ apiListReqOptions := new(whisk.ApiListRequestOptions)
+ apiListReqOptions.Limit = flags.common.limit
+ apiListReqOptions.Skip = flags.common.skip
+ if apiListReqOptions.SpaceGuid, err = getUserContextId(); err != nil {
+ return err
+ }
+ if apiListReqOptions.AccessToken, err = getAccessToken(); err != nil {
+ return err
+ }
+
+ retApiList, _, err = client.Apis.ListV2(apiListReqOptions)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Apis.ListV2(%#v) error: %s\n", apiListReqOptions, err)
+ errMsg := wski18n.T("Unable to obtain the API list: {{.err}}", map[string]interface{}{"err": err})
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+ whisk.Debug(whisk.DbgInfo, "client.Apis.ListV2 returned: %#v (%+v)\n", retApiList, retApiList)
+ // Cast to a common type to allow for code to print out apilist response or apiget response
+ retApiArray = (*whisk.RetApiArrayV2)(retApiList)
+ } else {
+ // Get API request body
+ apiGetReq := new(whisk.ApiGetRequest)
+ apiGetReq.Namespace = client.Config.Namespace
+ // Get API request options
+ apiGetReqOptions := new(whisk.ApiGetRequestOptions)
+ if apiGetReqOptions.SpaceGuid, err = getUserContextId(); err != nil {
+ return err
+ }
+ if apiGetReqOptions.AccessToken, err = getAccessToken(); err != nil {
+ return err
+ }
+
+ // The first argument is either a basepath (must start with /) or an API name
+ apiGetReqOptions.ApiBasePath = args[0]
+ if (len(args) > 1) {
+ // Is the API path valid?
+ if whiskErr, ok := isValidRelpath(args[1]); !ok {
+ return whiskErr
+ }
+ apiPath = args[1]
+ apiGetReqOptions.ApiRelPath = apiPath
+ }
+ if (len(args) > 2) {
+ // Is the API verb valid?
+ if whiskErr, ok := IsValidApiVerb(args[2]); !ok {
+ return whiskErr
+ }
+ apiVerb = strings.ToUpper(args[2])
+ apiGetReqOptions.ApiVerb = apiVerb
+ }
+
+ retApi, _, err = client.Apis.GetV2(apiGetReq, apiGetReqOptions)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Apis.GetV2(%#v, %#v) error: %s\n", apiGetReq, apiGetReqOptions, err)
+ errMsg := wski18n.T("Unable to obtain the API list: {{.err}}", map[string]interface{}{"err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+ whisk.Debug(whisk.DbgInfo, "client.Apis.GetV2 returned: %#v\n", retApi)
+ // Cast to a common type to allow for code to print out apilist response or apiget response
+ retApiArray = (*whisk.RetApiArrayV2)(retApi)
+ }
+
+ // Display the APIs - applying any specified filtering
+ if (flags.common.full) {
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} APIs\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ }))
+
+ for i:=0; i<len(retApiArray.Apis); i++ {
+ printFilteredListApiV2(retApiArray.Apis[i].ApiValue, apiPath, apiVerb)
+ }
+ } else {
+ if (len(retApiArray.Apis) > 0) {
+ // Dynamically create the output format string based on the maximum size of the
+ // fully qualified action name and the API Name.
+ maxActionNameSize := min(40, max(len("Action"), getLargestActionNameSizeV2(retApiArray, apiPath, apiVerb)))
+ maxApiNameSize := min(30, max(len("API Name"), getLargestApiNameSizeV2(retApiArray, apiPath, apiVerb)))
+ fmtString = "%-"+strconv.Itoa(maxActionNameSize)+"s %7s %"+strconv.Itoa(maxApiNameSize+1)+"s %s\n"
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} APIs\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ }))
+ fmt.Printf(fmtString, "Action", "Verb", "API Name", "URL")
+ for i:=0; i<len(retApiArray.Apis); i++ {
+ printFilteredListRowV2(retApiArray.Apis[i].ApiValue, apiPath, apiVerb, maxActionNameSize, maxApiNameSize)
+ }
+ } else {
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} APIs\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ }))
+ fmt.Printf(fmtString, "Action", "Verb", "API Name", "URL")
+ }
+ }
+
+ return nil
+ },
+}
+
+/*
+ * Takes an API object (containing one more more single basepath/relpath/operation triplets)
+ * and some filtering configuration. For each API endpoint matching the filtering criteria, display
+ * each endpoint's configuration - one line per configuration property (action name, verb, api name, api gw url)
+ */
+func printFilteredListApiV2(resultApi *whisk.RetApiV2, apiPath string, apiVerb string) {
+ baseUrl := strings.TrimSuffix(resultApi.BaseUrl, "/")
+ apiName := resultApi.Swagger.Info.Title
+ basePath := resultApi.Swagger.BasePath
+ if (resultApi.Swagger != nil && resultApi.Swagger.Paths != nil) {
+ for path, _ := range resultApi.Swagger.Paths {
+ whisk.Debug(whisk.DbgInfo, "printFilteredListApiV2: comparing api relpath: %s\n", path)
+ if ( len(apiPath) == 0 || path == apiPath) {
+ whisk.Debug(whisk.DbgInfo, "printFilteredListApiV2: relpath matches\n")
+ for op, opv := range resultApi.Swagger.Paths[path] {
+ whisk.Debug(whisk.DbgInfo, "printFilteredListApiV2: comparing operation: '%s'\n", op)
+ if ( len(apiVerb) == 0 || strings.ToLower(op) == strings.ToLower(apiVerb)) {
+ whisk.Debug(whisk.DbgInfo, "printFilteredListApiV2: operation matches: %#v\n", opv)
+ var actionName string
+ if (len(opv.XOpenWhisk.Package) > 0) {
+ actionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.Package+"/"+opv.XOpenWhisk.ActionName
+ } else {
+ actionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.ActionName
+ }
+ fmt.Printf("%s: %s\n", wski18n.T("Action"), actionName)
+ fmt.Printf(" %s: %s\n", wski18n.T("API Name"), apiName)
+ fmt.Printf(" %s: %s\n", wski18n.T("Base path"), basePath)
+ fmt.Printf(" %s: %s\n", wski18n.T("Path"), path)
+ fmt.Printf(" %s: %s\n", wski18n.T("Verb"), op)
+ fmt.Printf(" %s: %s\n", wski18n.T("URL"), baseUrl+path)
+ }
+ }
+ }
+ }
+ }
+}
+
+/*
+ * Takes an API object (containing one more more single basepath/relpath/operation triplets)
+ * and some filtering configuration. For each API matching the filtering criteria, display the API
+ * on a single line (action name, verb, api name, api gw url).
+ *
+ * NOTE: Large action name and api name value will be truncated by their associated max size parameters.
+ */
+func printFilteredListRowV2(resultApi *whisk.RetApiV2, apiPath string, apiVerb string, maxActionNameSize int, maxApiNameSize int) {
+ baseUrl := strings.TrimSuffix(resultApi.BaseUrl, "/")
+ apiName := resultApi.Swagger.Info.Title
+ if (resultApi.Swagger != nil && resultApi.Swagger.Paths != nil) {
+ for path, _ := range resultApi.Swagger.Paths {
+ whisk.Debug(whisk.DbgInfo, "printFilteredListRowV2: comparing api relpath: %s\n", path)
+ if ( len(apiPath) == 0 || path == apiPath) {
+ whisk.Debug(whisk.DbgInfo, "printFilteredListRowV2: relpath matches\n")
+ for op, opv := range resultApi.Swagger.Paths[path] {
+ whisk.Debug(whisk.DbgInfo, "printFilteredListRowV2: comparing operation: '%s'\n", op)
+ if ( len(apiVerb) == 0 || strings.ToLower(op) == strings.ToLower(apiVerb)) {
+ whisk.Debug(whisk.DbgInfo, "printFilteredListRowV2: operation matches: %#v\n", opv)
+ var actionName string
+ if (len(opv.XOpenWhisk.Package) > 0) {
+ actionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.Package+"/"+opv.XOpenWhisk.ActionName
+ } else {
+ actionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.ActionName
+ }
+ fmt.Printf(fmtString,
+ actionName[0 : min(len(actionName), maxActionNameSize)],
+ op,
+ apiName[0 : min(len(apiName), maxApiNameSize)],
+ baseUrl+path)
+ }
+ }
+ }
+ }
+ }
+}
+
+func getLargestActionNameSizeV2(retApiArray *whisk.RetApiArrayV2, apiPath string, apiVerb string) int {
+ var maxNameSize = 0
+ for i:=0; i<len(retApiArray.Apis); i++ {
+ var resultApi = retApiArray.Apis[i].ApiValue
+ if (resultApi.Swagger != nil && resultApi.Swagger.Paths != nil) {
+ for path, _ := range resultApi.Swagger.Paths {
+ whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: comparing api relpath: %s\n", path)
+ if ( len(apiPath) == 0 || path == apiPath) {
+ whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: relpath matches\n")
+ for op, opv := range resultApi.Swagger.Paths[path] {
+ whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: comparing operation: '%s'\n", op)
+ if ( len(apiVerb) == 0 || strings.ToLower(op) == strings.ToLower(apiVerb)) {
+ whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: operation matches: %#v\n", opv)
+ var fullActionName string
+ if (len(opv.XOpenWhisk.Package) > 0) {
+ fullActionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.Package+"/"+opv.XOpenWhisk.ActionName
+ } else {
+ fullActionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.ActionName
+ }
+ if (len(fullActionName) > maxNameSize) {
+ maxNameSize = len(fullActionName)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return maxNameSize
+}
+
+func getLargestApiNameSizeV2(retApiArray *whisk.RetApiArrayV2, apiPath string, apiVerb string) int {
+ var maxNameSize = 0
+ for i:=0; i<len(retApiArray.Apis); i++ {
+ var resultApi = retApiArray.Apis[i].ApiValue
+ apiName := resultApi.Swagger.Info.Title
+ if (resultApi.Swagger != nil && resultApi.Swagger.Paths != nil) {
+ for path, _ := range resultApi.Swagger.Paths {
+ whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: comparing api relpath: %s\n", path)
+ if ( len(apiPath) == 0 || path == apiPath) {
+ whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: relpath matches\n")
+ for op, opv := range resultApi.Swagger.Paths[path] {
+ whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: comparing operation: '%s'\n", op)
+ if ( len(apiVerb) == 0 || strings.ToLower(op) == strings.ToLower(apiVerb)) {
+ whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: operation matches: %#v\n", opv)
+ if (len(apiName) > maxNameSize) {
+ maxNameSize = len(apiName)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return maxNameSize
+}
+
+/*
+ * if # args = 4
+ * args[0] = API base path
+ * args[0] = API relative path
+ * args[1] = API verb
+ * args[2] = Optional. Action name (may or may not be qualified with namespace and package name)
+ *
+ * if # args = 3
+ * args[0] = API relative path
+ * args[1] = API verb
+ * args[2] = Optional. Action name (may or may not be qualified with namespace and package name)
+ */
+func parseApiV2(cmd *cobra.Command, args []string) (*whisk.Api, *QualifiedName, error) {
+ var err error
+ var basepath string = "/"
+ var apiname string
+ var basepathArgIsApiName = false;
+
+ api := new(whisk.Api)
+
+ if (len(args) > 3) {
+ // Is the argument a basepath (must start with /) or an API name
+ if _, ok := isValidBasepath(args[0]); !ok {
+ whisk.Debug(whisk.DbgInfo, "Treating '%s' as an API name; as it does not begin with '/'\n", args[0])
+ basepathArgIsApiName = true;
+ }
+ basepath = args[0]
+
+ // Shift the args so the remaining code works with or without the explicit base path arg
+ args = args[1:]
+ }
+
+ // Is the API path valid?
+ if (len(args) > 0) {
+ if whiskErr, ok := isValidRelpath(args[0]); !ok {
+ return nil, nil, whiskErr
+ }
+ api.GatewayRelPath = args[0] // Maintain case as URLs may be case-sensitive
+ }
+
+ // Is the API verb valid?
+ if (len(args) > 1) {
+ if whiskErr, ok := IsValidApiVerb(args[1]); !ok {
+ return nil, nil, whiskErr
+ }
+ api.GatewayMethod = strings.ToUpper(args[1])
+ }
+
+ // Is the specified action name valid?
+ var qName QualifiedName
+ if (len(args) == 3) {
+ qName = QualifiedName{}
+ qName, err = parseQualifiedName(args[2])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[2], err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid action name: {{.err}}",
+ map[string]interface{}{"name": args[2], "err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return nil, nil, whiskErr
+ }
+ if (qName.entityName == "") {
+ whisk.Debug(whisk.DbgError, "Action name '%s' is invalid\n", args[2])
+ errMsg := wski18n.T("'{{.name}}' is not a valid action name.", map[string]interface{}{"name": args[2]})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return nil, nil, whiskErr
+ }
+ }
+
+ if ( len(flags.api.apiname) > 0 ) {
+ if (basepathArgIsApiName) {
+ // Specifying API name as argument AND as a --apiname option value is invalid
+ whisk.Debug(whisk.DbgError, "API is specified as an argument '%s' and as a flag '%s'\n", basepath, flags.api.apiname)
+ errMsg := wski18n.T("An API name can only be specified once.")
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return nil, nil, whiskErr
+ }
+ apiname = flags.api.apiname
+ }
+
+ api.Namespace = client.Config.Namespace
+ api.Action = new(whisk.ApiAction)
+ var urlActionPackage string
+ if (len(qName.packageName) > 0) {
+ urlActionPackage = qName.packageName
+ } else {
+ urlActionPackage = "default"
+ }
+ api.Action.BackendUrl = "https://" + client.Config.Host + "/api/v1/web/" + qName.namespace + "/" + urlActionPackage + "/" + qName.entity + ".http"
+ api.Action.BackendMethod = api.GatewayMethod
+ api.Action.Name = qName.entityName
+ api.Action.Namespace = qName.namespace
+ api.Action.Auth = client.Config.AuthToken
+ api.ApiName = apiname
+ api.GatewayBasePath = basepath
+ if (!basepathArgIsApiName) { api.Id = "API:"+api.Namespace+":"+api.GatewayBasePath }
+
+ whisk.Debug(whisk.DbgInfo, "Parsed api struct: %#v\n", api)
+ return api, &qName, nil
+}
+
+func parseSwaggerApiV2() (*whisk.Api, error) {
+ // Test is for completeness, but this situation should only arise due to an internal error
+ if ( len(flags.api.configfile) == 0 ) {
+ whisk.Debug(whisk.DbgError, "No swagger file is specified\n")
+ errMsg := wski18n.T("A configuration file was not specified.")
+ whiskErr := whisk.MakeWskError(errors.New(errMsg),whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return nil, whiskErr
+ }
+
+ swagger, err:= readFile(flags.api.configfile)
+ if ( err != nil ) {
+ whisk.Debug(whisk.DbgError, "readFile(%s) error: %s\n", flags.api.configfile, err)
+ errMsg := wski18n.T("Error reading swagger file '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": flags.api.configfile, "err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return nil, whiskErr
+ }
+
+ // Check if this swagger is in JSON or YAML format
+ isYaml := strings.HasSuffix(flags.api.configfile, yamlFileExtension) || strings.HasSuffix(flags.api.configfile, ymlFileExtension)
+ if isYaml {
+ whisk.Debug(whisk.DbgInfo, "Converting YAML formated API configuration into JSON\n")
+ jsonbytes, err := yaml.YAMLToJSON([]byte(swagger))
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "yaml.YAMLToJSON() error: %s\n", err)
+ errMsg := wski18n.T("Unable to parse YAML configuration file: {{.err}}", map[string]interface{}{"err": err})
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return nil, whiskErr
+ }
+ swagger = string(jsonbytes)
+ }
+
+ // Parse the JSON into a swagger object
+ swaggerObj := new(whisk.ApiSwaggerV2)
+ err = json.Unmarshal([]byte(swagger), swaggerObj)
+ if ( err != nil ) {
+ whisk.Debug(whisk.DbgError, "JSON parse of `%s' error: %s\n", flags.api.configfile, err)
+ errMsg := wski18n.T("Error parsing swagger file '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": flags.api.configfile, "err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return nil, whiskErr
+ }
+ if (swaggerObj.BasePath == "" || swaggerObj.SwaggerName == "" || swaggerObj.Info == nil || swaggerObj.Paths == nil) {
+ whisk.Debug(whisk.DbgError, "Swagger file is invalid.\n", flags.api.configfile, err)
+ errMsg := wski18n.T("Swagger file is invalid (missing basePath, info, paths, or swagger fields)")
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return nil, whiskErr
+ }
+ if _, ok := isValidBasepath(swaggerObj.BasePath); !ok {
+ whisk.Debug(whisk.DbgError, "Swagger file basePath is invalid.\n", flags.api.configfile, err)
+ errMsg := wski18n.T("Swagger file basePath must start with a leading slash (/)")
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return nil, whiskErr
+ }
+
+ api := new(whisk.Api)
+ api.Namespace = client.Config.Namespace
+ api.Swagger = swagger
+
+ return api, nil
+}
+
+func getAccessToken() (string, error) {
+ var token string = "DUMMY TOKEN"
+ var err error
+
+ props, err := readProps(Properties.PropsFile)
+ if err == nil {
+ if len(props["APIGW_ACCESS_TOKEN"]) > 0 {
+ token = props["APIGW_ACCESS_TOKEN"]
+ }
+ } else {
+ whisk.Debug(whisk.DbgError, "readProps(%s) failed: %s\n", Properties.PropsFile, err)
+ errStr := wski18n.T("Unable to obtain the API Gateway access token from the properties file: {{.err}}", map[string]interface{}{"err": err})
+ err = whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ }
+
+ return token, err
+}
+
+func getUserContextId() (string, error) {
+ var guid string
+ var err error
+
+ props, err := readProps(Properties.PropsFile)
+ if err == nil {
+ if len(props["AUTH"]) > 0 {
+ guid = strings.Split(props["AUTH"], ":")[0]
+ } else {
+ whisk.Debug(whisk.DbgError, "AUTH property not set in properties file: %s\n", Properties.PropsFile)
+ errStr := wski18n.T("Authorization key is not configured (--auth is required)")
+ err = whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ }
+ } else {
+ whisk.Debug(whisk.DbgError, "readProps(%s) failed: %s\n", Properties.PropsFile, err)
+ errStr := wski18n.T("Unable to obtain the auth key from the properties file: {{.err}}", map[string]interface{}{"err": err})
+ err = whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ }
+
+ return guid, err
+}
+
+///////////
+// Flags //
+///////////
+
+func init() {
+ apiCreateCmd.Flags().StringVarP(&flags.api.apiname, "apiname", "n", "", wski18n.T("Friendly name of the API; ignored when CFG_FILE is specified (default BASE_PATH)"))
+ apiCreateCmd.Flags().StringVarP(&flags.api.configfile, "config-file", "c", "", wski18n.T("`CFG_FILE` containing API configuration in swagger JSON format"))
+ //apiUpdateCmd.Flags().StringVarP(&flags.api.action, "action", "a", "", wski18n.T("`ACTION` to invoke when API is called"))
+ //apiUpdateCmd.Flags().StringVarP(&flags.api.path, "path", "p", "", wski18n.T("relative `PATH` of API"))
+ //apiUpdateCmd.Flags().StringVarP(&flags.api.verb, "method", "m", "", wski18n.T("API `VERB`"))
+ apiGetCmd.Flags().BoolVarP(&flags.common.detail, "full", "f", false, wski18n.T("display full API configuration details"))
+ apiListCmd.Flags().IntVarP(&flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of actions from the result"))
+ apiListCmd.Flags().IntVarP(&flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of actions from the collection"))
+ apiListCmd.Flags().BoolVarP(&flags.common.full, "full", "f", false, wski18n.T("display full description of each API"))
+ apiExperimentalCmd.AddCommand(
+ apiCreateCmd,
+ //apiUpdateCmd,
+ apiGetCmd,
+ apiDeleteCmd,
+ apiListCmd,
+ )
+
+ apiCreateCmdV2.Flags().StringVarP(&flags.api.apiname, "apiname", "n", "", wski18n.T("Friendly name of the API; ignored when CFG_FILE is specified (default BASE_PATH)"))
+ apiCreateCmdV2.Flags().StringVarP(&flags.api.configfile, "config-file", "c", "", wski18n.T("`CFG_FILE` containing API configuration in swagger JSON format"))
+ apiCreateCmdV2.Flags().StringVar(&flags.api.resptype, "response-type", "json", wski18n.T("Set the web action response `TYPE`. Possible values are html, http, json, text, svg"))
+ apiGetCmdV2.Flags().BoolVarP(&flags.common.detail, "full", "f", false, wski18n.T("display full API configuration details"))
+ apiGetCmdV2.Flags().StringVarP(&flags.common.format, "format", "", formatOptionJson, wski18n.T("Specify the API output `TYPE`, either json or yaml"))
+ apiListCmdV2.Flags().IntVarP(&flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of actions from the result"))
+ apiListCmdV2.Flags().IntVarP(&flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of actions from the collection"))
+ apiListCmdV2.Flags().BoolVarP(&flags.common.full, "full", "f", false, wski18n.T("display full description of each API"))
+ apiCmd.AddCommand(
+ apiCreateCmdV2,
+ apiGetCmdV2,
+ apiDeleteCmdV2,
+ apiListCmdV2,
+ )
+}
diff --git a/tools/cli/go-whisk-cli/commands/commands.go b/tools/cli/go-whisk-cli/commands/commands.go
new file mode 100644
index 0000000..6180c37
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/commands.go
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package commands
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "os"
+
+ "../../go-whisk/whisk"
+ "../wski18n"
+
+ "github.com/spf13/cobra"
+)
+
+var client *whisk.Client
+const DefaultOpenWhiskApiPath string = "/api"
+
+func setupClientConfig(cmd *cobra.Command, args []string) (error){
+ baseURL, err := getURLBase(Properties.APIHost, DefaultOpenWhiskApiPath)
+
+ // Determine if the parent command will require the API host to be set
+ apiHostRequired := (cmd.Parent().Name() == "property" && cmd.Name() == "get" && (flags.property.auth ||
+ flags.property.apihost || flags.property.namespace || flags.property.apiversion || flags.property.cliversion)) ||
+ (cmd.Parent().Name() == "property" && cmd.Name() == "set" && (len(flags.property.apihostSet) > 0 ||
+ len(flags.property.apiversionSet) > 0 || len(flags.global.auth) > 0)) ||
+ (cmd.Parent().Name() == "sdk" && cmd.Name() == "install" && len(args) > 0 && args[0] == "bashauto")
+
+ // Display an error if the parent command requires an API host to be set, and the current API host is not valid
+ if err != nil && !apiHostRequired {
+ whisk.Debug(whisk.DbgError, "getURLBase(%s, %s) error: %s\n", Properties.APIHost, DefaultOpenWhiskApiPath, err)
+ errMsg := wski18n.T("The API host is not valid: {{.err}}", map[string]interface{}{"err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+
+ clientConfig := &whisk.Config{
+ AuthToken: Properties.Auth,
+ Namespace: Properties.Namespace,
+ BaseURL: baseURL,
+ Version: Properties.APIVersion,
+ Insecure: flags.global.insecure,
+ Host: Properties.APIHost,
+ }
+
+ // Setup client
+ client, err = whisk.NewClient(http.DefaultClient, clientConfig)
+
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "whisk.NewClient(%#v, %#v) error: %s\n", http.DefaultClient, clientConfig, err)
+ errMsg := wski18n.T("Unable to initialize server connection: {{.err}}", map[string]interface{}{"err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return whiskErr
+ }
+
+ return nil
+}
+
+func init() {
+ var err error
+
+ err = loadProperties()
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "loadProperties() error: %s\n", err)
+ fmt.Println(err)
+ os.Exit(whisk.EXITCODE_ERR_GENERAL)
+ }
+}
+
+func getKeyValueArgs(args []string, argIndex int, parsedArgs []string) ([]string, []string, error) {
+ var whiskErr error
+ var key string
+ var value string
+
+ if len(args) - 1 >= argIndex + 2 {
+ key = args[argIndex + 1]
+ value = args[argIndex + 2]
+ parsedArgs = append(parsedArgs, getFormattedJSON(key, value))
+ args = append(args[:argIndex], args[argIndex + 3:]...)
+ } else {
+ whisk.Debug(whisk.DbgError, "Arguments for '%s' must be a key/value pair; args: %s", args[argIndex], args)
+ errMsg := wski18n.T("Arguments for '{{.arg}}' must be a key/value pair",
+ map[string]interface{}{"arg": args[argIndex]})
+ whiskErr = whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG,
+ whisk.DISPLAY_USAGE)
+ }
+
+ return parsedArgs, args, whiskErr
+}
+
+func getValueFromArgs(args []string, argIndex int, parsedArgs []string) ([]string, []string, error) {
+ var whiskErr error
+
+ if len(args) - 1 >= argIndex + 1 {
+ parsedArgs = append(parsedArgs, args[argIndex + 1])
+ args = append(args[:argIndex], args[argIndex + 2:]...)
+ } else {
+ whisk.Debug(whisk.DbgError, "An argument must be provided for '%s'; args: %s", args[argIndex], args)
+ errMsg := wski18n.T("An argument must be provided for '{{.arg}}'",
+ map[string]interface{}{"arg": args[argIndex]})
+ whiskErr = whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG,
+ whisk.DISPLAY_USAGE)
+ }
+
+ return parsedArgs, args, whiskErr
+}
+
+func parseArgs(args []string) ([]string, []string, []string, error) {
+ var paramArgs []string
+ var annotArgs []string
+ var whiskErr error
+
+ i := 0
+
+ for i < len(args) {
+ if args[i] == "-P" || args[i] == "--param-file" {
+ paramArgs, args, whiskErr = getValueFromArgs(args, i, paramArgs)
+ if whiskErr != nil {
+ whisk.Debug(whisk.DbgError, "getValueFromArgs(%#v, %d) failed: %s\n", args, i, whiskErr)
+ errMsg := wski18n.T("The parameter arguments are invalid: {{.err}}",
+ map[string]interface{}{"err": whiskErr})
+ whiskErr = whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG,
+ whisk.DISPLAY_USAGE)
+ return nil, nil, nil, whiskErr
+ }
+
+ filename := paramArgs[len(paramArgs) - 1]
+ paramArgs[len(paramArgs) - 1], whiskErr = readFile(filename)
+ if whiskErr != nil {
+ whisk.Debug(whisk.DbgError, "readFile(%s) error: %s\n", filename, whiskErr)
+ return nil, nil, nil, whiskErr
+ }
+ } else if args[i] == "-A" || args[i] == "--annotation-file" {
+ annotArgs, args, whiskErr = getValueFromArgs(args, i, annotArgs)
+ if whiskErr != nil {
+ whisk.Debug(whisk.DbgError, "getValueFromArgs(%#v, %d) failed: %s\n", args, i, whiskErr)
+ errMsg := wski18n.T("The annotation arguments are invalid: {{.err}}",
+ map[string]interface{}{"err": whiskErr})
+ whiskErr = whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG,
+ whisk.DISPLAY_USAGE)
+ return nil, nil, nil, whiskErr
+ }
+
+ filename := annotArgs[len(annotArgs) - 1]
+ annotArgs[len(annotArgs) - 1], whiskErr = readFile(filename)
+ if whiskErr != nil {
+ whisk.Debug(whisk.DbgError, "readFile(%s) error: %s\n", filename, whiskErr)
+ return nil, nil, nil, whiskErr
+ }
+ } else if args[i] == "-p" || args[i] == "--param" {
+ paramArgs, args, whiskErr = getKeyValueArgs(args, i, paramArgs)
+ if whiskErr != nil {
+ whisk.Debug(whisk.DbgError, "getKeyValueArgs(%#v, %d) failed: %s\n", args, i, whiskErr)
+ errMsg := wski18n.T("The parameter arguments are invalid: {{.err}}",
+ map[string]interface{}{"err": whiskErr})
+ whiskErr = whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG,
+ whisk.DISPLAY_USAGE)
+ return nil, nil, nil, whiskErr
+ }
+ } else if args[i] == "-a" || args[i] == "--annotation"{
+ annotArgs, args, whiskErr = getKeyValueArgs(args, i, annotArgs)
+ if whiskErr != nil {
+ whisk.Debug(whisk.DbgError, "getKeyValueArgs(%#v, %d) failed: %s\n", args, i, whiskErr)
+ errMsg := wski18n.T("The annotation arguments are invalid: {{.err}}",
+ map[string]interface{}{"err": whiskErr})
+ whiskErr = whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG,
+ whisk.DISPLAY_USAGE)
+ return nil, nil, nil, whiskErr
+ }
+ } else {
+ i++
+ }
+ }
+
+ whisk.Debug(whisk.DbgInfo, "Found param args '%s'.\n", paramArgs)
+ whisk.Debug(whisk.DbgInfo, "Found annotations args '%s'.\n", annotArgs)
+ whisk.Debug(whisk.DbgInfo, "Arguments with param args removed '%s'.\n", args)
+
+ return args, paramArgs, annotArgs, nil
+}
+
+func Execute() error {
+ var err error
+
+ whisk.Debug(whisk.DbgInfo, "wsk args: %#v\n", os.Args)
+ os.Args, flags.common.param, flags.common.annotation, err = parseArgs(os.Args)
+
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseParams(%s) failed: %s\n", os.Args, err)
+ errMsg := wski18n.T("Failed to parse arguments: {{.err}}", map[string]interface{}{"err":err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return whiskErr
+ }
+
+ return WskCmd.Execute()
+}
diff --git a/tools/cli/go-whisk-cli/commands/flags.go b/tools/cli/go-whisk-cli/commands/flags.go
new file mode 100644
index 0000000..2af878d
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/flags.go
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package commands
+
+import (
+ "os"
+)
+
+///////////
+// Flags //
+///////////
+
+const MEMORY_FLAG = "memory"
+const LOG_SIZE_FLAG = "logsize"
+const TIMEOUT_FLAG = "timeout"
+const WEB_FLAG = "web"
+
+var cliDebug = os.Getenv("WSK_CLI_DEBUG") // Useful for tracing init() code
+
+var flags struct {
+
+ global struct {
+ verbose bool
+ debug bool
+ auth string
+ apihost string
+ apiversion string
+ insecure bool
+ }
+
+ common struct {
+ blocking bool
+ annotation []string
+ annotFile string
+ param []string
+ paramFile string
+ shared string // AKA "public" or "publish"
+ skip int // skip first N records
+ limit int // return max N records
+ full bool // return full records (docs=true for client request)
+ summary bool
+ feed string // name of feed
+ detail bool
+ format string
+ }
+
+ property struct {
+ auth bool
+ apihost bool
+ apiversion bool
+ namespace bool
+ cliversion bool
+ apibuild bool
+ apibuildno bool
+ insecure bool
+ all bool
+ apihostSet string
+ apiversionSet string
+ namespaceSet string
+ }
+
+ action struct {
+ docker bool
+ copy bool
+ pipe bool
+ web string
+ sequence bool
+ timeout int
+ memory int
+ logsize int
+ result bool
+ kind string
+ main string
+ }
+
+ activation struct {
+ action string // retrieve results for this action
+ upto int64 // retrieve results up to certain time
+ since int64 // retrieve results after certain time
+ seconds int // stop polling for activation upda
+ sinceSeconds int
+ sinceMinutes int
+ sinceHours int
+ sinceDays int
+ exit int
+ }
+
+ // rule
+ rule struct {
+ disable bool
+ summary bool
+ }
+
+ // trigger
+ trigger struct {
+ summary bool
+ }
+
+ // api
+ api struct {
+ action string
+ path string
+ verb string
+ basepath string
+ apiname string
+ configfile string
+ resptype string
+ }
+}
+
+func IsVerbose() bool {
+ return flags.global.verbose || IsDebug()
+}
+
+func IsDebug() bool {
+ return len(cliDebug) > 0 || flags.global.debug
+}
diff --git a/tools/cli/go-whisk-cli/commands/namespace.go b/tools/cli/go-whisk-cli/commands/namespace.go
new file mode 100644
index 0000000..cafed6f
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/namespace.go
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package commands
+
+import (
+ "fmt"
+ "errors"
+
+ "github.com/spf13/cobra"
+ "github.com/fatih/color"
+
+ "../../go-whisk/whisk"
+ "../wski18n"
+)
+
+// namespaceCmd represents the namespace command
+var namespaceCmd = &cobra.Command{
+ Use: "namespace",
+ Short: wski18n.T("work with namespaces"),
+}
+
+var namespaceListCmd = &cobra.Command{
+ Use: "list",
+ Short: wski18n.T("list available namespaces"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ // add "TYPE" --> public / private
+
+ if whiskErr := checkArgs(args, 0, 0, "Namespace list", wski18n.T("No arguments are required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ namespaces, _, err := client.Namespaces.List()
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Namespaces.List() error: %s\n", err)
+ errStr := wski18n.T("Unable to obtain the list of available namespaces: {{.err}}",
+ map[string]interface{}{"err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_NETWORK, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ printList(namespaces)
+ return nil
+ },
+}
+
+var namespaceGetCmd = &cobra.Command{
+ Use: "get [NAMESPACE]",
+ Short: wski18n.T("get triggers, actions, and rules in the registry for a namespace"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var qName QualifiedName
+ var err error
+
+ if whiskErr := checkArgs(args, 0, 1, "Namespace get",
+ wski18n.T("An optional namespace is the only valid argument.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ // Namespace argument is optional; defaults to configured property namespace
+ if len(args) == 1 {
+ qName, err = parseQualifiedName(args[0])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": args[0], "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ }
+
+ namespace, _, err := client.Namespaces.Get(qName.namespace)
+
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Namespaces.Get(%s) error: %s\n", getClientNamespace(), err)
+ errStr := wski18n.T("Unable to obtain the list of entities for namespace '{{.namespace}}': {{.err}}",
+ map[string]interface{}{"namespace": getClientNamespace(), "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_NETWORK,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ fmt.Fprintf(color.Output, wski18n.T("Entities in namespace: {{.namespace}}\n",
+ map[string]interface{}{"namespace": boldString(getClientNamespace())}))
+ printList(namespace.Contents.Packages)
+ printList(namespace.Contents.Actions)
+ printList(namespace.Contents.Triggers)
+ printList(namespace.Contents.Rules)
+
+ return nil
+ },
+}
+
+var listCmd = &cobra.Command{
+ Use: "list",
+ Short: wski18n.T("list entities in the current namespace"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: namespaceGetCmd.RunE,
+}
+
+func init() {
+ namespaceCmd.AddCommand(
+ namespaceListCmd,
+ namespaceGetCmd,
+ )
+}
diff --git a/tools/cli/go-whisk-cli/commands/package.go b/tools/cli/go-whisk-cli/commands/package.go
new file mode 100644
index 0000000..6020b72
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/package.go
@@ -0,0 +1,581 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package commands
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+
+ "../../go-whisk/whisk"
+ "../wski18n"
+
+ "github.com/fatih/color"
+ "github.com/spf13/cobra"
+)
+
+var packageCmd = &cobra.Command{
+ Use: "package",
+ Short: wski18n.T("work with packages"),
+}
+
+var packageBindCmd = &cobra.Command{
+ Use: "bind PACKAGE_NAME BOUND_PACKAGE_NAME",
+ Short: wski18n.T("bind parameters to a package"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+
+ if whiskErr := checkArgs(args, 2, 2, "Package bind",
+ wski18n.T("A package name and binding name are required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ packageName := args[0]
+ pkgQName, err := parseQualifiedName(packageName)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", packageName, err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": packageName, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ bindingName := args[1]
+ bindQName, err := parseQualifiedName(bindingName)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", bindingName, err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": bindingName, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ client.Namespace = bindQName.namespace
+
+ // Convert the binding's list of default parameters from a string into []KeyValue
+ // The 1 or more --param arguments have all been combined into a single []string
+ // e.g. --p arg1,arg2 --p arg3,arg4 -> [arg1, arg2, arg3, arg4]
+
+ whisk.Debug(whisk.DbgInfo, "Parsing parameters: %#v\n", flags.common.param)
+ parameters, err := getJSONFromStrings(flags.common.param, true)
+
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", flags.common.param, err)
+ errStr := wski18n.T("Invalid parameter argument '{{.param}}': {{.err}}",
+ map[string]interface{}{"param": fmt.Sprintf("%#v",flags.common.param), "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return werr
+ }
+
+ // Convert the binding's list of default annotations from a string into []KeyValue
+ // The 1 or more --annotation arguments have all been combined into a single []string
+ // e.g. --a arg1,arg2 --a arg3,arg4 -> [arg1, arg2, arg3, arg4]
+ whisk.Debug(whisk.DbgInfo, "Parsing annotations: %#v\n", flags.common.annotation)
+ annotations, err := getJSONFromStrings(flags.common.annotation, true)
+
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", flags.common.annotation, err)
+ errStr := wski18n.T("Invalid annotation argument '{{.annotation}}': {{.err}}",
+ map[string]interface{}{"annotation": fmt.Sprintf("%#v",flags.common.annotation), "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return werr
+ }
+
+ binding := whisk.Binding{
+ Name: pkgQName.entityName,
+ Namespace: pkgQName.namespace,
+ }
+
+ p := &whisk.BindingPackage{
+ Name: bindQName.entityName,
+ Annotations: annotations.(whisk.KeyValueArr),
+ Parameters: parameters.(whisk.KeyValueArr),
+ Binding: binding,
+ }
+
+ _, _, err = client.Packages.Insert(p, false)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Packages.Insert(%#v, false) failed: %s\n", p, err)
+ errStr := wski18n.T("Binding creation failed: {{.err}}", map[string]interface{}{"err":err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ fmt.Fprintf(color.Output, wski18n.T("{{.ok}} created binding {{.name}}\n",
+ map[string]interface{}{"ok": color.GreenString(wski18n.T("ok:")), "name":boldString(bindingName)}))
+ return nil
+ },
+}
+
+var packageCreateCmd = &cobra.Command{
+ Use: "create PACKAGE_NAME",
+ Short: wski18n.T("create a new package"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+ var shared, sharedSet bool
+
+ if whiskErr := checkArgs(args, 1, 1, "Package create", wski18n.T("A package name is required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ qName, err := parseQualifiedName(args[0])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": args[0], "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ client.Namespace = qName.namespace
+
+ if shared, sharedSet, err = parseShared(flags.common.shared); err != nil {
+ whisk.Debug(whisk.DbgError, "parseShared(%s) failed: %s\n", flags.common.shared, err)
+ return err
+ }
+
+ whisk.Debug(whisk.DbgInfo, "Parsing parameters: %#v\n", flags.common.param)
+ parameters, err := getJSONFromStrings(flags.common.param, true)
+
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", flags.common.param, err)
+ errStr := wski18n.T("Invalid parameter argument '{{.param}}': {{.err}}",
+ map[string]interface{}{"param": fmt.Sprintf("%#v",flags.common.param), "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return werr
+ }
+
+ whisk.Debug(whisk.DbgInfo, "Parsing annotations: %#v\n", flags.common.annotation)
+ annotations, err := getJSONFromStrings(flags.common.annotation, true)
+
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", flags.common.annotation, err)
+ errStr := wski18n.T("Invalid annotation argument '{{.annotation}}': {{.err}}",
+ map[string]interface{}{"annotation": fmt.Sprintf("%#v",flags.common.annotation), "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return werr
+ }
+
+ p := &whisk.Package{
+ Name: qName.entityName,
+ Namespace: qName.namespace,
+ Annotations: annotations.(whisk.KeyValueArr),
+ Parameters: parameters.(whisk.KeyValueArr),
+ }
+
+ if sharedSet {
+ p.Publish = &shared
+ }
+
+ p, _, err = client.Packages.Insert(p, false)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Packages.Insert(%#v, false) failed: %s\n", p, err)
+ errStr := wski18n.T(
+ "Unable to create package '{{.name}}': {{.err}}",
+ map[string]interface{}{
+ "name": qName.entityName,
+ "err": err,
+ })
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ fmt.Fprintf(color.Output, wski18n.T("{{.ok}} created package {{.name}}\n",
+ map[string]interface{}{"ok": color.GreenString(wski18n.T("ok:")), "name":boldString(qName.entityName)}))
+ return nil
+ },
+}
+
+var packageUpdateCmd = &cobra.Command{
+ Use: "update PACKAGE_NAME",
+ Short: wski18n.T("update an existing package, or create a package if it does not exist"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+ var shared, sharedSet bool
+
+ if whiskErr := checkArgs(args, 1, 1, "Package update", wski18n.T("A package name is required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ qName, err := parseQualifiedName(args[0])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": args[0], "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ client.Namespace = qName.namespace
+
+ if shared, sharedSet, err = parseShared(flags.common.shared); err != nil {
+ whisk.Debug(whisk.DbgError, "parseShared(%s) failed: %s\n", flags.common.shared, err)
+ return err
+ }
+
+ whisk.Debug(whisk.DbgInfo, "Parsing parameters: %#v\n", flags.common.param)
+ parameters, err := getJSONFromStrings(flags.common.param, true)
+
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", flags.common.param, err)
+ errStr := wski18n.T("Invalid parameter argument '{{.param}}': {{.err}}",
+ map[string]interface{}{"param": fmt.Sprintf("%#v",flags.common.param), "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return werr
+ }
+
+ whisk.Debug(whisk.DbgInfo, "Parsing annotations: %#v\n", flags.common.annotation)
+ annotations, err := getJSONFromStrings(flags.common.annotation, true)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", flags.common.annotation, err)
+ errStr := wski18n.T("Invalid annotation argument '{{.annotation}}': {{.err}}",
+ map[string]interface{}{"annotation": fmt.Sprintf("%#v",flags.common.annotation), "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return werr
+ }
+
+ p := &whisk.Package{
+ Name: qName.entityName,
+ Namespace: qName.namespace,
+ Annotations: annotations.(whisk.KeyValueArr),
+ Parameters: parameters.(whisk.KeyValueArr),
+ }
+
+ if sharedSet {
+ p.Publish = &shared
+ }
+
+ p, _, err = client.Packages.Insert(p, true)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Packages.Insert(%#v, true) failed: %s\n", p, err)
+ errStr := wski18n.T("Package update failed: {{.err}}", map[string]interface{}{"err":err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ fmt.Fprintf(color.Output, wski18n.T("{{.ok}} updated package {{.name}}\n",
+ map[string]interface{}{"ok": color.GreenString(wski18n.T("ok:")), "name":boldString(qName.entityName)}))
+ return nil
+ },
+}
+
+var packageGetCmd = &cobra.Command{
+ Use: "get PACKAGE_NAME [FIELD_FILTER]",
+ Short: wski18n.T("get package"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+ var field string
+
+ if whiskErr := checkArgs(args, 1, 2, "Package get", wski18n.T("A package name is required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ if len(args) > 1 {
+ field = args[1]
+
+ if !fieldExists(&whisk.Package{}, field) {
+ errMsg := wski18n.T("Invalid field filter '{{.arg}}'.", map[string]interface{}{"arg": field})
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+ }
+
+ qName, err := parseQualifiedName(args[0])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": args[0], "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ client.Namespace = qName.namespace
+
+ xPackage, _, err := client.Packages.Get(qName.entityName)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Packages.Get(%s) failed: %s\n", qName.entityName, err)
+ errStr := wski18n.T(
+ "Unable to get package '{{.name}}': {{.err}}",
+ map[string]interface{}{
+ "name": qName.entityName,
+ "err":err,
+ })
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ if flags.common.summary {
+ printSummary(xPackage)
+ } else {
+
+ if len(field) > 0 {
+ fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got package {{.name}}, displaying field {{.field}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(qName.entityName),
+ "field": boldString(field)}))
+ printField(xPackage, field)
+ } else {
+ fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got package {{.name}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(qName.entityName)}))
+ printJSON(xPackage)
+ }
+ }
+
+ return nil
+ },
+}
+
+var packageDeleteCmd = &cobra.Command{
+ Use: "delete PACKAGE_NAME",
+ Short: wski18n.T("delete package"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+
+ if whiskErr := checkArgs(args, 1, 1, "Package delete", wski18n.T("A package name is required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ qName, err := parseQualifiedName(args[0])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": args[0], "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ client.Namespace = qName.namespace
+
+ _, err = client.Packages.Delete(qName.entityName)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Packages.Delete(%s) failed: %s\n", qName.entityName, err)
+ errStr := wski18n.T(
+ "Unable to delete package '{{.name}}': {{.err}}",
+ map[string]interface{}{
+ "name": qName.entityName,
+ "err": err,
+ })
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} deleted package {{.name}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(qName.entityName)}))
+ return nil
+ },
+}
+
+var packageListCmd = &cobra.Command{
+ Use: "list [NAMESPACE]",
+ Short: wski18n.T("list all packages"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+ var shared bool
+
+ qName := QualifiedName{}
+ if len(args) == 1 {
+ qName, err = parseQualifiedName(args[0])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": args[0], "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ ns := qName.namespace
+ if len(ns) == 0 {
+ whisk.Debug(whisk.DbgError, "An empty namespace in the package name '%s' is invalid \n", args[0])
+ errStr := wski18n.T("No valid namespace detected. Run 'wsk property set --namespace' or ensure the name argument is preceded by a \"/\"")
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return werr
+ }
+
+ client.Namespace = ns
+ } else if whiskErr := checkArgs(args, 0, 1, "Package list",
+ wski18n.T("An optional namespace is the only valid argument.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ if flags.common.shared == "yes" {
+ shared = true
+ } else {
+ shared = false
+ }
+
+ options := &whisk.PackageListOptions{
+ Skip: flags.common.skip,
+ Limit: flags.common.limit,
+ Public: shared,
+ }
+
+ packages, _, err := client.Packages.List(options)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Packages.List(%+v) failed: %s\n", options, err)
+ errStr := wski18n.T("Unable to obtain the list of packages for namespace '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": getClientNamespace(), "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ printList(packages)
+ return nil
+ },
+}
+
+var packageRefreshCmd = &cobra.Command{
+ Use: "refresh [NAMESPACE]",
+ Short: wski18n.T("refresh package bindings"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+ var qName QualifiedName
+
+ if whiskErr := checkArgs(args, 0, 1, "Package refresh",
+ wski18n.T("An optional namespace is the only valid argument.")); whiskErr != nil {
+ return whiskErr
+ } else {
+ if len(args) == 0 {
+ qName.namespace = getNamespace()
+ } else {
+ qName, err = parseQualifiedName(args[0])
+
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": args[0], "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ }
+ }
+
+ currentNamespace := client.Config.Namespace
+ client.Config.Namespace = qName.namespace
+
+ defer func() {
+ client.Config.Namespace = currentNamespace
+ }()
+
+ updates, resp, err := client.Packages.Refresh()
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Packages.Refresh() of namespace '%s' failed: %s\n", client.Config.Namespace, err)
+ errStr := wski18n.T("Package refresh for namespace '{{.name}}' failed: {{.err}}",
+ map[string]interface{}{"name": client.Config.Namespace, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ whisk.Debug(whisk.DbgInfo, "Refresh updates received: %#v\n", updates)
+
+ switch resp.StatusCode {
+ case http.StatusOK:
+ fmt.Printf(wski18n.T("{{.name}} refreshed successfully\n",
+ map[string]interface{}{"name": client.Config.Namespace}))
+
+ fmt.Println(wski18n.T("created bindings:"))
+
+ if len(updates.Added) > 0 {
+ printArrayContents(updates.Added)
+ }
+
+ fmt.Println(wski18n.T("updated bindings:"))
+
+ if len(updates.Updated) > 0 {
+ printArrayContents(updates.Updated)
+ }
+
+ fmt.Println(wski18n.T("deleted bindings:"))
+
+ if len(updates.Deleted) > 0 {
+ printArrayContents(updates.Deleted)
+ }
+
+ case http.StatusNotImplemented:
+ whisk.Debug(whisk.DbgError, "client.Packages.Refresh() for namespace '%s' returned 'Not Implemented' HTTP status code: %d\n", client.Config.Namespace, resp.StatusCode)
+ errStr := wski18n.T("The package refresh feature is not implemented in the target deployment")
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_NETWORK, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ default:
+ whisk.Debug(whisk.DbgError, "client.Packages.Refresh() for namespace '%s' returned an unexpected HTTP status code: %d\n", client.Config.Namespace, resp.StatusCode)
+ errStr := wski18n.T("Package refresh for namespace '{{.name}}' failed due to unexpected HTTP status code: {{.code}}",
+ map[string]interface{}{"name": client.Config.Namespace, "code": resp.StatusCode})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_NETWORK, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ return nil
+ },
+}
+
+func init() {
+ packageCreateCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", []string{}, wski18n.T("annotation values in `KEY VALUE` format"))
+ packageCreateCmd.Flags().StringVarP(&flags.common.annotFile, "annotation-file", "A", "", wski18n.T("`FILE` containing annotation values in JSON format"))
+ packageCreateCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format"))
+ packageCreateCmd.Flags().StringVarP(&flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
+ packageCreateCmd.Flags().StringVar(&flags.common.shared, "shared", "", wski18n.T("package visibility `SCOPE`; yes = shared, no = private"))
+
+ packageUpdateCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", []string{}, wski18n.T("annotation values in `KEY VALUE` format"))
+ packageUpdateCmd.Flags().StringVarP(&flags.common.annotFile, "annotation-file", "A", "", wski18n.T("`FILE` containing annotation values in JSON format"))
+ packageUpdateCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format"))
+ packageUpdateCmd.Flags().StringVarP(&flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
+ packageUpdateCmd.Flags().StringVar(&flags.common.shared, "shared", "", wski18n.T("package visibility `SCOPE`; yes = shared, no = private"))
+
+ packageGetCmd.Flags().BoolVarP(&flags.common.summary, "summary", "s", false, wski18n.T("summarize package details"))
+
+ packageBindCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", []string{}, wski18n.T("annotation values in `KEY VALUE` format"))
+ packageBindCmd.Flags().StringVarP(&flags.common.annotFile, "annotation-file", "A", "", wski18n.T("`FILE` containing annotation values in JSON format"))
+ packageBindCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format"))
+ packageBindCmd.Flags().StringVarP(&flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
+
+ packageListCmd.Flags().StringVar(&flags.common.shared, "shared", "", wski18n.T("include publicly shared entities in the result"))
+ packageListCmd.Flags().IntVarP(&flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of packages from the result"))
+ packageListCmd.Flags().IntVarP(&flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of packages from the collection"))
+
+ packageCmd.AddCommand(
+ packageBindCmd,
+ packageCreateCmd,
+ packageUpdateCmd,
+ packageGetCmd,
+ packageDeleteCmd,
+ packageListCmd,
+ packageRefreshCmd,
+ )
+}
diff --git a/tools/cli/go-whisk-cli/commands/property.go b/tools/cli/go-whisk-cli/commands/property.go
new file mode 100644
index 0000000..7fe91e7
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/property.go
@@ -0,0 +1,486 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package commands
+
+import (
+ "errors"
+ "fmt"
+ "os"
+
+ "github.com/mitchellh/go-homedir"
+ "github.com/spf13/cobra"
+ "github.com/fatih/color"
+
+ "../../go-whisk/whisk"
+ "../wski18n"
+)
+
+var Properties struct {
+ Auth string
+ APIHost string
+ APIVersion string
+ APIBuild string
+ APIBuildNo string
+ CLIVersion string
+ Namespace string
+ PropsFile string
+}
+
+const DefaultAuth string = ""
+const DefaultAPIHost string = ""
+const DefaultAPIVersion string = "v1"
+const DefaultAPIBuild string = ""
+const DefaultAPIBuildNo string = ""
+const DefaultNamespace string = "_"
+const DefaultPropsFile string = "~/.wskprops"
+
+var propertyCmd = &cobra.Command{
+ Use: "property",
+ Short: wski18n.T("work with whisk properties"),
+}
+
+//
+// Set one or more openwhisk property values
+//
+var propertySetCmd = &cobra.Command{
+ Use: "set",
+ Short: wski18n.T("set property"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var okMsg string = ""
+ var werr *whisk.WskError = nil
+
+ // get current props
+ props, err := readProps(Properties.PropsFile)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "readProps(%s) failed: %s\n", Properties.PropsFile, err)
+ errStr := wski18n.T("Unable to set the property value: {{.err}}", map[string]interface{}{"err": err})
+ werr = whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ // read in each flag, update if necessary
+
+ if auth := flags.global.auth; len(auth) > 0 {
+ props["AUTH"] = auth
+ client.Config.AuthToken = auth
+ okMsg += fmt.Sprintf(
+ wski18n.T("{{.ok}} whisk auth set. Run 'wsk property get --auth' to see the new value.\n",
+ map[string]interface{}{"ok": color.GreenString("ok:")}))
+ }
+
+ if apiHost := flags.property.apihostSet; len(apiHost) > 0 {
+ baseURL, err := getURLBase(apiHost, DefaultOpenWhiskApiPath)
+
+ if err != nil {
+ // Not aborting now. Subsequent commands will result in error
+ whisk.Debug(whisk.DbgError, "getURLBase(%s, %s) error: %s", apiHost, DefaultOpenWhiskApiPath, err)
+ errStr := fmt.Sprintf(
+ wski18n.T("Unable to set API host value; the API host value '{{.apihost}}' is invalid: {{.err}}",
+ map[string]interface{}{"apihost": apiHost, "err": err}))
+ werr = whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ } else {
+ props["APIHOST"] = apiHost
+ client.Config.BaseURL = baseURL
+ okMsg += fmt.Sprintf(
+ wski18n.T("{{.ok}} whisk API host set to {{.host}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "host": boldString(apiHost)}))
+ }
+ }
+
+ if apiVersion := flags.property.apiversionSet; len(apiVersion) > 0 {
+ props["APIVERSION"] = apiVersion
+ client.Config.Version = apiVersion
+ okMsg += fmt.Sprintf(
+ wski18n.T("{{.ok}} whisk API version set to {{.version}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "version": boldString(apiVersion)}))
+ }
+
+ if namespace := flags.property.namespaceSet; len(namespace) > 0 {
+ namespaces, _, err := client.Namespaces.List()
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Namespaces.List() failed: %s\n", err)
+ errStr := fmt.Sprintf(
+ wski18n.T("Authenticated user does not have namespace '{{.name}}'; set command failed: {{.err}}",
+ map[string]interface{}{"name": namespace, "err": err}))
+ werr = whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ } else {
+ whisk.Debug(whisk.DbgInfo, "Validating namespace '%s' is in user namespace list %#v\n", namespace, namespaces)
+ var validNamespace bool
+ for _, ns := range namespaces {
+ if ns.Name == namespace {
+ whisk.Debug(whisk.DbgInfo, "Namespace '%s' is valid\n", namespace)
+ validNamespace = true
+ }
+ }
+ if !validNamespace {
+ whisk.Debug(whisk.DbgError, "Namespace '%s' is not in the list of entitled namespaces\n", namespace)
+ errStr := fmt.Sprintf(
+ wski18n.T("Namespace '{{.name}}' is not in the list of entitled namespaces",
+ map[string]interface{}{"name": namespace}))
+ werr = whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ } else {
+ props["NAMESPACE"] = namespace
+ okMsg += fmt.Sprintf(
+ wski18n.T("{{.ok}} whisk namespace set to {{.name}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(namespace)}))
+ }
+ }
+ }
+
+ err = writeProps(Properties.PropsFile, props)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "writeProps(%s, %#v) failed: %s\n", Properties.PropsFile, props, err)
+ errStr := fmt.Sprintf(
+ wski18n.T("Unable to set the property value(s): {{.err}}",
+ map[string]interface{}{"err": err}))
+ werr = whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ } else {
+ fmt.Fprintf(color.Output, okMsg)
+ }
+
+ if (werr != nil) {
+ return werr
+ }
+
+ return nil
+ },
+}
+
+var propertyUnsetCmd = &cobra.Command{
+ Use: "unset",
+ Short: wski18n.T("unset property"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var okMsg string = ""
+ props, err := readProps(Properties.PropsFile)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "readProps(%s) failed: %s\n", Properties.PropsFile, err)
+ errStr := fmt.Sprintf(
+ wski18n.T("Unable to unset the property value: {{.err}}",
+ map[string]interface{}{"err": err}))
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ // read in each flag, update if necessary
+
+ if flags.property.auth {
+ delete(props, "AUTH")
+ okMsg += fmt.Sprintf(
+ wski18n.T("{{.ok}} whisk auth unset.\n",
+ map[string]interface{}{"ok": color.GreenString("ok:")}))
+ }
+
+ if flags.property.namespace {
+ delete(props, "NAMESPACE")
+ okMsg += fmt.Sprintf(
+ wski18n.T("{{.ok}} whisk namespace unset",
+ map[string]interface{}{"ok": color.GreenString("ok:")}))
+ if len(DefaultNamespace) > 0 {
+ okMsg += fmt.Sprintf(
+ wski18n.T("; the default value of {{.default}} will be used.\n",
+ map[string]interface{}{"default": boldString(DefaultNamespace)}))
+ } else {
+ okMsg += fmt.Sprint(
+ wski18n.T("; there is no default value that can be used.\n"))
+ }
+ }
+
+ if flags.property.apihost {
+ delete(props, "APIHOST")
+ okMsg += fmt.Sprintf(
+ wski18n.T("{{.ok}} whisk API host unset.\n",
+ map[string]interface{}{"ok": color.GreenString("ok:")}))
+ }
+
+ if flags.property.apiversion {
+ delete(props, "APIVERSION")
+ okMsg += fmt.Sprintf(
+ wski18n.T("{{.ok}} whisk API version unset",
+ map[string]interface{}{"ok": color.GreenString("ok:")}))
+ if len(DefaultAPIVersion) > 0 {
+ okMsg += fmt.Sprintf(
+ wski18n.T("; the default value of {{.default}} will be used.\n",
+ map[string]interface{}{"default": boldString(DefaultAPIVersion)}))
+ } else {
+ okMsg += fmt.Sprint(
+ wski18n.T("; there is no default value that can be used.\n"))
+ }
+ }
+
+ err = writeProps(Properties.PropsFile, props)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "writeProps(%s, %#v) failed: %s\n", Properties.PropsFile, props, err)
+ errStr := fmt.Sprintf(
+ wski18n.T("Unable to unset the property value: {{.err}}",
+ map[string]interface{}{"err": err}))
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ fmt.Fprintf(color.Output, okMsg)
+ if err = loadProperties(); err != nil {
+ whisk.Debug(whisk.DbgError, "loadProperties() failed: %s\n", err)
+ }
+ return nil
+ },
+}
+
+var propertyGetCmd = &cobra.Command{
+ Use: "get",
+ Short: wski18n.T("get property"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+
+ // If no property is explicitly specified, default to all properties
+ if !(flags.property.all || flags.property.auth ||
+ flags.property.apiversion || flags.property.cliversion ||
+ flags.property.namespace || flags.property.apibuild ||
+ flags.property.apihost || flags.property.apibuildno) {
+ flags.property.all = true
+ }
+
+ if flags.property.all || flags.property.auth {
+ fmt.Fprintf(color.Output, "%s\t\t%s\n", wski18n.T("whisk auth"), boldString(Properties.Auth))
+ }
+
+ if flags.property.all || flags.property.apihost {
+ fmt.Fprintf(color.Output, "%s\t\t%s\n", wski18n.T("whisk API host"), boldString(Properties.APIHost))
+ }
+
+ if flags.property.all || flags.property.apiversion {
+ fmt.Fprintf(color.Output, "%s\t%s\n", wski18n.T("whisk API version"), boldString(Properties.APIVersion))
+ }
+
+ if flags.property.all || flags.property.namespace {
+ fmt.Fprintf(color.Output, "%s\t\t%s\n", wski18n.T("whisk namespace"), boldString(Properties.Namespace))
+ }
+
+ if flags.property.all || flags.property.cliversion {
+ fmt.Fprintf(color.Output, "%s\t%s\n", wski18n.T("whisk CLI version"), boldString(Properties.CLIVersion))
+ }
+
+ if flags.property.all || flags.property.apibuild || flags.property.apibuildno {
+ info, _, err := client.Info.Get()
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Info.Get() failed: %s\n", err)
+ info = &whisk.Info{}
+ info.Build = wski18n.T("Unknown")
+ info.BuildNo = wski18n.T("Unknown")
+ }
+ if flags.property.all || flags.property.apibuild {
+ fmt.Fprintf(color.Output, "%s\t\t%s\n", wski18n.T("whisk API build"), boldString(info.Build))
+ }
+ if flags.property.all || flags.property.apibuildno {
+ fmt.Fprintf(color.Output, "%s\t%s\n", wski18n.T("whisk API build number"), boldString(info.BuildNo))
+ }
+ if err != nil {
+ errStr := fmt.Sprintf(
+ wski18n.T("Unable to obtain API build information: {{.err}}",
+ map[string]interface{}{"err": err}))
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ }
+
+ return nil
+ },
+}
+
+func init() {
+ propertyCmd.AddCommand(
+ propertySetCmd,
+ propertyUnsetCmd,
+ propertyGetCmd,
+ )
+
+ // need to set property flags as booleans instead of strings... perhaps with boolApihost...
+ propertyGetCmd.Flags().BoolVar(&flags.property.auth, "auth", false, wski18n.T("authorization key"))
+ propertyGetCmd.Flags().BoolVar(&flags.property.apihost, "apihost", false, wski18n.T("whisk API host"))
+ propertyGetCmd.Flags().BoolVar(&flags.property.apiversion, "apiversion", false, wski18n.T("whisk API version"))
+ propertyGetCmd.Flags().BoolVar(&flags.property.apibuild, "apibuild", false, wski18n.T("whisk API build version"))
+ propertyGetCmd.Flags().BoolVar(&flags.property.apibuildno, "apibuildno", false, wski18n.T("whisk API build number"))
+ propertyGetCmd.Flags().BoolVar(&flags.property.cliversion, "cliversion", false, wski18n.T("whisk CLI version"))
+ propertyGetCmd.Flags().BoolVar(&flags.property.namespace, "namespace", false, wski18n.T("whisk namespace"))
+ propertyGetCmd.Flags().BoolVar(&flags.property.all, "all", false, wski18n.T("all properties"))
+
+ propertySetCmd.Flags().StringVarP(&flags.global.auth, "auth", "u", "", wski18n.T("authorization `KEY`"))
+ propertySetCmd.Flags().StringVar(&flags.property.apihostSet, "apihost", "", wski18n.T("whisk API `HOST`"))
+ propertySetCmd.Flags().StringVar(&flags.property.apiversionSet, "apiversion", "", wski18n.T("whisk API `VERSION`"))
+ propertySetCmd.Flags().StringVar(&flags.property.namespaceSet, "namespace", "", wski18n.T("whisk `NAMESPACE`"))
+
+ propertyUnsetCmd.Flags().BoolVar(&flags.property.auth, "auth", false, wski18n.T("authorization key"))
+ propertyUnsetCmd.Flags().BoolVar(&flags.property.apihost, "apihost", false, wski18n.T("whisk API host"))
+ propertyUnsetCmd.Flags().BoolVar(&flags.property.apiversion, "apiversion", false, wski18n.T("whisk API version"))
+ propertyUnsetCmd.Flags().BoolVar(&flags.property.namespace, "namespace", false, wski18n.T("whisk namespace"))
+
+}
+
+func setDefaultProperties() {
+ Properties.Auth = DefaultAuth
+ Properties.Namespace = DefaultNamespace
+ Properties.APIHost = DefaultAPIHost
+ Properties.APIBuild = DefaultAPIBuild
+ Properties.APIBuildNo = DefaultAPIBuildNo
+ Properties.APIVersion = DefaultAPIVersion
+ Properties.PropsFile = DefaultPropsFile
+ // Properties.CLIVersion value is set from main's init()
+}
+
+func getPropertiesFilePath() (propsFilePath string, werr error) {
+ var envExists bool
+
+ // Environment variable overrides the default properties file path
+ if propsFilePath, envExists = os.LookupEnv("WSK_CONFIG_FILE"); envExists == true || propsFilePath != "" {
+ whisk.Debug(whisk.DbgInfo, "Using properties file '%s' from WSK_CONFIG_FILE environment variable\n", propsFilePath)
+ return propsFilePath, nil
+ } else {
+ var err error
+
+ propsFilePath, err = homedir.Expand(Properties.PropsFile)
+
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "homedir.Expand(%s) failed: %s\n", Properties.PropsFile, err)
+ errStr := fmt.Sprintf(
+ wski18n.T("Unable to locate properties file '{{.filename}}': {{.err}}",
+ map[string]interface{}{"filename": Properties.PropsFile, "err": err}))
+ werr = whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return propsFilePath, werr
+ }
+
+ whisk.Debug(whisk.DbgInfo, "Using properties file home dir '%s'\n", propsFilePath)
+ }
+
+ return propsFilePath, nil
+}
+
+func loadProperties() error {
+ var err error
+
+ setDefaultProperties()
+
+ Properties.PropsFile, err = getPropertiesFilePath()
+ if err != nil {
+ return nil
+ //whisk.Debug(whisk.DbgError, "getPropertiesFilePath() failed: %s\n", err)
+ //errStr := fmt.Sprintf("Unable to load the properties file: %s", err)
+ //werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ //return werr
+ }
+
+ props, err := readProps(Properties.PropsFile)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "readProps(%s) failed: %s\n", Properties.PropsFile, err)
+ errStr := wski18n.T("Unable to read the properties file '{{.filename}}': {{.err}}",
+ map[string]interface{}{"filename": Properties.PropsFile, "err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ if authToken, hasProp := props["AUTH"]; hasProp {
+ Properties.Auth = authToken
+ }
+
+ if authToken := os.Getenv("WHISK_AUTH"); len(authToken) > 0 {
+ Properties.Auth = authToken
+ }
+
+ if apiVersion, hasProp := props["APIVERSION"]; hasProp {
+ Properties.APIVersion = apiVersion
+ }
+
+ if apiVersion := os.Getenv("WHISK_APIVERSION"); len(apiVersion) > 0 {
+ Properties.APIVersion = apiVersion
+ }
+
+ if apiHost, hasProp := props["APIHOST"]; hasProp {
+ Properties.APIHost = apiHost
+ }
+
+ if apiHost := os.Getenv("WHISK_APIHOST"); len(apiHost) > 0 {
+ Properties.APIHost = apiHost
+ }
+
+ if namespace, hasProp := props["NAMESPACE"]; hasProp && len(namespace) > 0 {
+ Properties.Namespace = namespace
+ }
+
+ if namespace := os.Getenv("WHISK_NAMESPACE"); len(namespace) > 0 {
+ Properties.Namespace = namespace
+ }
+
+ return nil
+}
+
+func parseConfigFlags(cmd *cobra.Command, args []string) error {
+
+ if auth := flags.global.auth; len(auth) > 0 {
+ Properties.Auth = auth
+ if client != nil {
+ client.Config.AuthToken = auth
+ }
+ }
+
+ if namespace := flags.property.namespaceSet; len(namespace) > 0 {
+ Properties.Namespace = namespace
+ if client != nil {
+ client.Config.Namespace = namespace
+ }
+ }
+
+ if apiVersion := flags.global.apiversion; len(apiVersion) > 0 {
+ Properties.APIVersion = apiVersion
+ if client != nil {
+ client.Config.Version = apiVersion
+ }
+ }
+
+ if apiHost := flags.global.apihost; len(apiHost) > 0 {
+ Properties.APIHost = apiHost
+
+ if client != nil {
+ client.Config.Host = apiHost
+ baseURL, err := getURLBase(apiHost, DefaultOpenWhiskApiPath)
+
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "getURLBase(%s, %s) failed: %s\n", apiHost, DefaultOpenWhiskApiPath, err)
+ errStr := wski18n.T("Invalid host address '{{.host}}': {{.err}}",
+ map[string]interface{}{"host": Properties.APIHost, "err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ client.Config.BaseURL = baseURL
+ }
+ }
+
+ if flags.global.debug {
+ whisk.SetDebug(true)
+ }
+ if flags.global.verbose {
+ whisk.SetVerbose(true)
+ }
+
+ return nil
+}
+
diff --git a/tools/cli/go-whisk-cli/commands/rule.go b/tools/cli/go-whisk-cli/commands/rule.go
new file mode 100644
index 0000000..e29b677
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/rule.go
@@ -0,0 +1,455 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package commands
+
+import (
+ "errors"
+ "fmt"
+
+ "../../go-whisk/whisk"
+ "../wski18n"
+
+ "github.com/fatih/color"
+ "github.com/spf13/cobra"
+)
+
+// ruleCmd represents the rule command
+var ruleCmd = &cobra.Command{
+ Use: "rule",
+ Short: wski18n.T("work with rules"),
+}
+
+var ruleEnableCmd = &cobra.Command{
+ Use: "enable RULE_NAME",
+ Short: wski18n.T("enable rule"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+
+ if whiskErr := checkArgs(args, 1, 1, "Rule enable", wski18n.T("A rule name is required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ qName, err := parseQualifiedName(args[0])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": args[0], "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ client.Namespace = qName.namespace
+ ruleName := qName.entityName
+
+ _, _, err = client.Rules.SetState(ruleName, "active")
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Rules.SetState(%s, active) failed: %s\n", ruleName, err)
+ errStr := wski18n.T("Unable to enable rule '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": ruleName, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} enabled rule {{.name}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName)}))
+ return nil
+ },
+}
+
+var ruleDisableCmd = &cobra.Command{
+ Use: "disable RULE_NAME",
+ Short: wski18n.T("disable rule"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+
+ if whiskErr := checkArgs(args, 1, 1, "Rule disable", wski18n.T("A rule name is required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ qName, err := parseQualifiedName(args[0])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": args[0], "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ client.Namespace = qName.namespace
+ ruleName := qName.entityName
+
+ _, _, err = client.Rules.SetState(ruleName, "inactive")
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Rules.SetState(%s, inactive) failed: %s\n", ruleName, err)
+ errStr := wski18n.T("Unable to disable rule '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": ruleName, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} disabled rule {{.name}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName)}))
+ return nil
+ },
+}
+
+var ruleStatusCmd = &cobra.Command{
+ Use: "status RULE_NAME",
+ Short: wski18n.T("get rule status"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+
+ if whiskErr := checkArgs(args, 1, 1, "Rule status", wski18n.T("A rule name is required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ qName, err := parseQualifiedName(args[0])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": args[0], "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ client.Namespace = qName.namespace
+ ruleName := qName.entityName
+
+ rule, _, err := client.Rules.Get(ruleName)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Rules.Get(%s) failed: %s\n", ruleName, err)
+ errStr := wski18n.T("Unable to get status of rule '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": ruleName, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return werr
+ }
+
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} rule {{.name}} is {{.status}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName), "status": boldString(rule.Status)}))
+ return nil
+ },
+}
+
+var ruleCreateCmd = &cobra.Command{
+ Use: "create RULE_NAME TRIGGER_NAME ACTION_NAME",
+ Short: wski18n.T("create new rule"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+
+ if whiskErr := checkArgs(args, 3, 3, "Rule create",
+ wski18n.T("A rule, trigger and action name are required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ qName, err := parseQualifiedName(args[0])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": args[0], "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ client.Namespace = qName.namespace
+ ruleName := qName.entityName
+ triggerName := getQualifiedName(args[1], Properties.Namespace)
+ actionName := getQualifiedName(args[2], Properties.Namespace)
+
+ rule := &whisk.Rule{
+ Name: ruleName,
+ Trigger: triggerName,
+ Action: actionName,
+ }
+
+ whisk.Debug(whisk.DbgInfo, "Inserting rule:\n%+v\n", rule)
+ var retRule *whisk.Rule
+ retRule, _, err = client.Rules.Insert(rule, false)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Rules.Insert(%#v) failed: %s\n", rule, err)
+ errStr := wski18n.T("Unable to create rule '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": ruleName, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ whisk.Debug(whisk.DbgInfo, "Inserted rule:\n%+v\n", retRule)
+
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} created rule {{.name}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName)}))
+ return nil
+ },
+}
+
+var ruleUpdateCmd = &cobra.Command{
+ Use: "update RULE_NAME TRIGGER_NAME ACTION_NAME",
+ Short: wski18n.T("update an existing rule, or create a rule if it does not exist"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+
+ if whiskErr := checkArgs(args, 3, 3, "Rule update",
+ wski18n.T("A rule, trigger and action name are required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ qName, err := parseQualifiedName(args[0])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": args[0], "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ client.Namespace = qName.namespace
+ ruleName := qName.entityName
+ triggerName := getQualifiedName(args[1], Properties.Namespace)
+ actionName := getQualifiedName(args[2], Properties.Namespace)
+
+ rule := &whisk.Rule{
+ Name: ruleName,
+ Trigger: triggerName,
+ Action: actionName,
+ }
+
+ _, _, err = client.Rules.Insert(rule, true)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Rules.Insert(%#v) failed: %s\n", rule, err)
+ errStr := wski18n.T("Unable to update rule '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": rule.Name, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} updated rule {{.name}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName)}))
+ return nil
+ },
+}
+
+var ruleGetCmd = &cobra.Command{
+ Use: "get RULE_NAME",
+ Short: wski18n.T("get rule"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+ var field string
+
+ if whiskErr := checkArgs(args, 1, 2, "Rule get", wski18n.T("A rule name is required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ if len(args) > 1 {
+ field = args[1]
+
+ if !fieldExists(&whisk.Rule{}, field){
+ errMsg := wski18n.T("Invalid field filter '{{.arg}}'.", map[string]interface{}{"arg": field})
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+ }
+
+ qName, err := parseQualifiedName(args[0])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": args[0], "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ client.Namespace = qName.namespace
+ ruleName := qName.entityName
+
+ rule, _, err := client.Rules.Get(ruleName)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Rules.Get(%s) failed: %s\n", ruleName, err)
+ errStr := wski18n.T("Unable to get rule '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": ruleName, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return werr
+ }
+
+ if (flags.rule.summary) {
+ printRuleSummary(rule)
+ } else {
+ if len(field) > 0 {
+ fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got rule {{.name}}, displaying field {{.field}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName),
+ "field": field}))
+ printField(rule, field)
+ } else {
+ fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got rule {{.name}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName)}))
+ printJSON(rule)
+ }
+ }
+
+ return nil
+ },
+}
+
+var ruleDeleteCmd = &cobra.Command{
+ Use: "delete RULE_NAME",
+ Short: wski18n.T("delete rule"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+
+ if whiskErr := checkArgs(args, 1, 1, "Rule delete", wski18n.T("A rule name is required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ qName, err := parseQualifiedName(args[0])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": args[0], "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ client.Namespace = qName.namespace
+ ruleName := qName.entityName
+
+ if flags.rule.disable {
+ _, _, err := client.Rules.SetState(ruleName, "inactive")
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Rules.SetState(%s, inactive) failed: %s\n", ruleName, err)
+ errStr := wski18n.T("Unable to disable rule '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": ruleName, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ }
+
+ _, err = client.Rules.Delete(ruleName)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Rules.Delete(%s) error: %s\n", ruleName, err)
+ errStr := wski18n.T("Unable to delete rule '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": ruleName, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} deleted rule {{.name}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName)}))
+ return nil
+ },
+}
+
+var ruleListCmd = &cobra.Command{
+ Use: "list [NAMESPACE]",
+ Short: wski18n.T("list all rules"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+ qName := QualifiedName{}
+
+ if len(args) == 1 {
+ qName, err = parseQualifiedName(args[0])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errStr := wski18n.T("Namespace '{{.name}}' is invalid: {{.err}}\n",
+ map[string]interface{}{"name": args[0], "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ ns := qName.namespace
+ if len(ns) == 0 {
+ whisk.Debug(whisk.DbgError, "Namespace is missing from '%s'\n", args[0])
+ errStr := wski18n.T("No valid namespace detected. Run 'wsk property set --namespace' or ensure the name argument is preceded by a \"/\"")
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return werr
+ }
+ client.Namespace = ns
+ } else if whiskErr := checkArgs(args, 0, 1, "Rule list",
+ wski18n.T("An optional namespace is the only valid argument.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ ruleListOptions := &whisk.RuleListOptions{
+ Skip: flags.common.skip,
+ Limit: flags.common.limit,
+ }
+
+ rules, _, err := client.Rules.List(ruleListOptions)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Rules.List(%#v) error: %s\n", ruleListOptions, err)
+ errStr := wski18n.T("Unable to obtain the list of rules for namespace '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": getClientNamespace(), "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ printList(rules)
+ return nil
+ },
+}
+
+func init() {
+ ruleDeleteCmd.Flags().BoolVar(&flags.rule.disable, "disable", false, wski18n.T("automatically disable rule before deleting it"))
+
+ ruleGetCmd.Flags().BoolVarP(&flags.rule.summary, "summary", "s", false, wski18n.T("summarize rule details"))
+
+ ruleListCmd.Flags().IntVarP(&flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of rules from the result"))
+ ruleListCmd.Flags().IntVarP(&flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of rules from the collection"))
+
+ ruleCmd.AddCommand(
+ ruleCreateCmd,
+ ruleEnableCmd,
+ ruleDisableCmd,
+ ruleStatusCmd,
+ ruleUpdateCmd,
+ ruleGetCmd,
+ ruleDeleteCmd,
+ ruleListCmd,
+ )
+
+}
diff --git a/tools/cli/go-whisk-cli/commands/sdk.go b/tools/cli/go-whisk-cli/commands/sdk.go
new file mode 100644
index 0000000..4fc042b
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/sdk.go
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package commands
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+
+ "github.com/spf13/cobra"
+
+ "../../go-whisk/whisk"
+ "../wski18n"
+)
+
+// sdkCmd represents the sdk command
+var sdkCmd = &cobra.Command{
+ Use: "sdk",
+ Short: wski18n.T("work with the sdk"),
+}
+
+type sdkInfo struct {
+ UrlPath string
+ FileName string
+ isGzTar bool
+ IsGzip bool
+ IsZip bool
+ IsTar bool
+ Unpack bool
+ UnpackDir string
+}
+
+var sdkMap map[string]*sdkInfo
+const SDK_DOCKER_COMPONENT_NAME string = "docker"
+const SDK_IOS_COMPONENT_NAME string = "ios"
+const BASH_AUTOCOMPLETE_FILENAME string = "wsk_cli_bash_completion.sh"
+
+var sdkInstallCmd = &cobra.Command{
+ Use: "install COMPONENT",
+ Short: wski18n.T("install SDK artifacts"),
+ Long: wski18n.T("install SDK artifacts, where valid COMPONENT values are docker, ios, and bashauto"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+ if len(args) != 1 {
+ whisk.Debug(whisk.DbgError, "Invalid number of arguments: %d\n", len(args))
+ errStr := wski18n.T("The SDK component argument is missing. One component (docker, ios, or bashauto) must be specified")
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return werr
+ }
+ component := strings.ToLower(args[0])
+ switch component {
+ case "docker":
+ err = dockerInstall()
+ case "ios":
+ err = iOSInstall()
+ case "bashauto":
+ err = WskCmd.GenBashCompletionFile(BASH_AUTOCOMPLETE_FILENAME)
+ if (err != nil) {
+ whisk.Debug(whisk.DbgError, "GenBashCompletionFile('%s`) error: \n", BASH_AUTOCOMPLETE_FILENAME, err)
+ errStr := wski18n.T("Unable to generate '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": BASH_AUTOCOMPLETE_FILENAME, "err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ fmt.Printf(
+ wski18n.T("bash_completion_msg",
+ map[string]interface{}{"name": BASH_AUTOCOMPLETE_FILENAME}))
+ default:
+ whisk.Debug(whisk.DbgError, "Invalid component argument '%s'\n", component)
+ errStr := wski18n.T("The SDK component argument '{{.component}}' is invalid. Valid components are docker, ios and bashauto",
+ map[string]interface{}{"component": component})
+ err = whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ }
+
+ if err != nil {
+ return err
+ }
+ return nil
+ },
+}
+
+func dockerInstall() error {
+ var err error
+
+ targetFile := sdkMap[SDK_DOCKER_COMPONENT_NAME].FileName
+ if _, err = os.Stat(targetFile); err == nil {
+ whisk.Debug(whisk.DbgError, "os.Stat reports file '%s' exists\n", targetFile)
+ errStr := wski18n.T("The file {{.name}} already exists. Delete it and retry.",
+ map[string]interface{}{"name": targetFile})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ if err = sdkInstall(SDK_DOCKER_COMPONENT_NAME); err != nil {
+ whisk.Debug(whisk.DbgError, "sdkInstall(%s) failed: %s\n", SDK_DOCKER_COMPONENT_NAME, err)
+ errStr := wski18n.T("The {{.component}} SDK installation failed: {{.err}}",
+ map[string]interface{}{"component": SDK_DOCKER_COMPONENT_NAME, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ fmt.Println(wski18n.T("The docker skeleton is now installed at the current directory."))
+ return nil
+}
+
+func iOSInstall() error {
+ var err error
+
+ if err = sdkInstall(SDK_IOS_COMPONENT_NAME); err != nil {
+ whisk.Debug(whisk.DbgError, "sdkInstall(%s) failed: %s\n", SDK_IOS_COMPONENT_NAME, err)
+ errStr := wski18n.T("The {{.component}} SDK installation failed: {{.err}}",
+ map[string]interface{}{"component": SDK_IOS_COMPONENT_NAME, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ fmt.Printf(
+ wski18n.T("Downloaded OpenWhisk iOS starter app. Unzip {{.name}} and open the project in Xcode.\n",
+ map[string]interface{}{"name": sdkMap[SDK_IOS_COMPONENT_NAME].FileName}))
+ return nil
+}
+
+func sdkInstall(componentName string) error {
+ targetFile := sdkMap[componentName].FileName
+ if _, err := os.Stat(targetFile); err == nil {
+ whisk.Debug(whisk.DbgError, "os.Stat reports file '%s' exists\n", targetFile)
+ errStr := wski18n.T("The file {{.name}} already exists. Delete it and retry.",
+ map[string]interface{}{"name": targetFile})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ resp, err := client.Sdks.Install(sdkMap[componentName].UrlPath)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Sdks.Install(%s) failed: %s\n", sdkMap[componentName].UrlPath, err)
+ errStr := wski18n.T("Unable to retrieve '{{.urlpath}}' SDK: {{.err}}",
+ map[string]interface{}{"urlpath": sdkMap[componentName].UrlPath, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ if resp.Body == nil {
+ whisk.Debug(whisk.DbgError, "SDK Install HTTP response has no body\n")
+ errStr := wski18n.T("Server failed to send the '{{.component}}' SDK: {{.err}}",
+ map[string]interface{}{"name": componentName, "err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_NETWORK, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ // Create the SDK file
+ sdkfile, err := os.Create(targetFile)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "os.Create(%s) failure: %s\n", targetFile, err)
+ errStr := wski18n.T("Error creating SDK file {{.name}}: {{.err}}",
+ map[string]interface{}{"name": targetFile, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ // Read the HTTP response body and write it to the SDK file
+ whisk.Debug(whisk.DbgInfo, "Reading SDK file from HTTP response body\n")
+ _, err = io.Copy(sdkfile, resp.Body)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "io.Copy() of resp.Body into sdkfile failure: %s\n", err)
+ errStr := wski18n.T("Error copying server response into file: {{.err}}",
+ map[string]interface{}{"err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ sdkfile.Close()
+ return werr
+
+ }
+ sdkfile.Close() // Don't use 'defer' since this file might need to be deleted after unpack
+
+ // At this point, the entire file is downloaded from the server
+ // Check if there is any special post-download processing (i.e. unpack)
+ if sdkMap[componentName].Unpack {
+ // Make sure the target directory does not already exist
+ defer os.Remove(targetFile)
+ targetdir := sdkMap[componentName].UnpackDir
+ if _, err = os.Stat(targetdir); err == nil {
+ whisk.Debug(whisk.DbgError, "os.Stat reports that directory '%s' exists\n", targetdir)
+ errStr := wski18n.T("The directory {{.name}} already exists. Delete it and retry.",
+ map[string]interface{}{"name": targetdir})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ // If the packed SDK is a .tgz file, unpack it in two steps
+ // 1. UnGzip into temp .tar file
+ // 2. Untar the contents into the current folder
+ if sdkMap[componentName].isGzTar {
+ whisk.Debug(whisk.DbgInfo, "unGzipping downloaded file\n")
+ err := unpackGzip(targetFile, "temp.tar")
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "unpackGzip(%s,temp.tar) failure: %s\n", targetFile, err)
+ errStr := wski18n.T("Error unGzipping file {{.name}}: {{.err}}",
+ map[string]interface{}{"name": targetFile, "err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ defer os.Remove("temp.tar")
+
+ whisk.Debug(whisk.DbgInfo, "unTarring unGzipped file\n")
+ err = unpackTar("temp.tar")
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "unpackTar(temp.tar) failure: %s\n", err)
+ errStr := wski18n.T("Error untarring file {{.name}}: {{.err}}",
+ map[string]interface{}{"name": "temp.tar", "err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ }
+
+ // Future SDKs may require other unpacking procedures not yet covered here....
+ }
+
+ return nil
+}
+
+func init() {
+ sdkCmd.AddCommand(sdkInstallCmd)
+
+ sdkMap = make(map[string]*sdkInfo)
+ sdkMap["docker"] = &sdkInfo{ UrlPath: "blackbox-0.1.0.tar.gz", FileName: "blackbox-0.1.0.tar.gz", isGzTar: true, Unpack: true, UnpackDir: "dockerSkeleton"}
+ sdkMap["ios"] = &sdkInfo{ UrlPath: "OpenWhiskIOSStarterApp.zip", FileName: "OpenWhiskIOSStarterApp.zip", IsZip: true, Unpack: false}
+}
diff --git a/tools/cli/go-whisk-cli/commands/trigger.go b/tools/cli/go-whisk-cli/commands/trigger.go
new file mode 100644
index 0000000..2978aa7
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/trigger.go
@@ -0,0 +1,566 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package commands
+
+import (
+ "errors"
+ "fmt"
+
+ "../../go-whisk/whisk"
+ "../wski18n"
+
+ "github.com/spf13/cobra"
+ "github.com/fatih/color"
+)
+
+const FEED_LIFECYCLE_EVENT = "lifecycleEvent"
+const FEED_TRIGGER_NAME = "triggerName"
+const FEED_AUTH_KEY = "authKey"
+const FEED_CREATE = "CREATE"
+const FEED_DELETE = "DELETE"
+
+// triggerCmd represents the trigger command
+var triggerCmd = &cobra.Command{
+ Use: "trigger",
+ Short: wski18n.T("work with triggers"),
+}
+
+var triggerFireCmd = &cobra.Command{
+ Use: "fire TRIGGER_NAME [PAYLOAD]",
+ Short: wski18n.T("fire trigger event"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+ var parameters interface{}
+
+ if whiskErr := checkArgs(args, 1, 2, "Trigger fire",
+ wski18n.T("A trigger name is required. A payload is optional.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ qName, err := parseQualifiedName(args[0])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": args[0], "err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+
+ return whiskErr
+ }
+
+ client.Namespace = qName.namespace
+
+ // Add payload to parameters
+ if len(args) == 2 {
+ flags.common.param = append(flags.common.param, getFormattedJSON("payload", args[1]))
+ flags.common.param = append(flags.common.param, flags.common.param...)
+ }
+
+ if len(flags.common.param) > 0 {
+ parameters, err = getJSONFromStrings(flags.common.param, false)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, false) failed: %s\n", flags.common.param, err)
+ errStr := wski18n.T("Invalid parameter argument '{{.param}}': {{.err}}",
+ map[string]interface{}{"param": fmt.Sprintf("%#v",flags.common.param), "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return werr
+ }
+ }
+
+ trigResp, _, err := client.Triggers.Fire(qName.entityName, parameters)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Triggers.Fire(%s, %#v) failed: %s\n", qName.entityName, parameters, err)
+ errStr := wski18n.T("Unable to fire trigger '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": qName.entityName, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} triggered /{{.namespace}}/{{.name}} with id {{.id}}\n",
+ map[string]interface{}{
+ "ok": color.GreenString("ok:"),
+ "namespace": boldString(qName.namespace),
+ "name": boldString(qName.entityName),
+ "id": boldString(trigResp.ActivationId)}))
+ return nil
+ },
+}
+
+var triggerCreateCmd = &cobra.Command{
+ Use: "create TRIGGER_NAME",
+ Short: wski18n.T("create new trigger"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+ var annotations interface{}
+ var feedArgPassed bool = (flags.common.feed != "")
+
+ if whiskErr := checkArgs(args, 1, 1, "Trigger create",
+ wski18n.T("A trigger name is required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ qName, err := parseQualifiedName(args[0])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": args[0], "err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+
+ client.Namespace = qName.namespace
+
+ var fullTriggerName string
+ var fullFeedName string
+ if feedArgPassed {
+ whisk.Debug(whisk.DbgInfo, "Trigger has a feed\n")
+ feedqName, err := parseQualifiedName(flags.common.feed)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", flags.common.feed, err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": flags.common.feed, "err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return whiskErr
+ }
+ if len(feedqName.namespace) == 0 {
+ whisk.Debug(whisk.DbgError, "Namespace is missing from '%s'\n", flags.common.feed)
+ errStr := wski18n.T("No valid namespace detected. Run 'wsk property set --namespace' or ensure the name argument is preceded by a \"/\"")
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return werr
+ }
+
+ fullFeedName = fmt.Sprintf("/%s/%s", feedqName.namespace, feedqName.entityName)
+ fullTriggerName = fmt.Sprintf("/%s/%s", qName.namespace, qName.entityName)
+ flags.common.param = append(flags.common.param, getFormattedJSON(FEED_LIFECYCLE_EVENT, FEED_CREATE))
+ flags.common.param = append(flags.common.param, getFormattedJSON(FEED_TRIGGER_NAME, fullTriggerName))
+ flags.common.param = append(flags.common.param, getFormattedJSON(FEED_AUTH_KEY, client.Config.AuthToken))
+ }
+
+
+ // Convert the trigger's list of default parameters from a string into []KeyValue
+ // The 1 or more --param arguments have all been combined into a single []string
+ // e.g. --p arg1,arg2 --p arg3,arg4 -> [arg1, arg2, arg3, arg4]
+ whisk.Debug(whisk.DbgInfo, "Parsing parameters: %#v\n", flags.common.param)
+ parameters, err := getJSONFromStrings(flags.common.param, !feedArgPassed)
+
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", flags.common.param, err)
+ errStr := wski18n.T("Invalid parameter argument '{{.param}}': {{.err}}",
+ map[string]interface{}{"param": fmt.Sprintf("%#v",flags.common.param), "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return werr
+ }
+
+ // Add feed to annotations
+ if feedArgPassed {
+ flags.common.annotation = append(flags.common.annotation, getFormattedJSON("feed", flags.common.feed))
+ }
+
+ whisk.Debug(whisk.DbgInfo, "Parsing annotations: %#v\n", flags.common.annotation)
+ annotations, err = getJSONFromStrings(flags.common.annotation, true)
+
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", flags.common.annotation, err)
+ errStr := wski18n.T("Invalid annotation argument '{{.annotation}}': {{.err}}",
+ map[string]interface{}{"annotation": fmt.Sprintf("%#v",flags.common.annotation), "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return werr
+ }
+
+ trigger := &whisk.Trigger{
+ Name: qName.entityName,
+ Annotations: annotations.(whisk.KeyValueArr),
+ }
+
+ if !feedArgPassed {
+ trigger.Parameters = parameters.(whisk.KeyValueArr)
+ }
+
+ _, _, err = client.Triggers.Insert(trigger, false)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Triggers.Insert(%+v,false) failed: %s\n", trigger, err)
+ errStr := wski18n.T("Unable to create trigger '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": trigger.Name, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ // Invoke the specified feed action to configure the trigger feed
+ if feedArgPassed {
+ err := configureFeed(trigger.Name, fullFeedName)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "configureFeed(%s, %s) failed: %s\n", trigger.Name, flags.common.feed,
+ err)
+ errStr := wski18n.T("Unable to create trigger '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": trigger.Name, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+
+ // Delete trigger that was created for this feed
+ delerr := deleteTrigger(args[0])
+ if delerr != nil {
+ whisk.Debug(whisk.DbgWarn, "Ignoring deleteTrigger(%s) failure: %s\n", args[0], delerr)
+ }
+ return werr
+ }
+ }
+
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} created trigger {{.name}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(trigger.Name)}))
+ return nil
+ },
+}
+
+var triggerUpdateCmd = &cobra.Command{
+ Use: "update TRIGGER_NAME",
+ Short: wski18n.T("update an existing trigger, or create a trigger if it does not exist"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+
+ if whiskErr := checkArgs(args, 1, 1, "Trigger update",
+ wski18n.T("A trigger name is required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ qName, err := parseQualifiedName(args[0])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": args[0], "err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+
+ return whiskErr
+ }
+
+ client.Namespace = qName.namespace
+
+ // Convert the trigger's list of default parameters from a string into []KeyValue
+ // The 1 or more --param arguments have all been combined into a single []string
+ // e.g. --p arg1,arg2 --p arg3,arg4 -> [arg1, arg2, arg3, arg4]
+
+ whisk.Debug(whisk.DbgInfo, "Parsing parameters: %#v\n", flags.common.param)
+ parameters, err := getJSONFromStrings(flags.common.param, true)
+
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", flags.common.param, err)
+ errStr := wski18n.T("Invalid parameter argument '{{.param}}': {{.err}}",
+ map[string]interface{}{"param": fmt.Sprintf("%#v",flags.common.param), "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return werr
+ }
+
+ whisk.Debug(whisk.DbgInfo, "Parsing annotations: %#v\n", flags.common.annotation)
+ annotations, err := getJSONFromStrings(flags.common.annotation, true)
+
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", flags.common.annotation, err)
+ errStr := wski18n.T("Invalid annotation argument '{{.annotation}}': {{.err}}",
+ map[string]interface{}{"annotation": fmt.Sprintf("%#v",flags.common.annotation), "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return werr
+ }
+
+ trigger := &whisk.Trigger{
+ Name: qName.entityName,
+ Parameters: parameters.(whisk.KeyValueArr),
+ Annotations: annotations.(whisk.KeyValueArr),
+ }
+
+ _, _, err = client.Triggers.Insert(trigger, true)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Triggers.Insert(%+v,true) failed: %s\n", trigger, err)
+ errStr := wski18n.T("Unable to update trigger '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": trigger.Name, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} updated trigger {{.name}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(trigger.Name)}))
+ return nil
+ },
+}
+
+var triggerGetCmd = &cobra.Command{
+ Use: "get TRIGGER_NAME [FIELD_FILTER]",
+ Short: wski18n.T("get trigger"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+ var field string
+
+ if whiskErr := checkArgs(args, 1, 2, "Trigger get", wski18n.T("A trigger name is required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ if len(args) > 1 {
+ field = args[1]
+
+ if !fieldExists(&whisk.Trigger{}, field) {
+ errMsg := wski18n.T("Invalid field filter '{{.arg}}'.", map[string]interface{}{"arg": field})
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+ }
+
+ qName, err := parseQualifiedName(args[0])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": args[0], "err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+
+ return whiskErr
+ }
+
+ client.Namespace = qName.namespace
+
+ retTrigger, _, err := client.Triggers.Get(qName.entityName)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Triggers.Get(%s) failed: %s\n", qName.entityName, err)
+ errStr := wski18n.T("Unable to get trigger '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": qName.entityName, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ if (flags.trigger.summary) {
+ printSummary(retTrigger)
+ } else {
+ if len(field) > 0 {
+ fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got trigger {{.name}}, displaying field {{.field}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(qName.entityName),
+ "field": boldString(field)}))
+ printField(retTrigger, field)
+ } else {
+ fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got trigger {{.name}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(qName.entityName)}))
+ printJSON(retTrigger)
+ }
+ }
+
+ return nil
+ },
+}
+
+var triggerDeleteCmd = &cobra.Command{
+ Use: "delete TRIGGER_NAME",
+ Short: wski18n.T("delete trigger"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+ var retTrigger *whisk.Trigger
+ var fullFeedName string
+ var origParams []string
+
+ if whiskErr := checkArgs(args, 1, 1, "Trigger delete",
+ wski18n.T("A trigger name is required.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ qName, err := parseQualifiedName(args[0])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": args[0], "err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+
+ return whiskErr
+ }
+
+ client.Namespace = qName.namespace
+
+ retTrigger, _, err = client.Triggers.Get(qName.entityName)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Triggers.Get(%s) failed: %s\n", qName.entityName, err)
+ errStr := wski18n.T("Unable to get trigger '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": qName.entityName, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ // Get full feed name from trigger delete request as it is needed to delete the feed
+ if retTrigger != nil && retTrigger.Annotations != nil {
+ fullFeedName = getValueString(retTrigger.Annotations, "feed")
+
+ if len(fullFeedName) > 0 {
+ origParams = flags.common.param
+ fullTriggerName := fmt.Sprintf("/%s/%s", qName.namespace, qName.entityName)
+ flags.common.param = append(flags.common.param, getFormattedJSON(FEED_LIFECYCLE_EVENT, FEED_DELETE))
+ flags.common.param = append(flags.common.param, getFormattedJSON(FEED_TRIGGER_NAME, fullTriggerName))
+ flags.common.param = append(flags.common.param, getFormattedJSON(FEED_AUTH_KEY, client.Config.AuthToken))
+
+ err = configureFeed(qName.entityName, fullFeedName)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "configureFeed(%s, %s) failed: %s\n", qName.entityName, fullFeedName, err)
+ }
+
+ flags.common.param = origParams
+ client.Namespace = qName.namespace
+ }
+
+ }
+
+ retTrigger, _, err = client.Triggers.Delete(qName.entityName)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Triggers.Delete(%s) failed: %s\n", qName.entityName, err)
+ errStr := wski18n.T("Unable to delete trigger '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": qName.entityName, "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ fmt.Fprintf(color.Output,
+ wski18n.T("{{.ok}} deleted trigger {{.name}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(qName.entityName)}))
+
+ return nil
+ },
+}
+
+var triggerListCmd = &cobra.Command{
+ Use: "list [NAMESPACE]",
+ Short: wski18n.T("list all triggers"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var err error
+ qName := QualifiedName{}
+ if len(args) == 1 {
+ qName, err = parseQualifiedName(args[0])
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+ errStr := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+ map[string]interface{}{"name": args[0], "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return werr
+ }
+ ns := qName.namespace
+ if len(ns) == 0 {
+ whisk.Debug(whisk.DbgError, "Namespace is missing from '%s'\n", args[0])
+ errStr := wski18n.T("No valid namespace detected. Run 'wsk property set --namespace' or ensure the name argument is preceded by a \"/\"")
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return werr
+ }
+ client.Namespace = ns
+ whisk.Debug(whisk.DbgInfo, "Using namespace '%s' from argument '%s''\n", ns, args[0])
+ } else if whiskErr := checkArgs(args, 0, 1, "Trigger list",
+ wski18n.T("An optional namespace is the only valid argument.")); whiskErr != nil {
+ return whiskErr
+ }
+
+ options := &whisk.TriggerListOptions{
+ Skip: flags.common.skip,
+ Limit: flags.common.limit,
+ }
+ triggers, _, err := client.Triggers.List(options)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "client.Triggers.List(%#v) for namespace '%s' failed: %s\n", options,
+ client.Namespace, err)
+ errStr := wski18n.T("Unable to obtain the list of triggers for namespace '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": getClientNamespace(), "err": err})
+ werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ printList(triggers)
+ return nil
+ },
+}
+
+func configureFeed(triggerName string, FullFeedName string) error {
+ feedArgs := []string {FullFeedName}
+ flags.common.blocking = true
+ err := actionInvokeCmd.RunE(nil, feedArgs)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "Invoke of action '%s' failed: %s\n", FullFeedName, err)
+ errStr := wski18n.T("Unable to invoke trigger '{{.trigname}}' feed action '{{.feedname}}'; feed is not configured: {{.err}}",
+ map[string]interface{}{"trigname": triggerName, "feedname": FullFeedName, "err": err})
+ err = whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ } else {
+ whisk.Debug(whisk.DbgInfo, "Successfully configured trigger feed via feed action '%s'\n", FullFeedName)
+ }
+
+ return err
+}
+
+func deleteTrigger(triggerName string) error {
+ args := []string {triggerName}
+ err := triggerDeleteCmd.RunE(nil, args)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "Trigger '%s' delete failed: %s\n", triggerName, err)
+ errStr := wski18n.T("Unable to delete trigger '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": triggerName, "err": err})
+ err = whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ }
+
+ return err
+}
+
+func init() {
+ triggerCreateCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", []string{}, wski18n.T("annotation values in `KEY VALUE` format"))
+ triggerCreateCmd.Flags().StringVarP(&flags.common.annotFile, "annotation-file", "A", "", wski18n.T("`FILE` containing annotation values in JSON format"))
+ triggerCreateCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format"))
+ triggerCreateCmd.Flags().StringVarP(&flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
+ triggerCreateCmd.Flags().StringVarP(&flags.common.feed, "feed", "f", "", wski18n.T("trigger feed `ACTION_NAME`"))
+
+ triggerUpdateCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", []string{}, wski18n.T("annotation values in `KEY VALUE` format"))
+ triggerUpdateCmd.Flags().StringVarP(&flags.common.annotFile, "annotation-file", "A", "", wski18n.T("`FILE` containing annotation values in JSON format"))
+ triggerUpdateCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format"))
+ triggerUpdateCmd.Flags().StringVarP(&flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
+
+ triggerGetCmd.Flags().BoolVarP(&flags.trigger.summary, "summary", "s", false, wski18n.T("summarize trigger details"))
+
+ triggerFireCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format"))
+ triggerFireCmd.Flags().StringVarP(&flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
+
+ triggerListCmd.Flags().IntVarP(&flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of triggers from the result"))
+ triggerListCmd.Flags().IntVarP(&flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of triggers from the collection"))
+
+ triggerCmd.AddCommand(
+ triggerFireCmd,
+ triggerCreateCmd,
+ triggerUpdateCmd,
+ triggerGetCmd,
+ triggerDeleteCmd,
+ triggerListCmd,
+ )
+
+}
diff --git a/tools/cli/go-whisk-cli/commands/util.go b/tools/cli/go-whisk-cli/commands/util.go
new file mode 100644
index 0000000..b63efdc
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/util.go
@@ -0,0 +1,1069 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package commands
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "strings"
+
+ "../../go-whisk/whisk"
+ "../wski18n"
+
+ "github.com/fatih/color"
+ //prettyjson "github.com/hokaccha/go-prettyjson" // See prettyjson comment below
+ "archive/tar"
+ "io"
+ "os"
+ "compress/gzip"
+ "archive/zip"
+ "encoding/json"
+ "net/url"
+ "io/ioutil"
+ "sort"
+ "reflect"
+ "bytes"
+)
+
+type QualifiedName struct {
+ namespace string // namespace. does not include leading '/'. may be "" (i.e. default namespace)
+ packageName string // package. may be "". does not include leading/trailing '/'
+ entity string // entity. should not be ""
+ entityName string // pkg+entity
+}
+
+func (qName QualifiedName) String() string {
+ output := []string{}
+
+ if len(qName.namespace) > 0 {
+ output = append(output, "/", qName.namespace, "/")
+ }
+ if len(qName.packageName) > 0 {
+ output = append(output, qName.packageName, "/")
+ }
+ output = append(output, qName.entityName)
+
+ return strings.Join(output, "")
+}
+
+/*
+Parse a (possibly fully qualified) resource name into namespace and name components. If the given qualified name isNone,
+then this is a default qualified name and it is resolved from properties. If the namespace is missing from the qualified
+name, the namespace is also resolved from the property file.
+
+Return a qualifiedName struct
+
+Examples:
+ foo => qName {namespace: "_", entityName: foo}
+ pkg/foo => qName {namespace: "_", entityName: pkg/foo}
+ /ns/foo => qName {namespace: ns, entityName: foo}
+ /ns/pkg/foo => qName {namespace: ns, entityName: pkg/foo}
+*/
+func parseQualifiedName(name string) (QualifiedName, error) {
+ var qualifiedName QualifiedName
+
+ // If name has a preceding delimiter (/), it contains a namespace. Otherwise the name does not specify a namespace,
+ // so default the namespace to the namespace value set in the properties file; if that is not set, use "_"
+ if strings.HasPrefix(name, "/") {
+ parts := strings.Split(name, "/")
+ qualifiedName.namespace = parts[1]
+
+ if len(parts) < 2 || len(parts) > 4 {
+ whisk.Debug(whisk.DbgError, "A valid qualified name was not detected\n")
+ errStr := wski18n.T("A valid qualified name must be specified.")
+ err := whisk.MakeWskError(errors.New(errStr), whisk.NOT_ALLOWED, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return qualifiedName, err
+ }
+
+ for i := 1; i < len(parts); i++ {
+ if len(parts[i]) == 0 || parts[i] == "." {
+ whisk.Debug(whisk.DbgError, "A valid qualified name was not detected\n")
+ errStr := wski18n.T("A valid qualified name must be specified.")
+ err := whisk.MakeWskError(errors.New(errStr), whisk.NOT_ALLOWED, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return qualifiedName, err
+ }
+ }
+
+ qualifiedName.entityName = strings.Join(parts[2:], "/")
+ if len(parts) == 4 {
+ qualifiedName.packageName = parts[2]
+ }
+ qualifiedName.entity = parts[len(parts)-1]
+ } else {
+ if len(name) == 0 || name == "." {
+ whisk.Debug(whisk.DbgError, "A valid qualified name was not detected\n")
+ errStr := wski18n.T("A valid qualified name must be specified.")
+ err := whisk.MakeWskError(errors.New(errStr), whisk.NOT_ALLOWED, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return qualifiedName, err
+ }
+
+ parts := strings.Split(name, "/")
+ qualifiedName.entity = parts[len(parts)-1]
+ if len(parts) == 2 {
+ qualifiedName.packageName = parts[0]
+ }
+ qualifiedName.entityName = name
+ qualifiedName.namespace = getNamespace()
+ }
+
+ whisk.Debug(whisk.DbgInfo, "Qualified pkg+entity (EntityName): %s\n", qualifiedName.entityName)
+ whisk.Debug(whisk.DbgInfo, "Qualified namespace: %s\n", qualifiedName.namespace)
+ whisk.Debug(whisk.DbgInfo, "Qualified package: %s\n", qualifiedName.packageName)
+ whisk.Debug(whisk.DbgInfo, "Qualified entity: %s\n", qualifiedName.entity)
+
+ return qualifiedName, nil
+}
+
+func getNamespace() (string) {
+ namespace := "_"
+
+ if Properties.Namespace != "" {
+ namespace = Properties.Namespace
+ }
+
+ return namespace
+}
+
+/*
+Return a fully qualified name given a (possibly fully qualified) resource name and optional namespace.
+
+Examples:
+ (foo, None) => /_/foo
+ (pkg/foo, None) => /_/pkg/foo
+ (foo, ns) => /ns/foo
+ (/ns/pkg/foo, None) => /ns/pkg/foo
+ (/ns/pkg/foo, otherns) => /ns/pkg/foo
+*/
+func getQualifiedName(name string, namespace string) (string) {
+ if strings.HasPrefix(name, "/") {
+ return name
+ } else if strings.HasPrefix(namespace, "/") {
+ return fmt.Sprintf("%s/%s", namespace, name)
+ } else {
+ if len(namespace) == 0 {
+ namespace = Properties.Namespace
+ }
+ return fmt.Sprintf("/%s/%s", namespace, name)
+ }
+}
+
+func csvToQualifiedActions(artifacts string) ([]string) {
+ var res []string
+ actions := strings.Split(artifacts, ",")
+ for i := 0; i < len(actions); i++ {
+ res = append(res, getQualifiedName(actions[i], Properties.Namespace))
+ }
+
+ return res
+}
+
+func getJSONFromStrings(content []string, keyValueFormat bool) (interface{}, error) {
+ var data map[string]interface{}
+ var res interface{}
+
+ whisk.Debug(whisk.DbgInfo, "Convert content to JSON: %#v\n", content)
+
+ for i := 0; i < len(content); i++ {
+ dc := json.NewDecoder(strings.NewReader(content[i]))
+ dc.UseNumber()
+ if err := dc.Decode(&data); err!=nil {
+ whisk.Debug(whisk.DbgError, "Invalid JSON detected for '%s' \n", content[i])
+ return whisk.KeyValueArr{} , err
+ }
+
+ whisk.Debug(whisk.DbgInfo, "Created map '%v' from '%v'\n", data, content[i])
+ }
+
+ if keyValueFormat {
+ res = getKeyValueFormattedJSON(data)
+ } else {
+ res = data
+ }
+
+ return res, nil
+}
+
+func getKeyValueFormattedJSON(data map[string]interface{}) (whisk.KeyValueArr) {
+ var keyValueArr whisk.KeyValueArr
+
+ for key, value := range data {
+ keyValue := whisk.KeyValue{
+ Key: key,
+ Value: value,
+ }
+ keyValueArr = append(keyValueArr, keyValue)
+ }
+
+ whisk.Debug(whisk.DbgInfo, "Created key/value format '%v' from '%v'\n", keyValueArr, data)
+
+ return keyValueArr
+}
+
+func getFormattedJSON(key string, value string) (string) {
+ var res string
+
+ key = getEscapedJSON(key)
+
+ if isValidJSON(value) {
+ whisk.Debug(whisk.DbgInfo, "Value '%s' is valid JSON.\n", value)
+ res = fmt.Sprintf("{\"%s\": %s}", key, value)
+ } else {
+ whisk.Debug(whisk.DbgInfo, "Converting value '%s' to a string as it is not valid JSON.\n", value)
+ res = fmt.Sprintf("{\"%s\": \"%s\"}", key, value)
+ }
+
+ whisk.Debug(whisk.DbgInfo, "Formatted JSON '%s'\n", res)
+
+ return res
+}
+
+func getEscapedJSON(value string) (string) {
+ value = strings.Replace(value, "\\", "\\\\", -1)
+ value = strings.Replace(value, "\"", "\\\"", -1)
+
+ return value
+}
+
+func isValidJSON(value string) (bool) {
+ var jsonInterface interface{}
+ err := json.Unmarshal([]byte(value), &jsonInterface)
+ return err == nil
+}
+
+var boldString = color.New(color.Bold).SprintFunc()
+
+func printList(collection interface{}) {
+ switch collection := collection.(type) {
+ case []whisk.Action:
+ printActionList(collection)
+ case []whisk.Trigger:
+ printTriggerList(collection)
+ case []whisk.Package:
+ printPackageList(collection)
+ case []whisk.Rule:
+ printRuleList(collection)
+ case []whisk.Namespace:
+ printNamespaceList(collection)
+ case []whisk.Activation:
+ printActivationList(collection)
+ case []whisk.Api:
+ printApiList(collection)
+ }
+}
+
+func printFullList(collection interface{}) {
+ switch collection := collection.(type) {
+ case []whisk.Action:
+
+ case []whisk.Trigger:
+
+ case []whisk.Package:
+
+ case []whisk.Rule:
+
+ case []whisk.Namespace:
+
+ case []whisk.Activation:
+ printFullActivationList(collection)
+ }
+}
+
+func printSummary(collection interface{}) {
+ switch collection := collection.(type) {
+ case *whisk.Action:
+ printActionSummary(collection)
+ case *whisk.Trigger:
+ printTriggerSummary(collection)
+ case *whisk.Package:
+ printPackageSummary(collection)
+ case *whisk.Rule:
+
+ case *whisk.Namespace:
+
+ case *whisk.Activation:
+ }
+}
+
+func printActionList(actions []whisk.Action) {
+ fmt.Fprintf(color.Output, "%s\n", boldString("actions"))
+ for _, action := range actions {
+ publishState := wski18n.T("private")
+ kind := getValueString(action.Annotations, "exec")
+ fmt.Printf("%-70s %s %s\n", fmt.Sprintf("/%s/%s", action.Namespace, action.Name), publishState, kind)
+ }
+}
+
+func printTriggerList(triggers []whisk.Trigger) {
+ fmt.Fprintf(color.Output, "%s\n", boldString("triggers"))
+ for _, trigger := range triggers {
+ publishState := wski18n.T("private")
+ fmt.Printf("%-70s %s\n", fmt.Sprintf("/%s/%s", trigger.Namespace, trigger.Name), publishState)
+ }
+}
+
+func printPackageList(packages []whisk.Package) {
+ fmt.Fprintf(color.Output, "%s\n", boldString("packages"))
+ for _, xPackage := range packages {
+ publishState := wski18n.T("private")
+ if xPackage.Publish != nil && *xPackage.Publish {
+ publishState = wski18n.T("shared")
+ }
+ fmt.Printf("%-70s %s\n", fmt.Sprintf("/%s/%s", xPackage.Namespace, xPackage.Name), publishState)
+ }
+}
+
+func printRuleList(rules []whisk.Rule) {
+ fmt.Fprintf(color.Output, "%s\n", boldString("rules"))
+ for _, rule := range rules {
+ publishState := wski18n.T("private")
+ fmt.Printf("%-70s %s\n", fmt.Sprintf("/%s/%s", rule.Namespace, rule.Name), publishState)
+ }
+}
+
+func printNamespaceList(namespaces []whisk.Namespace) {
+ fmt.Fprintf(color.Output, "%s\n", boldString("namespaces"))
+ for _, namespace := range namespaces {
+ fmt.Printf("%s\n", namespace.Name)
+ }
+}
+
+func printActivationList(activations []whisk.Activation) {
+ fmt.Fprintf(color.Output, "%s\n", boldString("activations"))
+ for _, activation := range activations {
+ fmt.Printf("%s %-20s\n", activation.ActivationID, activation.Name)
+ }
+}
+
+func printFullActivationList(activations []whisk.Activation) {
+ fmt.Fprintf(color.Output, "%s\n", boldString("activations"))
+ for _, activation := range activations {
+ printJSON(activation)
+ }
+}
+
+func printApiList(apis []whisk.Api) {
+ fmt.Fprintf(color.Output, "%s\n", boldString("apis"))
+ for _, api := range apis {
+ fmt.Printf("%s %20s %20s\n", api.ApiName, api.GatewayBasePath, api.GatewayFullPath)
+ }
+}
+
+func printFullApiList(apis []whisk.Api) {
+ fmt.Fprintf(color.Output, "%s\n", boldString("apis"))
+ for _, api := range apis {
+ printJSON(api)
+ }
+}
+
+func printActivationLogs(logs []string) {
+ for _, log := range logs {
+ fmt.Printf("%s\n", log)
+ }
+}
+
+func printArrayContents(arrStr []string) {
+ for _, str := range arrStr {
+ fmt.Printf("%s\n", str)
+ }
+}
+
+func printPackageSummary(pkg *whisk.Package) {
+ printEntitySummary(fmt.Sprintf("%7s", "package"), getFullName(pkg.Namespace, pkg.Name, ""),
+ getValueString(pkg.Annotations, "description"),
+ strings.Join(getChildValueStrings(pkg.Annotations, "parameters", "name"), ", "))
+
+
+ if pkg.Actions != nil {
+ for _, action := range pkg.Actions {
+ printEntitySummary(fmt.Sprintf("%7s", "action"), getFullName(pkg.Namespace, pkg.Name, action.Name),
+ getValueString(action.Annotations, "description"),
+ strings.Join(getChildValueStrings(action.Annotations, "parameters", "name"), ", "))
+ }
+ }
+
+ if pkg.Feeds != nil {
+ for _, feed := range pkg.Feeds {
+ printEntitySummary(fmt.Sprintf("%7s", "feed "), getFullName(pkg.Namespace, pkg.Name, feed.Name),
+ getValueString(feed.Annotations, "description"),
+ strings.Join(getChildValueStrings(feed.Annotations, "parameters", "name"), ", "))
+ }
+ }
+}
+
+func printActionSummary(action *whisk.Action) {
+ printEntitySummary(fmt.Sprintf("%6s", "action"),
+ getFullName(action.Namespace, "", action.Name),
+ getValueString(action.Annotations, "description"),
+ strings.Join(getChildValueStrings(action.Annotations, "parameters", "name"), ", "))
+}
+
+func printTriggerSummary(trigger *whisk.Trigger) {
+ printEntitySummary(fmt.Sprintf("%7s", "trigger"),
+ getFullName(trigger.Namespace, "", trigger.Name),
+ getValueString(trigger.Annotations, "description"),
+ strings.Join(getChildValueStrings(trigger.Annotations, "parameters", "name"), ", "))
+}
+
+func printRuleSummary(rule *whisk.Rule) {
+ fmt.Fprintf(color.Output, "%s %s\n", boldString(fmt.Sprintf("%4s", "rule")),
+ getFullName(rule.Namespace, "", rule.Name))
+ fmt.Fprintf(color.Output, " (%s: %s)\n", boldString(wski18n.T("status")), rule.Status)
+}
+
+func printEntitySummary(entityType string, fullName string, description string, params string) {
+ if len(description) > 0 {
+ fmt.Fprintf(color.Output, "%s %s: %s\n", boldString(entityType), fullName, description)
+ } else {
+ fmt.Fprintf(color.Output, "%s %s\n", boldString(entityType), fullName)
+ }
+
+ if len(params) > 0 {
+ fmt.Fprintf(color.Output, " (%s: %s)\n", boldString(wski18n.T("parameters")), params)
+ }
+}
+
+func getFullName(namespace string, packageName string, entityName string) (string) {
+ var fullName string
+
+ if len(namespace) > 0 && len(packageName) > 0 && len(entityName) > 0 {
+ fullName = fmt.Sprintf("/%s/%s/%s", namespace, packageName, entityName)
+ } else if len(namespace) > 0 && len(packageName) > 0 {
+ fullName = fmt.Sprintf("/%s/%s", namespace, packageName)
+ } else if len(namespace) > 0 && len(entityName) > 0 {
+ fullName = fmt.Sprintf("/%s/%s", namespace, entityName)
+ } else if len(namespace) > 0 {
+ fullName = fmt.Sprintf("/%s", namespace)
+ }
+
+ return fullName
+}
+
+func deleteKey(key string, keyValueArr whisk.KeyValueArr) (whisk.KeyValueArr) {
+ for i := 0; i < len(keyValueArr); i++ {
+ if keyValueArr[i].Key == key {
+ keyValueArr = append(keyValueArr[:i], keyValueArr[i + 1:]...)
+ break
+ }
+ }
+
+ return keyValueArr
+}
+
+func addKeyValue(key string, value interface{}, keyValueArr whisk.KeyValueArr) (whisk.KeyValueArr) {
+ keyValue := whisk.KeyValue{
+ Key: key,
+ Value: value,
+ }
+
+ return append(keyValueArr, keyValue)
+}
+
+func getKeys(keyValueArr whisk.KeyValueArr) ([]string) {
+ var res []string
+
+ for i := 0; i < len(keyValueArr); i++ {
+ res = append(res, keyValueArr[i].Key)
+ }
+
+ sort.Strings(res)
+ whisk.Debug(whisk.DbgInfo, "Got keys '%v' from '%v'\n", res, keyValueArr)
+
+ return res
+}
+
+func getValue(keyValueArr whisk.KeyValueArr, key string) (interface{}) {
+ var res interface{}
+
+ for i := 0; i < len(keyValueArr); i++ {
+ if keyValueArr[i].Key == key {
+ res = keyValueArr[i].Value
+ break;
+ }
+ }
+
+ whisk.Debug(whisk.DbgInfo, "Got value '%v' from '%v' for key '%s'\n", res, keyValueArr, key)
+
+ return res
+}
+
+func getValueString(keyValueArr whisk.KeyValueArr, key string) (string) {
+ var value interface{}
+ var res string
+
+ value = getValue(keyValueArr, key)
+ castedValue, canCast := value.(string)
+
+ if (canCast) {
+ res = castedValue
+ }
+
+ whisk.Debug(whisk.DbgInfo, "Got string value '%v' for key '%s'\n", res, key)
+
+ return res
+}
+
+func getChildValues(keyValueArr whisk.KeyValueArr, key string, childKey string) ([]interface{}) {
+ var value interface{}
+ var res []interface{}
+
+ value = getValue(keyValueArr, key)
+
+ castedValue, canCast := value.([]interface{})
+ if canCast {
+ for i := 0; i < len(castedValue); i++ {
+ castedValue, canCast := castedValue[i].(map[string]interface{})
+ if canCast {
+ for subKey, subValue := range castedValue {
+ if subKey == childKey {
+ res = append(res, subValue)
+ }
+ }
+ }
+ }
+ }
+
+ whisk.Debug(whisk.DbgInfo, "Got values '%s' from '%v' for key '%s' and child key '%s'\n", res, keyValueArr, key,
+ childKey)
+
+ return res
+}
+
+func getChildValueStrings(keyValueArr whisk.KeyValueArr, key string, childKey string) ([]string) {
+ var keys []interface{}
+ var res []string
+
+ keys = getChildValues(keyValueArr, key, childKey)
+
+ for i := 0; i < len(keys); i++ {
+ castedValue, canCast := keys[i].(string)
+ if (canCast) {
+ res = append(res, castedValue)
+ }
+ }
+
+ sort.Strings(res)
+ whisk.Debug(whisk.DbgInfo, "Got values '%s' from '%v' for key '%s' and child key '%s'\n", res, keyValueArr, key,
+ childKey)
+
+ return res
+}
+
+func getValueFromJSONResponse(field string, response map[string]interface {}) (interface{}) {
+ var res interface{}
+
+ for key, value := range response {
+ if key == field {
+ res = value
+ break
+ }
+ }
+
+ return res
+}
+
+func logoText() string {
+ logo := `
+ ____ ___ _ _ _ _ _
+ /\ \ / _ \ _ __ ___ _ __ | | | | |__ (_)___| | __
+ /\ /__\ \ | | | | '_ \ / _ \ '_ \| | | | '_ \| / __| |/ /
+ / \____ \ / | |_| | |_) | __/ | | | |/\| | | | | \__ \ <
+ \ \ / \/ \___/| .__/ \___|_| |_|__/\__|_| |_|_|___/_|\_\
+ \___\/ tm |_|
+`
+
+ return logo
+}
+
+func printJSON(v interface{}, stream ...io.Writer) {
+ // Can't use prettyjson util issue https://github.com/hokaccha/go-prettyjson/issues/1 is fixed
+ //output, _ := prettyjson.Marshal(v)
+ //
+ //if len(stream) > 0 {
+ // fmt.Fprintf(stream[0], string(output))
+ //} else {
+ // fmt.Fprintf(color.Output, string(output))
+ //}
+ printJsonNoColor(v, stream...)
+}
+
+func printJsonNoColor(decoded interface{}, stream ...io.Writer) {
+ var output bytes.Buffer
+
+ buffer := new(bytes.Buffer)
+ encoder := json.NewEncoder(buffer)
+ encoder.SetEscapeHTML(false)
+ encoder.Encode(&decoded)
+ json.Indent(&output, buffer.Bytes(), "", " ")
+
+ if len(stream) > 0 {
+ fmt.Fprintf(stream[0], "%s", string(output.Bytes()))
+ } else {
+ fmt.Fprintf(os.Stdout, "%s", string(output.Bytes()))
+ }
+}
+
+func unpackGzip(inpath string, outpath string) error {
+ // Make sure the target file does not exist
+ if _, err := os.Stat(outpath); err == nil {
+ whisk.Debug(whisk.DbgError, "os.Stat reports file '%s' exists\n", outpath)
+ errStr := wski18n.T("The file {{.name}} already exists. Delete it and retry.",
+ map[string]interface{}{"name": outpath})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ // Make sure the input file exists
+ if _, err := os.Stat(inpath); err != nil {
+ whisk.Debug(whisk.DbgError, "os.Stat reports file '%s' does not exist\n", inpath)
+ errStr := wski18n.T("The file '{{.name}}' does not exist.", map[string]interface{}{"name": inpath})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ unGzFile, err := os.Create(outpath)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "os.Create(%s) failed: %s\n", outpath, err)
+ errStr := wski18n.T("Error creating unGzip file '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": outpath, "err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ defer unGzFile.Close()
+
+ gzFile, err := os.Open(inpath)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "os.Open(%s) failed: %s\n", inpath, err)
+ errStr := wski18n.T("Error opening Gzip file '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": inpath, "err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ defer gzFile.Close()
+
+ gzReader, err := gzip.NewReader(gzFile)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "gzip.NewReader() failed: %s\n", err)
+ errStr := wski18n.T("Unable to unzip file '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": inpath, "err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ _, err = io.Copy(unGzFile, gzReader)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "io.Copy() failed: %s\n", err)
+ errStr := wski18n.T("Unable to unzip file '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": inpath, "err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ return nil
+}
+
+func unpackZip(inpath string) error {
+ // Make sure the input file exists
+ if _, err := os.Stat(inpath); err != nil {
+ whisk.Debug(whisk.DbgError, "os.Stat reports file '%s' does not exist\n", inpath)
+ errStr := wski18n.T("The file '{{.name}}' does not exist.", map[string]interface{}{"name": inpath})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ zipFileReader, err := zip.OpenReader(inpath)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "zip.OpenReader(%s) failed: %s\n", inpath, err)
+ errStr := wski18n.T("Unable to opens '{{.name}}' for unzipping: {{.err}}",
+ map[string]interface{}{"name": inpath, "err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ defer zipFileReader.Close()
+
+ // Loop through the files in the zipfile
+ for _, item := range zipFileReader.File {
+ itemName := item.Name
+ itemType := item.Mode()
+
+ whisk.Debug(whisk.DbgInfo, "file item - %#v\n", item)
+
+ if itemType.IsDir() {
+ if err := os.MkdirAll(item.Name, item.Mode()); err != nil {
+ whisk.Debug(whisk.DbgError, "os.MkdirAll(%s, %d) failed: %s\n", item.Name, item.Mode(), err)
+ errStr := wski18n.T("Unable to create directory '{{.dir}}' while unzipping '{{.name}}': {{.err}}",
+ map[string]interface{}{"dir": item.Name, "name": inpath, "err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ }
+
+ if itemType.IsRegular() {
+ unzipFile, err := item.Open()
+ defer unzipFile.Close()
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "'%s' Open() failed: %s\n", item.Name, err)
+ errStr := wski18n.T("Unable to open zipped file '{{.file}}' while unzipping '{{.name}}': {{.err}}",
+ map[string]interface{}{"file": item.Name, "name": inpath, "err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ targetFile, err := os.Create(itemName)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "os.Create(%s) failed: %s\n", itemName, err)
+ errStr := wski18n.T("Unable to create file '{{.file}}' while unzipping '{{.name}}': {{.err}}",
+ map[string]interface{}{"file": item.Name, "name": inpath, "err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ if _, err := io.Copy(targetFile, unzipFile); err != nil {
+ whisk.Debug(whisk.DbgError, "io.Copy() of '%s' failed: %s\n", itemName, err)
+ errStr := wski18n.T("Unable to unzip file '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": itemName, "err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ }
+ }
+
+ return nil
+}
+
+func unpackTar(inpath string) error {
+
+ // Make sure the input file exists
+ if _, err := os.Stat(inpath); err != nil {
+ whisk.Debug(whisk.DbgError, "os.Stat reports file '%s' does not exist\n", inpath)
+ errStr := wski18n.T("The file '{{.name}}' does not exist.", map[string]interface{}{"name": inpath})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ tarFileReader, err := os.Open(inpath)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "os.Open(%s) failed: %s\n", inpath, err)
+ errStr := wski18n.T("Error opening tar file '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": inpath, "err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ defer tarFileReader.Close()
+
+ // Loop through the files in the tarfile
+ tReader := tar.NewReader(tarFileReader)
+ for {
+ item, err := tReader.Next()
+ if err == io.EOF {
+ whisk.Debug(whisk.DbgError, "EOF reach during untar\n")
+ break // end of tar
+ }
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "tReader.Next() failed: %s\n", err)
+ errStr := wski18n.T("Error reading tar file '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": inpath, "err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+
+ whisk.Debug(whisk.DbgInfo, "tar file item - %#v\n", item)
+ switch item.Typeflag {
+ case tar.TypeDir:
+ if err := os.MkdirAll(item.Name, os.FileMode(item.Mode)); err != nil {
+ whisk.Debug(whisk.DbgError, "os.MkdirAll(%s, %d) failed: %s\n", item.Name, item.Mode, err)
+ errStr := wski18n.T("Unable to create directory '{{.dir}}' while untarring '{{.name}}': {{.err}}",
+ map[string]interface{}{"dir": item.Name, "name": inpath, "err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ case tar.TypeReg:
+ untarFile, err:= os.OpenFile(item.Name, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(item.Mode))
+ defer untarFile.Close()
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "os.Create(%s) failed: %s\n", item.Name, err)
+ errStr := wski18n.T("Unable to create file '{{.file}}' while untarring '{{.name}}': {{.err}}",
+ map[string]interface{}{"file": item.Name, "name": inpath, "err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ if _, err := io.Copy(untarFile, tReader); err != nil {
+ whisk.Debug(whisk.DbgError, "io.Copy() of '%s' failed: %s\n", item.Name, err)
+ errStr := wski18n.T("Unable to untar file '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": item.Name, "err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ default:
+ whisk.Debug(whisk.DbgError, "Unexpected tar file type of %q\n", item.Typeflag)
+ errStr := wski18n.T("Unable to untar '{{.name}}' due to unexpected tar file type\n",
+ map[string]interface{}{"name": item.Name})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ }
+ return nil
+}
+
+func checkArgs(args []string, minimumArgNumber int, maximumArgNumber int, commandName string,
+ requiredArgMsg string) (*whisk.WskError) {
+ exactlyOrAtLeast := wski18n.T("exactly")
+ exactlyOrNoMoreThan := wski18n.T("exactly")
+
+ if (minimumArgNumber != maximumArgNumber) {
+ exactlyOrAtLeast = wski18n.T("at least")
+ exactlyOrNoMoreThan = wski18n.T("no more than")
+ }
+
+ if len(args) < minimumArgNumber {
+ whisk.Debug(whisk.DbgError, fmt.Sprintf("%s command must have %s %d argument(s)\n", commandName,
+ exactlyOrAtLeast, minimumArgNumber))
+ errMsg := wski18n.T("Invalid argument(s). {{.required}}", map[string]interface{}{"required": requiredArgMsg})
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return whiskErr
+ } else if len(args) > maximumArgNumber {
+ whisk.Debug(whisk.DbgError, fmt.Sprintf("%s command must have %s %d argument(s)\n", commandName,
+ exactlyOrNoMoreThan, maximumArgNumber))
+ errMsg := wski18n.T("Invalid argument(s): {{.args}}. {{.required}}",
+ map[string]interface{}{"args": strings.Join(args[maximumArgNumber:], ", "), "required": requiredArgMsg})
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return whiskErr
+ } else {
+ return nil
+ }
+}
+
+func getURLBase(host string, path string) (*url.URL, error) {
+ if len(host) == 0 {
+ errMsg := wski18n.T("An API host must be provided.")
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return nil, whiskErr
+ }
+
+ urlBase := fmt.Sprintf("%s%s", host, path)
+ url, err := url.Parse(urlBase)
+
+ if len(url.Scheme) == 0 || len(url.Host) == 0 {
+ urlBase = fmt.Sprintf("https://%s%s", host, path)
+ url, err = url.Parse(urlBase)
+ }
+
+ return url, err
+}
+
+func normalizeNamespace(namespace string) (string) {
+ if (namespace == "_") {
+ namespace = wski18n.T("default")
+ }
+
+ return namespace
+}
+
+func getClientNamespace() (string) {
+ return normalizeNamespace(client.Config.Namespace)
+}
+
+func readFile(filename string) (string, error) {
+ _, err := os.Stat(filename)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "os.Stat(%s) error: %s\n", filename, err)
+ errMsg := wski18n.T("File '{{.name}}' is not a valid file or it does not exist: {{.err}}",
+ map[string]interface{}{"name": filename, "err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_USAGE,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+
+ return "", whiskErr
+ }
+
+ file, err := ioutil.ReadFile(filename)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "os.ioutil.ReadFile(%s) error: %s\n", filename, err)
+ errMsg := wski18n.T("Unable to read '{{.name}}': {{.err}}",
+ map[string]interface{}{"name": filename, "err": err})
+ whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+ whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+ return "", whiskErr
+ }
+
+ return string(file), nil
+}
+
+func fieldExists(value interface{}, field string) (bool) {
+ element := reflect.ValueOf(value).Elem()
+
+ for i := 0; i < element.NumField(); i++ {
+ if strings.ToLower(element.Type().Field(i).Name) == strings.ToLower(field) {
+ return true
+ }
+ }
+
+ return false
+}
+
+func printField(value interface{}, field string) {
+ var matchFunc = func(structField string) bool {
+ return strings.ToLower(structField) == strings.ToLower(field)
+ }
+
+ structValue := reflect.ValueOf(value)
+ fieldValue := reflect.Indirect(structValue).FieldByNameFunc(matchFunc)
+
+ printJSON(fieldValue.Interface())
+}
+
+func parseShared(shared string) (bool, bool, error) {
+ var isShared, isSet bool
+
+ if strings.ToLower(shared) == "yes" {
+ isShared = true
+ isSet = true
+ } else if strings.ToLower(shared) == "no" {
+ isShared = false
+ isSet = true
+ } else if len(shared) == 0 {
+ isSet = false
+ } else {
+ whisk.Debug(whisk.DbgError, "Cannot use value '%s' for shared.\n", shared)
+ errMsg := wski18n.T("Cannot use value '{{.arg}}' for shared.", map[string]interface{}{"arg": shared})
+ whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG,
+ whisk.DISPLAY_USAGE)
+ return false, false, whiskErr
+ }
+
+ whisk.Debug(whisk.DbgError, "Sharing is '%t'\n", isShared)
+
+ return isShared, isSet, nil
+}
+
+func max(a int, b int) int {
+ if (a > b) {
+ return a
+ }
+ return b
+}
+
+func min (a int, b int) int {
+ if (a < b) {
+ return a
+ }
+ return b
+}
+
+func readProps(path string) (map[string]string, error) {
+
+ props := map[string]string{}
+
+ file, err := os.Open(path)
+ if err != nil {
+ // If file does not exist, just return props
+ whisk.Debug(whisk.DbgWarn, "Unable to read whisk properties file '%s' (file open error: %s); falling back to default properties\n" ,path, err)
+ return props, nil
+ }
+ defer file.Close()
+
+ lines := []string{}
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ lines = append(lines, scanner.Text())
+ }
+
+ props = map[string]string{}
+ for _, line := range lines {
+ kv := strings.Split(line, "=")
+ if len(kv) != 2 {
+ // Invalid format; skip
+ continue
+ }
+ props[kv[0]] = kv[1]
+ }
+
+ return props, nil
+
+}
+
+func writeProps(path string, props map[string]string) error {
+
+ file, err := os.Create(path)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "os.Create(%s) failed: %s\n", path, err)
+ errStr := wski18n.T("Whisk properties file write failure: {{.err}}", map[string]interface{}{"err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ defer file.Close()
+
+ writer := bufio.NewWriter(file)
+ defer writer.Flush()
+ for key, value := range props {
+ line := fmt.Sprintf("%s=%s", strings.ToUpper(key), value)
+ _, err = fmt.Fprintln(writer, line)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "fmt.Fprintln() write to '%s' failed: %s\n", path, err)
+ errStr := wski18n.T("Whisk properties file write failure: {{.err}}", map[string]interface{}{"err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return werr
+ }
+ }
+ return nil
+}
+
+func getSpaceGuid() (string, error) {
+ // get current props
+ props, err := readProps(Properties.PropsFile)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "readProps(%s) failed: %s\n", Properties.PropsFile, err)
+ errStr := wski18n.T("Unable to obtain the `auth` property value: {{.err}}", map[string]interface{}{"err": err})
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return "", werr
+ }
+
+ // get the auth key and parse out the space guid
+ if authToken, hasProp := props["AUTH"]; hasProp {
+ spaceGuid := strings.Split(authToken, ":")[0]
+ return spaceGuid, nil
+ }
+
+ whisk.Debug(whisk.DbgError, "auth not found in properties: %#q\n", props)
+ errStr := wski18n.T("Auth key property value is not set")
+ werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+ return "", werr
+}
+
+func isBlockingTimeout(err error) (bool) {
+ var blockingTimeout bool
+
+ whiskErr, isWhiskErr := err.(*whisk.WskError)
+
+ if isWhiskErr && whiskErr.TimedOut {
+ blockingTimeout = true
+ }
+
+ return blockingTimeout
+}
+
+func isApplicationError(err error) (bool) {
+ var applicationError bool
+
+ whiskErr, isWhiskErr := err.(*whisk.WskError)
+
+ if isWhiskErr && whiskErr.ApplicationError {
+ applicationError = true
+ }
+
+ return applicationError
+}
diff --git a/tools/cli/go-whisk-cli/commands/wsk.go b/tools/cli/go-whisk-cli/commands/wsk.go
new file mode 100644
index 0000000..7290549
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/wsk.go
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package commands
+
+import (
+ "github.com/spf13/cobra"
+ "../wski18n"
+)
+
+// WskCmd defines the entry point for the cli.
+var WskCmd = &cobra.Command{
+ Use: "wsk",
+ Short: wski18n.T("OpenWhisk cloud computing command line interface."),
+ Long: logoText(),
+ SilenceUsage: true,
+ PersistentPreRunE:parseConfigFlags,
+}
+
+
+
+
+func init() {
+ WskCmd.SetHelpTemplate(`{{with or .Long .Short }}{{.}}
+{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`)
+
+ WskCmd.AddCommand(
+ actionCmd,
+ activationCmd,
+ packageCmd,
+ ruleCmd,
+ triggerCmd,
+ sdkCmd,
+ propertyCmd,
+ namespaceCmd,
+ listCmd,
+ apiExperimentalCmd,
+ apiCmd,
+ )
+
+ WskCmd.PersistentFlags().BoolVarP(&flags.global.verbose, "verbose", "v", false, wski18n.T("verbose output"))
+ WskCmd.PersistentFlags().BoolVarP(&flags.global.debug, "debug", "d", false, wski18n.T("debug level output"))
+ WskCmd.PersistentFlags().StringVarP(&flags.global.auth, "auth", "u", "", wski18n.T("authorization `KEY`"))
+ WskCmd.PersistentFlags().StringVar(&flags.global.apihost, "apihost", "", wski18n.T("whisk API `HOST`"))
+ WskCmd.PersistentFlags().StringVar(&flags.global.apiversion, "apiversion", "", wski18n.T("whisk API `VERSION`"))
+ WskCmd.PersistentFlags().BoolVarP(&flags.global.insecure, "insecure", "i", false, wski18n.T("bypass certificate checking"))
+}
diff --git a/tools/cli/go-whisk-cli/main.go b/tools/cli/go-whisk-cli/main.go
new file mode 100644
index 0000000..a26f8fc
--- /dev/null
+++ b/tools/cli/go-whisk-cli/main.go
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package main
+
+import (
+ "fmt"
+ "os"
+ "reflect"
+ goi18n "github.com/nicksnyder/go-i18n/i18n"
+ "github.com/fatih/color"
+
+ "../go-whisk/whisk"
+ "./commands"
+ "./wski18n"
+ "github.com/mattn/go-colorable"
+)
+
+// CLI_BUILD_TIME holds the time of the CLI build. During gradle builds,
+// this value will be overwritten via the command:
+// go build -ldflags "-X main.CLI_BUILD_TIME=nnnnn" // nnnnn is the new timestamp
+var CLI_BUILD_TIME string = "not set"
+
+var cliDebug = os.Getenv("WSK_CLI_DEBUG") // Useful for tracing init() code
+
+var T goi18n.TranslateFunc
+
+func init() {
+ if len(cliDebug) > 0 {
+ whisk.SetDebug(true)
+ }
+
+ T = wski18n.T
+
+ // Rest of CLI uses the Properties struct, so set the build time there
+ commands.Properties.CLIVersion = CLI_BUILD_TIME
+}
+
+func main() {
+ var exitCode int = 0
+ var displayUsage bool = false
+ var displayMsg bool = false
+ var msgDisplayed bool = true
+ var displayPrefix bool = true
+
+ defer func() {
+ if r := recover(); r != nil {
+ fmt.Println(r)
+ fmt.Println(T("Application exited unexpectedly"))
+ }
+ }()
+
+ if err := commands.Execute(); err != nil {
+ whisk.Debug(whisk.DbgInfo, "err object type: %s\n", reflect.TypeOf(err).String())
+
+ werr, isWskError := err.(*whisk.WskError) // Is the err a WskError?
+ if isWskError {
+ whisk.Debug(whisk.DbgError, "Got a *whisk.WskError error: %#v\n", werr)
+ displayUsage = werr.DisplayUsage
+ displayMsg = werr.DisplayMsg
+ msgDisplayed = werr.MsgDisplayed
+ displayPrefix = werr.DisplayPrefix
+ exitCode = werr.ExitCode
+ } else {
+ whisk.Debug(whisk.DbgError, "Got some other error: %s\n", err)
+ fmt.Fprintf(os.Stderr, "%s\n", err)
+
+ displayUsage = false // Cobra already displayed the usage message
+ exitCode = 1;
+ }
+
+ outputStream := colorable.NewColorableStderr()
+
+ // If the err msg should be displayed to the console and it has not already been
+ // displayed, display it now.
+ if displayMsg && !msgDisplayed && displayPrefix && exitCode != 0 {
+ fmt.Fprintf(outputStream, "%s%s\n", color.RedString(T("error: ")), err)
+ } else if displayMsg && !msgDisplayed && !displayPrefix && exitCode != 0 {
+ fmt.Fprintf(outputStream, "%s\n", err)
+ } else if displayMsg && !msgDisplayed && exitCode == 0 {
+ fmt.Fprintf(outputStream, "%s\n", err)
+ }
+
+ // Displays usage
+ if displayUsage {
+ fmt.Fprintf(outputStream, T("Run '{{.Name}} --help' for usage.\n",
+ map[string]interface{}{ "Name" : commands.WskCmd.CommandPath()}))
+ }
+ }
+ os.Exit(exitCode)
+ return
+}
diff --git a/tools/cli/go-whisk-cli/wski18n/detection.go b/tools/cli/go-whisk-cli/wski18n/detection.go
new file mode 100644
index 0000000..03d9be8
--- /dev/null
+++ b/tools/cli/go-whisk-cli/wski18n/detection.go
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package wski18n
+
+import "github.com/cloudfoundry/jibber_jabber"
+
+type Detector interface {
+ DetectLocale() string
+ DetectLanguage() string
+}
+
+type JibberJabberDetector struct{}
+
+func (d *JibberJabberDetector) DetectLocale() string {
+ userLocale, err := jibber_jabber.DetectIETF()
+ if err != nil {
+ userLocale = ""
+ }
+ return userLocale
+}
+
+func (d *JibberJabberDetector) DetectLanguage() string {
+ lang, err := jibber_jabber.DetectLanguage()
+ if err != nil {
+ lang = ""
+ }
+ return lang
+}
diff --git a/tools/cli/go-whisk-cli/wski18n/i18n.go b/tools/cli/go-whisk-cli/wski18n/i18n.go
new file mode 100644
index 0000000..3c68b5f
--- /dev/null
+++ b/tools/cli/go-whisk-cli/wski18n/i18n.go
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package wski18n
+
+import (
+ "path/filepath"
+ "strings"
+
+ goi18n "github.com/nicksnyder/go-i18n/i18n"
+)
+
+const (
+ DEFAULT_LOCALE = "en_US"
+)
+
+var SUPPORTED_LOCALES = []string{
+ "de_DE",
+ "en_US",
+ "es_ES",
+ "fr_FR",
+ "it_IT",
+ "ja_JA",
+ "ko_KR",
+ "pt_BR",
+ "zh_Hans",
+ "zh_Hant",
+}
+
+var resourcePath = filepath.Join("wski18n", "resources")
+
+func GetResourcePath() string {
+ return resourcePath
+}
+
+func SetResourcePath(path string) {
+ resourcePath = path
+}
+
+var T goi18n.TranslateFunc
+var curLocale string
+
+func init() {
+ curLocale = Init(new(JibberJabberDetector))
+}
+
+func CurLocale() string {
+ return curLocale
+}
+
+func Locale(detector Detector) string {
+
+ // Use default locale until strings are translated
+ /*sysLocale := normalize(detector.DetectLocale())
+ if isSupported(sysLocale) {
+ return sysLocale
+ }
+
+ locale := defaultLocaleForLang(detector.DetectLanguage())
+ if locale != "" {
+ return locale
+ }*/
+
+ return DEFAULT_LOCALE
+}
+
+func Init(detector Detector) string {
+ l := Locale(detector)
+ InitWithLocale(l)
+ return l
+}
+
+func InitWithLocale(locale string) {
+ err := loadFromAsset(locale)
+ if err != nil {
+ panic(err)
+ }
+ T = goi18n.MustTfunc(locale)
+}
+
+func loadFromAsset(locale string) (err error) {
+ assetName := locale + ".all.json"
+ assetKey := filepath.Join(resourcePath, assetName)
+ bytes, err := Asset(assetKey)
+ if err != nil {
+ return
+ }
+ err = goi18n.ParseTranslationFileBytes(assetName, bytes)
+ return
+}
+
+func normalize(locale string) string {
+ locale = strings.ToLower(strings.Replace(locale, "-", "_", 1))
+ for _, l := range SUPPORTED_LOCALES {
+ if strings.EqualFold(locale, l) {
+ return l
+ }
+ }
+ switch locale {
+ case "zh_cn", "zh_sg":
+ return "zh_Hans"
+ case "zh_hk", "zh_tw":
+ return "zh_Hant"
+ }
+ return locale
+}
+
+func isSupported(locale string) bool {
+ for _, l := range SUPPORTED_LOCALES {
+ if strings.EqualFold(locale, l) {
+ return true
+ }
+ }
+ return false
+}
+
+func defaultLocaleForLang(lang string) string {
+ if lang != "" {
+ lang = strings.ToLower(lang)
+ for _, l := range SUPPORTED_LOCALES {
+ if lang == LangOfLocale(l) {
+ return l
+ }
+ }
+ }
+ return ""
+}
+
+func LangOfLocale(locale string) string {
+ if len(locale) < 2 {
+ return ""
+ }
+ return locale[0:2]
+}
diff --git a/tools/cli/go-whisk-cli/wski18n/resources/de_DE.all.json b/tools/cli/go-whisk-cli/wski18n/resources/de_DE.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk-cli/wski18n/resources/en_US.all.json b/tools/cli/go-whisk-cli/wski18n/resources/en_US.all.json
new file mode 100644
index 0000000..e173b42
--- /dev/null
+++ b/tools/cli/go-whisk-cli/wski18n/resources/en_US.all.json
@@ -0,0 +1,1466 @@
+[
+ {
+ "id": "Application exited unexpectedly",
+ "translation": "Application exited unexpectedly"
+ },
+ {
+ "id": "Run '{{.Name}} --help' for usage.\n",
+ "translation": "Run '{{.Name}} --help' for usage.\n"
+ },
+ {
+ "id": "error: ",
+ "translation": "error: "
+ },
+ {
+ "id": "not set",
+ "translation": "not set"
+ },
+ {
+ "id": "OpenWhisk cloud computing command line interface.",
+ "translation": "OpenWhisk cloud computing command line interface."
+ },
+ {
+ "id": "verbose output",
+ "translation": "verbose output"
+ },
+ {
+ "id": "debug level output",
+ "translation": "debug level output"
+ },
+ {
+ "id": "authorization `KEY`",
+ "translation": "authorization `KEY`"
+ },
+ {
+ "id": "whisk API `HOST`",
+ "translation": "whisk API `HOST`"
+ },
+ {
+ "id": "whisk API `VERSION`",
+ "translation": "whisk API `VERSION`"
+ },
+ {
+ "id": "bypass certificate checking",
+ "translation": "bypass certificate checking"
+ },
+ {
+ "id": "Unable to initialize server connection: {{.err}}",
+ "translation": "Unable to initialize server connection: {{.err}}"
+ },
+ {
+ "id": "Parameter arguments must be a key value pair: {{.args}}",
+ "translation": "Parameter arguments must be a key value pair: {{.args}}"
+ },
+ {
+ "id": "Annotation arguments must be a key value pair: {{.args}}",
+ "translation": "Annotation arguments must be a key value pair: {{.args}}"
+ },
+ {
+ "id": "Failed to parse arguments: {{.err}}",
+ "translation": "Failed to parse arguments: {{.err}}"
+ },
+ {
+ "id": "work with namespaces",
+ "translation": "work with namespaces"
+ },
+ {
+ "id": "list entities in the current namespace",
+ "translation": "list entities in the current namespace"
+ },
+ {
+ "id": "Unable to obtain the list of available namespaces: {{.err}}",
+ "translation": "Unable to obtain the list of available namespaces: {{.err}}"
+ },
+ {
+ "id": "get triggers, actions, and rules in the registry for a namespace",
+ "translation": "get triggers, actions, and rules in the registry for a namespace"
+ },
+ {
+ "id": "'{{.name}}' is not a valid qualified name: {{.err}}",
+ "translation": "'{{.name}}' is not a valid qualified name: {{.err}}"
+ },
+ {
+ "id": "Unable to obtain the list of entities for namespace '{{.namespace}}': {{.err}}",
+ "translation": "Unable to obtain the list of entities for namespace '{{.namespace}}': {{.err}}"
+ },
+ {
+ "id": "Entities in namespace: {{.namespace}}\n",
+ "translation": "Entities in namespace: {{.namespace}}\n"
+ },
+ {
+ "id": "list available namespaces",
+ "translation": "list available namespaces"
+ },
+ {
+ "id": "work with packages",
+ "translation": "work with packages"
+ },
+ {
+ "id": "bind parameters to a package",
+ "translation": "bind parameters to a package"
+ },
+ {
+ "id": "Invalid parameter argument '{{.param}}': {{.err}}",
+ "translation": "Invalid parameter argument '{{.param}}': {{.err}}"
+ },
+ {
+ "id": "Invalid annotation argument '{{.annotation}}': {{.err}}",
+ "translation": "Invalid annotation argument '{{.annotation}}': {{.err}}"
+ },
+ {
+ "id": "Binding creation failed: {{.err}}",
+ "translation": "Binding creation failed: {{.err}}"
+ },
+ {
+ "id": "{{.ok}} created binding {{.name}}\n",
+ "translation": "{{.ok}} created binding {{.name}}\n"
+ },
+ {
+ "id": "create a new package",
+ "translation": "create a new package"
+ },
+ {
+ "id": "Unable to create package '{{.name}}': {{.err}}",
+ "translation": "Unable to create package '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "{{.ok}} created package {{.name}}\n",
+ "translation": "{{.ok}} created package {{.name}}\n"
+ },
+ {
+ "id": "update an existing package, or create a package if it does not exist",
+ "translation": "update an existing package, or create a package if it does not exist"
+ },
+ {
+ "id": "Package update failed: {{.err}}",
+ "translation": "Package update failed: {{.err}}"
+ },
+ {
+ "id": "{{.ok}} updated package {{.name}}\n",
+ "translation": "{{.ok}} updated package {{.name}}\n"
+ },
+ {
+ "id": "get package",
+ "translation": "get package"
+ },
+ {
+ "id": "Unable to get package '{{.name}}': {{.err}}",
+ "translation": "Unable to get package '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "{{.ok}} got package {{.name}}\n",
+ "translation": "{{.ok}} got package {{.name}}\n"
+ },
+ {
+ "id": "delete package",
+ "translation": "delete package"
+ },
+ {
+ "id": "Unable to delete package '{{.name}}': {{.err}}",
+ "translation": "Unable to delete package '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "{{.ok}} deleted package {{.name}}\n",
+ "translation": "{{.ok}} deleted package {{.name}}\n"
+ },
+ {
+ "id": "list all packages",
+ "translation": "list all packages"
+ },
+ {
+ "id": "No valid namespace detected. Run 'wsk property set --namespace' or ensure the name argument is preceded by a \"/\"",
+ "translation": "No valid namespace detected. Run 'wsk property set --namespace' or ensure the name argument is preceded by a \"/\""
+ },
+ {
+ "id": "Unable to obtain the list of packages for namespace '{{.name}}': {{.err}}",
+ "translation": "Unable to obtain the list of packages for namespace '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "refresh package bindings",
+ "translation": "refresh package bindings"
+ },
+ {
+ "id": "Package refresh for namespace '{{.name}}' failed: {{.err}}",
+ "translation": "Package refresh for namespace '{{.name}}' failed: {{.err}}"
+ },
+ {
+ "id": "{{.name}} refreshed successfully\n",
+ "translation": "{{.name}} refreshed successfully\n"
+ },
+ {
+ "id": "created bindings:",
+ "translation": "created bindings:"
+ },
+ {
+ "id": "updated bindings:",
+ "translation": "updated bindings:"
+ },
+ {
+ "id": "deleted bindings:",
+ "translation": "deleted bindings:"
+ },
+ {
+ "id": "The package refresh feature is not implemented in the target deployment",
+ "translation": "The package refresh feature is not implemented in the target deployment"
+ },
+ {
+ "id": "Package refresh for namespace '{{.name}}' failed due to unexpected HTTP status code: {{.code}}",
+ "translation": "Package refresh for namespace '{{.name}}' failed due to unexpected HTTP status code: {{.code}}"
+ },
+ {
+ "id": "annotation values in `KEY VALUE` format",
+ "translation": "annotation values in `KEY VALUE` format"
+ },
+ {
+ "id": "`FILE` containing annotation values in JSON format",
+ "translation": "`FILE` containing annotation values in JSON format"
+ },
+ {
+ "note-to-translators": "DO NOT TRANSLATE THE 'yes' AND 'no'. THOSE ARE FIXED CLI ARGUMENT VALUES",
+ "id": "package visibility `SCOPE`; yes = shared, no = private",
+ "translation": "package visibility `SCOPE`; yes = shared, no = private"
+ },
+ {
+ "id": "summarize package details",
+ "translation": "summarize package details"
+ },
+ {
+ "id": "include publicly shared entities in the result",
+ "translation": "include publicly shared entities in the result"
+ },
+ {
+ "id": "exclude the first `SKIP` number of packages from the result",
+ "translation": "exclude the first `SKIP` number of packages from the result"
+ },
+ {
+ "id": "only return `LIMIT` number of packages from the collection",
+ "translation": "only return `LIMIT` number of packages from the collection"
+ },
+ {
+ "id": "property",
+ "translation": "property"
+ },
+ {
+ "id": "work with whisk properties",
+ "translation": "work with whisk properties"
+ },
+ {
+ "id": "set property",
+ "translation": "set property"
+ },
+ {
+ "id": "Unable to set the property value: {{.err}}",
+ "translation": "Unable to set the property value: {{.err}}"
+ },
+ {
+ "id": "{{.ok}} whisk auth set. Run 'wsk property get --auth' to see the new value.\n",
+ "translation": "{{.ok}} whisk auth set. Run 'wsk property get --auth' to see the new value.\n"
+ },
+ {
+ "id": "Unable to set API host value; the API host value '{{.apihost}}' is invalid: {{.err}}",
+ "translation": "Unable to set API host value; the API host value '{{.apihost}}' is invalid: {{.err}}"
+ },
+ {
+ "id": "{{.ok}} whisk API host set to {{.host}}\n",
+ "translation": "{{.ok}} whisk API host set to {{.host}}\n"
+ },
+ {
+ "id": "{{.ok}} whisk API version set to {{.version}}\n",
+ "translation": "{{.ok}} whisk API version set to {{.version}}\n"
+ },
+ {
+ "id": "Authenticated user does not have namespace '{{.name}}'; set command failed: {{.err}}",
+ "translation": "Authenticated user does not have namespace '{{.name}}'; set command failed: {{.err}}"
+ },
+ {
+ "id": "Namespace '{{.name}}' is not in the list of entitled namespaces",
+ "translation": "Namespace '{{.name}}' is not in the list of entitled namespaces"
+ },
+ {
+ "id": "{{.ok}} whisk namespace set to {{.name}}\n",
+ "translation": "{{.ok}} whisk namespace set to {{.name}}\n"
+ },
+ {
+ "id": "Unable to set the property value(s): {{.err}}",
+ "translation": "Unable to set the property value(s): {{.err}}"
+ },
+ {
+ "id": "unset property",
+ "translation": "unset property"
+ },
+ {
+ "id": "Unable to unset the property value: {{.err}}",
+ "translation": "Unable to unset the property value: {{.err}}"
+ },
+ {
+ "id": "{{.ok}} whisk auth unset.\n",
+ "translation": "{{.ok}} whisk auth unset.\n"
+ },
+ {
+ "id": "{{.ok}} whisk namespace unset",
+ "translation": "{{.ok}} whisk namespace unset"
+ },
+ {
+ "id": "{{.ok}} whisk API host unset.\n",
+ "translation": "{{.ok}} whisk API host unset.\n"
+ },
+ {
+ "id": "{{.ok}} whisk API version unset",
+ "translation": "{{.ok}} whisk API version unset"
+ },
+ {
+ "id": "; the default value of {{.default}} will be used.\n",
+ "translation": "; the default value of {{.default}} will be used.\n"
+ },
+ {
+ "id": "; there is no default value that can be used.\n",
+ "translation": "; there is no default value that can be used.\n"
+ },
+ {
+ "id": "get property",
+ "translation": "get property"
+ },
+ {
+ "id": "whisk auth",
+ "translation": "whisk auth"
+ },
+ {
+ "id": "whisk API host",
+ "translation": "whisk API host"
+ },
+ {
+ "id": "whisk API version",
+ "translation": "whisk API version"
+ },
+ {
+ "id": "whisk namespace",
+ "translation": "whisk namespace"
+ },
+ {
+ "id": "whisk CLI version",
+ "translation": "whisk CLI version"
+ },
+ {
+ "id": "Unknown",
+ "translation": "Unknown"
+ },
+ {
+ "id": "whisk API build",
+ "translation": "whisk API build"
+ },
+ {
+ "id": "whisk API build number",
+ "translation": "whisk API build number"
+ },
+ {
+ "id": "Unable to obtain API build information: {{.err}}",
+ "translation": "Unable to obtain API build information: {{.err}}"
+ },
+ {
+ "id": "authorization key",
+ "translation": "authorization key"
+ },
+ {
+ "id": "whisk API build version",
+ "translation": "whisk API build version"
+ },
+ {
+ "id": "all properties",
+ "translation": "all properties"
+ },
+ {
+ "id": "whisk `NAMESPACE`",
+ "translation": "whisk `NAMESPACE`"
+ },
+ {
+ "id": "Unable to locate properties file '{{.filename}}': {{.err}}",
+ "translation": "Unable to locate properties file '{{.filename}}': {{.err}}"
+ },
+ {
+ "id": "Unable to read the properties file '{{.filename}}': {{.err}}",
+ "translation": "Unable to read the properties file '{{.filename}}': {{.err}}"
+ },
+ {
+ "id": "Invalid host address '{{.host}}': {{.err}}",
+ "translation": "Invalid host address '{{.host}}': {{.err}}"
+ },
+ {
+ "id": "Whisk properties file write failure: {{.err}}",
+ "translation": "Whisk properties file write failure: {{.err}}"
+ },
+ {
+ "id": "work with rules",
+ "translation": "work with rules"
+ },
+ {
+ "id": "enable rule",
+ "translation": "enable rule"
+ },
+ {
+ "id": "Unable to enable rule '{{.name}}': {{.err}}",
+ "translation": "Unable to enable rule '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "{{.ok}} enabled rule {{.name}}\n",
+ "translation": "{{.ok}} enabled rule {{.name}}\n"
+ },
+ {
+ "id": "disable rule",
+ "translation": "disable rule"
+ },
+ {
+ "id": "Unable to disable rule '{{.name}}': {{.err}}",
+ "translation": "Unable to disable rule '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "{{.ok}} disabled rule {{.name}}\n",
+ "translation": "{{.ok}} disabled rule {{.name}}\n"
+ },
+ {
+ "id": "get rule status",
+ "translation": "get rule status"
+ },
+ {
+ "id": "Unable to get rule '{{.name}}': {{.err}}",
+ "translation": "Unable to get rule '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "Unable to get status of rule '{{.name}}': {{.err}}",
+ "translation": "Unable to get status of rule '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "{{.ok}} rule {{.name}} is {{.status}}\n",
+ "translation": "{{.ok}} rule {{.name}} is {{.status}}\n"
+ },
+ {
+ "id": "create new rule",
+ "translation": "create new rule"
+ },
+ {
+ "id": "Unable to create rule '{{.name}}': {{.err}}",
+ "translation": "Unable to create rule '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "{{.ok}} created rule {{.name}}\n",
+ "translation": "{{.ok}} created rule {{.name}}\n"
+ },
+ {
+ "id": "update an existing rule, or create a rule if it does not exist",
+ "translation": "update an existing rule, or create a rule if it does not exist"
+ },
+ {
+ "id": "Unable to update rule '{{.name}}': {{.err}}",
+ "translation": "Unable to update rule '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "{{.ok}} updated rule {{.name}}\n",
+ "translation": "{{.ok}} updated rule {{.name}}\n"
+ },
+ {
+ "id": "get rule",
+ "translation": "get rule"
+ },
+ {
+ "id": "Unable to retrieve rule '{{.name}}': {{.err}}",
+ "translation": "Unable to retrieve rule '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "{{.ok}} got rule {{.name}}\n",
+ "translation": "{{.ok}} got rule {{.name}}\n"
+ },
+ {
+ "id": "delete rule",
+ "translation": "delete rule"
+ },
+ {
+ "id": "Unable to delete rule '{{.name}}': {{.err}}",
+ "translation": "Unable to delete rule '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "{{.ok}} deleted rule {{.name}}\n",
+ "translation": "{{.ok}} deleted rule {{.name}}\n"
+ },
+ {
+ "id": "list all rules",
+ "translation": "list all rules"
+ },
+ {
+ "id": "Namespace '{{.name}}' is invalid: {{.err}}\n",
+ "translation": "Namespace '{{.name}}' is invalid: {{.err}}\n"
+ },
+ {
+ "id": "Unable to obtain the list of rules for namespace '{{.name}}': {{.err}}",
+ "translation": "Unable to obtain the list of rules for namespace '{{.name}}': {{.err}}"
+ },
+ {
+ "note-to-translators": "DO NOT TRANSLATE THE 'yes' AND 'no'. THOSE ARE FIXED CLI ARGUMENT VALUES",
+ "id": "rule visibility `SCOPE`; yes = shared, no = private",
+ "translation": "rule visibility `SCOPE`; yes = shared, no = private"
+ },
+ {
+ "id": "automatically enable rule after creating it",
+ "translation": "automatically enable rule after creating it"
+ },
+ {
+ "id": "automatically disable rule before deleting it",
+ "translation": "automatically disable rule before deleting it"
+ },
+ {
+ "id": "summarize rule details",
+ "translation": "summarize rule details"
+ },
+ {
+ "id": "work with the sdk",
+ "translation": "work with the sdk"
+ },
+ {
+ "id": "install SDK artifacts",
+ "translation": "install SDK artifacts"
+ },
+ {
+ "id": "install SDK artifacts, where valid COMPONENT values are docker, ios, and bashauto",
+ "translation": "install SDK artifacts, where valid COMPONENT values are docker, iOS, and bashauto"
+ },
+ {
+ "id": "The SDK component argument is missing. One component (docker, ios, or bashauto) must be specified",
+ "translation": "The SDK component argument is missing. One component (docker, ios, or bashauto) must be specified"
+ },
+ {
+ "id": "Unable to generate '{{.name}}': {{.err}}",
+ "translation": "Unable to generate '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "bash_completion_msg",
+ "translation": "The bash auto-completion script ({{.name}}) is installed in the current directory.\nTo enable command line completion of wsk commands, source the auto completion script into your bash environment\n"
+ },
+ {
+ "id": "The SDK component argument '{{.component}}' is invalid. Valid components are docker, ios and bashauto",
+ "translation": "The SDK component argument '{{.component}}' is invalid. Valid components are docker, ios and bashauto"
+ },
+ {
+ "id": "The file {{.name}} already exists. Delete it and retry.",
+ "translation": "The file {{.name}} already exists. Delete it and retry."
+ },
+ {
+ "id": "The {{.component}} SDK installation failed: {{.err}}",
+ "translation": "The {{.component}} SDK installation failed: {{.err}}"
+ },
+ {
+ "id": "The docker skeleton is now installed at the current directory.",
+ "translation": "The docker skeleton is now installed at the current directory."
+ },
+ {
+ "id": "Downloaded OpenWhisk iOS starter app. Unzip {{.name}} and open the project in Xcode.\n",
+ "translation": "Downloaded OpenWhisk iOS starter app. Unzip {{.name}} and open the project in Xcode.\n"
+ },
+ {
+ "id": "Unable to retrieve '{{.urlpath}}' SDK: {{.err}}",
+ "translation": "Unable to retrieve '{{.urlpath}}' SDK: {{.err}}"
+ },
+ {
+ "id": "Server failed to send the '{{.component}}' SDK: {{.err}}",
+ "translation": "Server failed to send the '{{.component}}' SDK: {{.err}}"
+ },
+ {
+ "id": "Error creating SDK file {{.name}}: {{.err}}",
+ "translation": "Error creating SDK file {{.name}}: {{.err}}"
+ },
+ {
+ "id": "Error copying server response into file: {{.err}}",
+ "translation": "Error copying server response into file: {{.err}}"
+ },
+ {
+ "id": "The directory {{.name}} already exists. Delete it and retry.",
+ "translation": "The directory {{.name}} already exists. Delete it and retry."
+ },
+ {
+ "id": "Error unGzipping file {{.name}}: {{.err}}",
+ "translation": "Error unGzipping file {{.name}}: {{.err}}"
+ },
+ {
+ "id": "Error untarring file {{.name}}: {{.err}}",
+ "translation": "Error untarring file {{.name}}: {{.err}}"
+ },
+ {
+ "id": "work with triggers",
+ "translation": "work with triggers"
+ },
+ {
+ "id": "fire trigger event",
+ "translation": "fire trigger event"
+ },
+ {
+ "id": "Unable to fire trigger '{{.name}}': {{.err}}",
+ "translation": "Unable to fire trigger '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "{{.ok}} triggered /{{.namespace}}/{{.name}} with id {{.id}}\n",
+ "translation": "{{.ok}} triggered /{{.namespace}}/{{.name}} with id {{.id}}\n"
+ },
+ {
+ "id": "create new trigger",
+ "translation": "create new trigger"
+ },
+ {
+ "note-to-translators": "DO NOT TRANSLATE THE 'yes' AND 'no'. THOSE ARE FIXED CLI ARGUMENT VALUES",
+ "id": "Invalid --shared argument value '{{.argval}}'; valid values are 'yes' or 'no'",
+ "translation": "Invalid --shared argument value '{{.argval}}'; valid values are 'yes' or 'no'"
+ },
+ {
+ "id": "Unable to create trigger '{{.name}}': {{.err}}",
+ "translation": "Unable to create trigger '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "{{.ok}} created trigger {{.name}}\n",
+ "translation": "{{.ok}} created trigger {{.name}}\n"
+ },
+ {
+ "id": "update an existing trigger, or create a trigger if it does not exist",
+ "translation": "update an existing an trigger, or create a trigger if it does not exist"
+ },
+ {
+ "id": "Unable to update trigger '{{.name}}': {{.err}}",
+ "translation": "Unable to update trigger '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "{{.ok}} updated trigger {{.name}}\n",
+ "translation": "{{.ok}} updated trigger {{.name}}\n"
+ },
+ {
+ "id": "get trigger",
+ "translation": "get trigger"
+ },
+ {
+ "id": "Unable to get trigger '{{.name}}': {{.err}}",
+ "translation": "Unable to get trigger '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "{{.ok}} got trigger {{.name}}\n",
+ "translation": "{{.ok}} got trigger {{.name}}\n"
+ },
+ {
+ "id": "delete trigger",
+ "translation": "delete trigger"
+ },
+ {
+ "id": "Unable to delete trigger '{{.name}}': {{.err}}",
+ "translation": "Unable to delete trigger '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "{{.ok}} deleted trigger {{.name}}\n",
+ "translation": "{{.ok}} deleted trigger {{.name}}\n"
+ },
+ {
+ "id": "list all triggers",
+ "translation": "list all triggers"
+ },
+ {
+ "id": "Unable to obtain the list of triggers for namespace '{{.name}}': {{.err}}",
+ "translation": "Unable to obtain the list of triggers for namespace '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "Unable to invoke trigger '{{.trigname}}' feed action '{{.feedname}}'; feed is not configured: {{.err}}",
+ "translation": "Unable to invoke trigger '{{.trigname}}' feed action '{{.feedname}}'; feed is not configured: {{.err}}"
+ },
+ {
+ "note-to-translators": "DO NOT TRANSLATE THE 'yes' AND 'no'. THOSE ARE FIXED CLI ARGUMENT VALUES",
+ "id": "trigger visibility `SCOPE`; yes = shared, no = private",
+ "translation": "trigger visibility `SCOPE`; yes = shared, no = private"
+ },
+ {
+ "id": "trigger feed `ACTION_NAME`",
+ "translation": "trigger feed `ACTION_NAME`"
+ },
+ {
+ "id": "summarize trigger details",
+ "translation": "summarize trigger details"
+ },
+ {
+ "id": "parameter values in `KEY VALUE` format",
+ "translation": "parameter values in `KEY VALUE` format"
+ },
+ {
+ "id": "`FILE` containing parameter values in JSON format",
+ "translation": "`FILE` containing parameter values in JSON format"
+ },
+ {
+ "id": "Arguments must be comma separated, and must be quoted if they contain spaces.",
+ "translation": "Arguments must be comma separated, and must be quoted if they contain spaces."
+ },
+ {
+ "id": "Invalid arguments: {{.err}}",
+ "translation": "Invalid arguments: {{.err}}"
+ },
+ {
+ "id": "The argument `{{.arg}}` is invalid: {{.err}}",
+ "translation": "The argument `{{.arg}}` is invalid: {{.err}}"
+ },
+ {
+ "id": "The argument `{{.arg}}` is invalid JSON: {{.err}}",
+ "translation": "The argument `{{.arg}}` is invalid JSON: {{.err}}"
+ },
+ {
+ "id": "private",
+ "translation": "private"
+ },
+ {
+ "id": "shared",
+ "translation": "shared"
+ },
+ {
+ "id": "The file '{{.name}}' does not exist.",
+ "translation": "The file '{{.name}}' does not exist."
+ },
+ {
+ "id": "Error creating unGzip file '{{.name}}': {{.err}}",
+ "translation": "Error creating unGzip file '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "Error opening Gzip file '{{.name}}': {{.err}}",
+ "translation": "Error opening Gzip file '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "Unable to unzip file '{{.name}}': {{.err}}",
+ "translation": "Unable to unzip file '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "Unable to opens '{{.name}}' for unzipping: {{.err}}",
+ "translation": "Unable to opens '{{.name}}' for unzipping: {{.err}}"
+ },
+ {
+ "id": "Unable to create directory '{{.dir}}' while unzipping '{{.name}}': {{.err}}",
+ "translation": "Unable to create directory '{{.dir}}' while unzipping '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "Unable to open zipped file '{{.file}}' while unzipping '{{.name}}': {{.err}}",
+ "translation": "Unable to open zipped file '{{.file}}' while unzipping '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "Unable to create file '{{.file}}' while unzipping '{{.name}}': {{.err}}",
+ "translation": "Unable to create file '{{.file}}' while unzipping '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "Error opening tar file '{{.name}}': {{.err}}",
+ "translation": "Error opening tar file '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "Error reading tar file '{{.name}}': {{.err}}",
+ "translation": "Error reading tar file '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "Unable to create directory '{{.dir}}' while untarring '{{.name}}': {{.err}}",
+ "translation": "Unable to create directory '{{.dir}}' while untarring '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "Unable to create file '{{.file}}' while untarring '{{.name}}': {{.err}}",
+ "translation": "Unable to create file '{{.file}}' while untarring '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "Unable to untar file '{{.name}}': {{.err}}",
+ "translation": "Unable to untar file '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "Unable to untar '{{.name}}' due to unexpected tar file type\n",
+ "translation": "Unable to untar '{{.name}}' due to unexpected tar file type\n"
+ },
+ {
+ "id": "work with actions",
+ "translation": "work with actions"
+ },
+ {
+ "id": "create a new action",
+ "translation": "create a new action"
+ },
+ {
+ "id": "Unable to parse action command arguments: {{.err}}",
+ "translation": "Unable to parse action command arguments: {{.err}}"
+ },
+ {
+ "id": "Unable to create action '{{.name}}': {{.err}}",
+ "translation": "Unable to create action '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "{{.ok}} created action {{.name}}\n",
+ "translation": "{{.ok}} created action {{.name}}\n"
+ },
+ {
+ "id": "update an existing action, or create an action if it does not exist",
+ "translation": "update an existing action, or create an action if it does not exist"
+ },
+ {
+ "id": "Unable to update action: {{.err}}",
+ "translation": "Unable to update action: {{.err}}"
+ },
+ {
+ "id": "{{.ok}} updated action {{.name}}\n",
+ "translation": "{{.ok}} updated action {{.name}}\n"
+ },
+ {
+ "id": "invoke action",
+ "translation": "invoke action"
+ },
+ {
+ "id": "Unable to invoke action '{{.name}}': {{.err}}",
+ "translation": "Unable to invoke action '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "{{.ok}} invoked /{{.namespace}}/{{.name}} with id {{.id}}\n",
+ "translation": "{{.ok}} invoked /{{.namespace}}/{{.name}} with id {{.id}}\n"
+ },
+ {
+ "id": "get action",
+ "translation": "get action"
+ },
+ {
+ "id": "Unable to get action: {{.err}}",
+ "translation": "Unable to get action: {{.err}}"
+ },
+ {
+ "id": "action",
+ "translation": "action"
+ },
+ {
+ "id": "{{.ok}} got action {{.name}}\n",
+ "translation": "{{.ok}} got action {{.name}}\n"
+ },
+ {
+ "id": "delete action",
+ "translation": "delete action"
+ },
+ {
+ "id": "Unable to delete action '{{.name}}': {{.err}}",
+ "translation": "Unable to delete action '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "{{.ok}} deleted action {{.name}}\n",
+ "translation": "{{.ok}} deleted action {{.name}}\n"
+ },
+ {
+ "id": "list all actions",
+ "translation": "list all actions"
+ },
+ {
+ "id": "Unable to obtain the list of actions for namespace '{{.name}}': {{.err}}",
+ "translation": "Unable to obtain the list of actions for namespace '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "Could not find 'main' method in '{{.name}}'",
+ "translation": "Could not find 'main' method in '{{.name}}'"
+ },
+ {
+ "id": "Unable to get action '{{.name}}': {{.err}}",
+ "translation": "Unable to get action '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "File '{{.name}}' is not a valid file or it does not exist: {{.err}}",
+ "translation": "File '{{.name}}' is not a valid file or it does not exist: {{.err}}"
+ },
+ {
+ "id": "Unable to read '{{.name}}': {{.err}}",
+ "translation": "Unable to read '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "'{{.name}}' is not a supported action runtime",
+ "translation": "'{{.name}}' is not a supported action runtime"
+ },
+ {
+ "id": "creating an action from a .zip artifact requires specifying the action kind explicitly",
+ "translation": "creating an action from a .zip artifact requires specifying the action kind explicitly"
+ },
+ {
+ "id": "Java actions require --main to specify the fully-qualified name of the main class",
+ "translation": "Java actions require --main to specify the fully-qualified name of the main class"
+ },
+ {
+ "id": "treat ACTION as docker image path on dockerhub",
+ "translation": "treat ACTION as docker image path on dockerhub"
+ },
+ {
+ "id": "treat ACTION as the name of an existing action",
+ "translation": "treat ACTION as the name of an existing action"
+ },
+ {
+ "id": "treat ACTION as comma separated sequence of actions to invoke",
+ "translation": "treat ACTION as comma separated sequence of actions to invoke"
+ },
+ {
+ "id": "the `KIND` of the action runtime (example: swift:default, nodejs:default)",
+ "translation": "the `KIND` of the action runtime (example: swift:default, nodejs:default)"
+ },
+ {
+ "id": "the name of the action entry point (function or fully-qualified method name when applicable)",
+ "translation": "the name of the action entry point (function or fully-qualified method name when applicable)"
+ },
+ {
+ "id": "action visibility `SCOPE`; yes = shared, no = private",
+ "translation": "action visibility `SCOPE`; yes = shared, no = private"
+ },
+ {
+ "id": "the timeout `LIMIT` in milliseconds after which the action is terminated",
+ "translation": "the timeout `LIMIT` in milliseconds after which the action is terminated"
+ },
+ {
+ "id": "the maximum memory `LIMIT` in MB for the action",
+ "translation": "the maximum memory `LIMIT` in MB for the action"
+ },
+ {
+ "id": "the maximum log size `LIMIT` in MB for the action",
+ "translation": "the maximum log size `LIMIT` in MB for the action"
+ },
+ {
+ "id": "blocking invoke",
+ "translation": "blocking invoke"
+ },
+ {
+ "id": "blocking invoke; show only activation result (unless there is a failure)",
+ "translation": "blocking invoke; show only activation result (unless there is a failure)"
+ },
+ {
+ "id": "exclude the first `SKIP` number of actions from the result",
+ "translation": "exclude the first `SKIP` number of actions from the result"
+ },
+ {
+ "id": "only return `LIMIT` number of actions from the collection",
+ "translation": "only return `LIMIT` number of actions from the collection"
+ },
+ {
+ "id": "summarize action details",
+ "translation": "summarize action details"
+ },
+ {
+ "id": "work with activations",
+ "translation": "work with activations"
+ },
+ {
+ "id": "list activations",
+ "translation": "list activations"
+ },
+ {
+ "id": "Namespace '{{.name}}' is invalid",
+ "translation": "Namespace '{{.name}}' is invalid"
+ },
+ {
+ "id": "Unable to obtain the list of activations for namespace '{{.name}}': {{.err}}",
+ "translation": "Unable to obtain the list of activations for namespace '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "get activation",
+ "translation": "get activation"
+ },
+ {
+ "id": "Unable to get activation '{{.id}}': {{.err}}",
+ "translation": "Unable to get activation '{{.id}}': {{.err}}"
+ },
+ {
+ "id": "activation result for /{{.namespace}}/{{.name}} ({{.status}} at {{.time}})\n",
+ "translation": "activation result for /{{.namespace}}/{{.name}} ({{.status}} at {{.time}})\n"
+ },
+ {
+ "id": "{{.ok}} got activation {{.id}}\n",
+ "translation": "{{.ok}} got activation {{.id}}\n"
+ },
+ {
+ "id": "get the logs of an activation",
+ "translation": "get the logs of an activation"
+ },
+ {
+ "id": "Unable to get logs for activation '{{.id}}': {{.err}}",
+ "translation": "Unable to get logs for activation '{{.id}}': {{.err}}"
+ },
+ {
+ "id": "Unable to get result for activation '{{.id}}': {{.err}}",
+ "translation": "Unable to get result for activation '{{.id}}': {{.err}}"
+ },
+ {
+ "id": "poll continuously for log messages from currently running actions",
+ "translation": "poll continuously for log messages from currently running actions"
+ },
+ {
+ "id": "Poll terminated",
+ "translation": "Poll terminated"
+ },
+ {
+ "id": "Enter Ctrl-c to exit.",
+ "translation": "Enter Ctrl-c to exit."
+ },
+ {
+ "id": "Polling for activation logs\n",
+ "translation": "Polling for activation logs\n"
+ },
+ {
+ "id": "\nActivation: {{.name}} ({{.id}})\n",
+ "translation": "\nActivation: {{.name}} ({{.id}})\n"
+ },
+ {
+ "id": "exclude the first `SKIP` number of activations from the result",
+ "translation": "exclude the first `SKIP` number of activations from the result"
+ },
+ {
+ "id": "only return `LIMIT` number of activations from the collection",
+ "translation": "only return `LIMIT` number of activations from the collection"
+ },
+ {
+ "id": "include full activation description",
+ "translation": "include full activation description"
+ },
+ {
+ "id": "return activations with timestamps earlier than `UPTO`; measured in milliseconds since Th, 01, Jan 1970",
+ "translation": "return activations with timestamps earlier than `UPTO`; measured in milliseconds since Th, 01, Jan 1970"
+ },
+ {
+ "id": "return activations with timestamps later than `SINCE`; measured in milliseconds since Th, 01, Jan 1970",
+ "translation": "return activations with timestamps later than `SINCE`; measured in milliseconds since Th, 01, Jan 1970"
+ },
+ {
+ "id": "summarize activation details",
+ "translation": "summarize activation details"
+ },
+ {
+ "id": "stop polling after `SECONDS` seconds",
+ "translation": "stop polling after `SECONDS` seconds"
+ },
+ {
+ "id": "start polling for activations `SECONDS` seconds ago",
+ "translation": "start polling for activations `SECONDS` seconds ago"
+ },
+ {
+ "id": "start polling for activations `MINUTES` minutes ago",
+ "translation": "start polling for activations `MINUTES` minutes ago"
+ },
+ {
+ "id": "start polling for activations `HOURS` hours ago",
+ "translation": "start polling for activations `HOURS` hours ago"
+ },
+ {
+ "id": "start polling for activations `DAYS` days ago",
+ "translation": "start polling for activations `DAYS` days ago"
+ },
+ {
+ "id": "Arguments for '{{.arg}}' must be a key/value pair",
+ "translation": "Arguments for '{{.arg}}' must be a key/value pair"
+ },
+ {
+ "id": "The parameter arguments are invalid: {{.err}}",
+ "translation": "The parameter arguments are invalid: {{.err}}"
+ },
+ {
+ "id": "The annotation arguments are invalid: {{.err}}",
+ "translation": "The annotation arguments are invalid: {{.err}}"
+ },
+ {
+ "id": "An action name and action are required.",
+ "translation": "An action name and action are required."
+ },
+ {
+ "id": "An action name is required.",
+ "translation": "An action name is required."
+ },
+ {
+ "id": "An action name is required. An action is optional.",
+ "translation": "An action name is required. An action is optional."
+ },
+ {
+ "id": "An activation ID is required.",
+ "translation": "An activation ID is required."
+ },
+ {
+ "id": "A package name and binding name are required.",
+ "translation": "A package name and binding name are required."
+ },
+ {
+ "id": "A package name is required.",
+ "translation": "A package name is required."
+ },
+ {
+ "id": "A rule name is required.",
+ "translation": "A rule name is required."
+ },
+ {
+ "id": "A rule, trigger and action name are required.",
+ "translation": "A rule, trigger and action name are required."
+ },
+ {
+ "id": "A trigger name is required.",
+ "translation": "A trigger name is required."
+ },
+ {
+ "id": "A trigger name is required. A payload is optional.",
+ "translation": "A trigger name is required. A payload is optional."
+ },
+ {
+ "id": "An optional namespace is the only valid argument.",
+ "translation": "An optional namespace is the only valid argument."
+ },
+ {
+ "id": "Invalid argument(s). {{.required}}",
+ "translation": "Invalid argument(s). {{.required}}"
+ },
+ {
+ "id": "Invalid argument(s): {{.args}}. {{.required}}",
+ "translation": "Invalid argument(s): {{.args}}. {{.required}}"
+ },
+ {
+ "id": "exactly",
+ "translation": "exactly"
+ },
+ {
+ "id": "at least",
+ "translation": "at least"
+ },
+ {
+ "id": "no more than",
+ "translation": "no more than"
+ },
+ {
+ "id": "No arguments are required.",
+ "translation": "No arguments are required."
+ },
+ {
+ "id": "status",
+ "translation": "status"
+ },
+ {
+ "id": "parameters",
+ "translation": "parameters"
+ },
+ {
+ "id": "default",
+ "translation": "default"
+ },
+ {
+ "id": "An argument must be provided for '{{.arg}}'",
+ "translation": "An argument must be provided for '{{.arg}}'"
+ },
+ {
+ "id": "The API host is not valid: {{.err}}",
+ "translation": "The API host is not valid: {{.err}}"
+ },
+ {
+ "id": "An API host must be provided.",
+ "translation": "An API host must be provided."
+ },
+ {
+ "id": "Invalid field filter '{{.arg}}'.",
+ "translation": "Invalid field filter '{{.arg}}'."
+ },
+ {
+ "id": "{{.ok}} got activation {{.id}}, displaying field {{.field}}\n",
+ "translation": "{{.ok}} got activation {{.id}}, displaying field {{.field}}\n"
+ },
+ {
+ "id": "{{.ok}} got action {{.name}}, displaying field {{.field}}\n",
+ "translation": "{{.ok}} got action {{.name}}, displaying field {{.field}}\n"
+ },
+ {
+ "id": "{{.ok}} got package {{.name}}, displaying field {{.field}}\n",
+ "translation": "{{.ok}} got package {{.name}}, displaying field {{.field}}\n"
+ },
+ {
+ "id": "{{.ok}} got rule {{.name}}, displaying field {{.field}}\n",
+ "translation": "{{.ok}} got rule {{.name}}, displaying field {{.field}}\n"
+ },
+ {
+ "id": "{{.ok}} got trigger {{.name}}, displaying field {{.field}}\n",
+ "translation": "{{.ok}} got trigger {{.name}}, displaying field {{.field}}\n"
+ },
+ {
+ "id": "An API path, an API verb, and an action name are required.",
+ "translation": "An API path, an API verb, and an action name are required."
+ },
+ {
+ "id": "An API path and an API verb are required.",
+ "translation": "An API path and an API verb are required."
+ },
+ {
+ "id":"'{{.name}}' is not a valid action name: {{.err}}",
+ "translation": "'{{.name}}' is not a valid action name: {{.err}}"
+ },
+ {
+ "id":"'{{.name}}' is not a valid action name.",
+ "translation": "'{{.name}}' is not a valid action name."
+ },
+ {
+ "id": "Unable to create API: {{.err}}",
+ "translation": "Unable to create API: {{.err}}"
+ },
+ {
+ "id": "{{.ok}} created API {{.path}} {{.verb}} for action {{.name}}\n{{.fullpath}}\n",
+ "translation": "{{.ok}} created API {{.path}} {{.verb}} for action {{.name}}\n{{.fullpath}}\n"
+ },
+ {
+ "id": "Unable to parse api command arguments: {{.err}}",
+ "translation": "Unable to parse api command arguments: {{.err}}"
+ },
+ {
+ "id": "create a new API",
+ "translation": "create a new API"
+ },
+ {
+ "id": "update an existing API",
+ "translation": "update an existing API"
+ },
+ {
+ "id": "Unable to update API: {{.err}}",
+ "translation": "Unable to update API: {{.err}}"
+ },
+ {
+ "id": "{{.ok}} updated API {{.path}} {{.verb}} for action {{.name}}\n{{.fullpath}}\n",
+ "translation": "{{.ok}} updated API {{.path}} {{.verb}} for action {{.name}}\n{{.fullpath}}\n"
+ },
+ {
+ "id": "get API",
+ "translation": "get API"
+ },
+ {
+ "id": "Unable to get API '{{.name}}': {{.err}}",
+ "translation": "Unable to get API '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "delete an API",
+ "translation": "delete an API"
+ },
+ {
+ "id": "Unable to delete API: {{.err}}",
+ "translation": "Unable to delete API: {{.err}}"
+ },
+ {
+ "id": "{{.ok}} deleted API {{.basepath}}\n",
+ "translation": "{{.ok}} deleted API {{.basepath}}\n"
+ },
+ {
+ "id": "{{.ok}} deleted {{.path}} from {{.basepath}}\n",
+ "translation": "{{.ok}} deleted {{.path}} from {{.basepath}}\n"
+ },
+ {
+ "id": "{{.ok}} deleted {{.path}} {{.verb}} from {{.basepath}}\n",
+ "translation": "{{.ok}} deleted {{.path}} {{.verb}} from {{.basepath}}\n"
+ },
+ {
+ "id": "list APIs",
+ "translation": "list APIs"
+ },
+ {
+ "id": "Unable to obtain the API list: {{.err}}",
+ "translation": "Unable to obtain the API list: {{.err}}"
+ },
+ {
+ "id": "'{{.verb}}' is not a valid API verb. Valid values are: {{.verbs}}",
+ "translation": "'{{.verb}}' is not a valid API verb. Valid values are: {{.verbs}}"
+ },
+ {
+ "id": "`ACTION` to invoke when API is called",
+ "translation": "`ACTION` to invoke when API is called"
+ },
+ {
+ "id": "relative `API_PATH` of API",
+ "translation": "relative `API_PATH` of API"
+ },
+ {
+ "id": "API `API_VERB`",
+ "translation": "API `API_VERB`"
+ },
+ {
+ "id": "API collection `NAME` (default NAMESPACE)",
+ "translation": "API collection `NAME` (default NAMESPACE)"
+ },
+ {
+ "id": "The API `BASE_PATH` to which the API_PATH is relative",
+ "translation": "The API `BASE_PATH` to which the API_PATH is relative"
+ },
+ {
+ "id": "{{.ok}} APIs\n",
+ "translation": "{{.ok}} APIs\n"
+ },
+ {
+ "id": "{{.url}} {{.operation}} {{.action}}\n",
+ "translation": "{{.url}} {{.operation}} {{.action}}\n"
+ },
+ {
+ "id": "An API base path is required. An optional API relative path and operation may also be provided.",
+ "translation": "An API base path is required. An optional API relative path and operation may also be provided."
+ },
+ {
+ "id": "'{{.path}}' must begin with '/'.",
+ "translation": "'{{.path}}' must begin with '/'."
+ },
+ {
+ "id": "Unable to parse swagger file: {{.err}}",
+ "translation": "Unable to parse swagger file: {{.err}}"
+ },
+ {
+ "id": "Error reading swagger file '{{.name}}': {{.err}}",
+ "translation": "Error reading swagger file '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "A configuration file was not specified.",
+ "translation": "A configuration file was not specified."
+ },
+ {
+ "id": "Error parsing swagger file '{{.name}}': {{.err}}",
+ "translation": "Error parsing swagger file '{{.name}}': {{.err}}"
+ },
+ {
+ "id": "Swagger file is invalid (missing basePath, info, paths, or swagger fields)",
+ "translation": "Swagger file is invalid (missing basePath, info, paths, or swagger fields)"
+ },
+ {
+ "id": "Swagger file basePath must start with a leading slash (/)",
+ "translation": "Swagger file basePath must start with a leading slash (/)"
+ },
+ {
+ "id": "API does not exist for basepath {{.basepath}}",
+ "translation": "API does not exist for basepath {{.basepath}}"
+ },
+ {
+ "id": "API does not exist for API name {{.apiname}}",
+ "translation": "API does not exist for API name {{.apiname}}"
+ },
+ {
+ "id": "An API name can only be specified once",
+ "translation": "An API name can only be specified once"
+ },
+ {
+ "id": "Specify a swagger file or specify an API base path with an API path, an API verb, and an action name.",
+ "translation": "Specify a swagger file or specify an API base path with an API path, an API verb, and an action name."
+ },
+ {
+ "id": "Invalid argument(s). Specify a swagger file or specify an API base path with an API path, an API verb, and an action name.",
+ "translation": "Invalid argument(s). Specify a swagger file or specify an API base path with an API path, an API verb, and an action name."
+ },
+ {
+ "id": "Cannot use value '{{.arg}}' for shared.",
+ "translation": "Cannot use value '{{.arg}}' for shared."
+ },
+ {
+ "id": "Action",
+ "translation": "Action"
+ },
+ {
+ "id": "Verb",
+ "translation": "Verb"
+ },
+ {
+ "id": "API Name",
+ "translation": "API Name"
+ },
+ {
+ "id": "URL",
+ "translation": "URL"
+ },
+ {
+ "id": "Base path",
+ "translation": "Base path"
+ },
+ {
+ "id": "Path",
+ "translation": "Path"
+ },
+ {
+ "id": "display full description of each API",
+ "translation": "display full description of each API"
+ },
+ {
+ "id": "An API host must be provided.",
+ "translation": "An API host must be provided."
+ },
+ {
+ "id": "Request accepted, but processing not completed yet.",
+ "translation": "Request accepted, but processing not completed yet."
+ },
+ {
+ "id": "{{.ok}} invoked /{{.namespace}}/{{.name}}, but the request has not yet finished, with id {{.id}}\n",
+ "translation": "{{.ok}} invoked /{{.namespace}}/{{.name}}, but the request has not yet finished, with id {{.id}}\n"
+ },
+ {
+ "id": "treat ACTION as a web action, a raw HTTP web action, or as a standard action; yes | true = web action, raw = raw HTTP web action, no | false = standard action",
+ "translation": "treat ACTION as a web action, a raw HTTP web action, or as a standard action; yes | true = web action, raw = raw HTTP web action, no | false = standard action"
+ },
+ {
+ "id": "Invalid argument '{{.arg}}' for --web flag. Valid input consist of 'yes', 'true', 'raw', 'false', or 'no'.",
+ "translation": "Invalid argument '{{.arg}}' for --web flag. Valid input consist of 'yes', 'true', 'raw', 'false', or 'no'."
+ },
+ {
+ "id": "API action '{{.name}}' does not exist",
+ "translation": "API action '{{.name}}' does not exist"
+ },
+ {
+ "id": "API action '{{.name}}' is not a web action. Issue 'wsk action update {{.name}} --web true' to convert the action to a web action.",
+ "translation": "API action '{{.name}}' is not a web action. Issue 'wsk action update {{.name}} --web true' to convert the action to a web action."
+ },
+ {
+ "id": "Invalid configuration. The x-openwhisk stanza is missing.",
+ "translation": "Invalid configuration. The x-openwhisk stanza is missing."
+ },
+ {
+ "id": "Internal error. Missing value stanza in API configuration response",
+ "translation": "Internal error. Missing value stanza in API configuration response"
+ },
+ {
+ "id": "Internal error. Missing apidoc stanza in API configuration",
+ "translation": "Internal error. Missing apidoc stanza in API configuration"
+ },
+ {
+ "id": "Missing operationId field in API configuration for operation {{.op}}",
+ "translation": "Missing operationId field in API configuration for operation {{.op}}"
+ },
+ {
+ "id": "Missing x-openwhisk stanza in API configuration for operation {{.op}}",
+ "translation": "Missing x-openwhisk stanza in API configuration for operation {{.op}}"
+ },
+ {
+ "id": "Missing x-openwhisk.namespace field in API configuration for operation {{.op}}",
+ "translation": "Missing x-openwhisk.namespace field in API configuration for operation {{.op}}"
+ },
+ {
+ "id": "Missing x-openwhisk.action field in API configuration for operation {{.op}}",
+ "translation": "Missing x-openwhisk.action field in API configuration for operation {{.op}}"
+ },
+ {
+ "id": "Missing x-openwhisk.url field in API configuration for operation {{.op}}",
+ "translation": "Missing x-openwhisk.url field in API configuration for operation {{.op}}"
+ },
+ {
+ "id": "work with APIs",
+ "translation": "work with APIs"
+ },
+ {
+ "id": "create a new API",
+ "translation": "create a new API"
+ },
+ {
+ "id": "get API details",
+ "translation": "get API details"
+ },
+ {
+ "id": "delete an API",
+ "translation": "delete an API"
+ },
+ {
+ "id": "list APIs",
+ "translation": "list APIs"
+ },
+ {
+ "id": "Set the web action response TYPE. Possible values are html, http, json, text, svg",
+ "translation": "Set the web action response TYPE. Possible values are html, http, json, text, svg"
+ },
+ {
+ "id": "Authorization key is not configured (--auth is required)",
+ "translation": "Authorization key is not configured (--auth is required)"
+ },
+ {
+ "id": "Specify the API output `TYPE`, either json or yaml",
+ "translation": "Specify the API output `TYPE`, either json or yaml"
+ },
+ {
+ "id": "Unable to parse YAML configuration file: {{.err}}",
+ "translation": "Unable to parse YAML configuration file: {{.err}}"
+ },
+ {
+ "id": "Unable to convert API into YAML: {{.err}}",
+ "translation": "Unable to convert API into YAML: {{.err}}"
+ },
+ {
+ "id": "Invalid format type: {{.type}}",
+ "translation": "Invalid format type: {{.type}}"
+ }
+]
diff --git a/tools/cli/go-whisk-cli/wski18n/resources/es_ES.all.json b/tools/cli/go-whisk-cli/wski18n/resources/es_ES.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk-cli/wski18n/resources/fr_FR.all.json b/tools/cli/go-whisk-cli/wski18n/resources/fr_FR.all.json
new file mode 100644
index 0000000..55e81ef
--- /dev/null
+++ b/tools/cli/go-whisk-cli/wski18n/resources/fr_FR.all.json
@@ -0,0 +1,6 @@
+[
+ {
+ "id": "bypass certificate checking",
+ "translation": "Some translation in French"
+ }
+]
diff --git a/tools/cli/go-whisk-cli/wski18n/resources/it_IT.all.json b/tools/cli/go-whisk-cli/wski18n/resources/it_IT.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk-cli/wski18n/resources/ja_JA.all.json b/tools/cli/go-whisk-cli/wski18n/resources/ja_JA.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk-cli/wski18n/resources/ko_KR.all.json b/tools/cli/go-whisk-cli/wski18n/resources/ko_KR.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk-cli/wski18n/resources/pt_BR.all.json b/tools/cli/go-whisk-cli/wski18n/resources/pt_BR.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk-cli/wski18n/resources/zh_Hans.all.json b/tools/cli/go-whisk-cli/wski18n/resources/zh_Hans.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk-cli/wski18n/resources/zh_Hant.all.json b/tools/cli/go-whisk-cli/wski18n/resources/zh_Hant.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk/whisk/action.go b/tools/cli/go-whisk/whisk/action.go
new file mode 100644
index 0000000..f2ced21
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/action.go
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk
+
+import (
+ "fmt"
+ "net/http"
+ "errors"
+ "net/url"
+ "../wski18n"
+)
+
+type ActionService struct {
+ client *Client
+}
+
+type Action struct {
+ Namespace string `json:"namespace,omitempty"`
+ Name string `json:"name,omitempty"`
+ Version string `json:"version,omitempty"`
+ Exec *Exec `json:"exec,omitempty"`
+ Annotations KeyValueArr `json:"annotations,omitempty"`
+ Parameters KeyValueArr `json:"parameters,omitempty"`
+ Limits *Limits `json:"limits,omitempty"`
+ Error string `json:"error,omitempty"`
+ Code int `json:"code,omitempty"`
+ Publish *bool `json:"publish,omitempty"`
+}
+
+type Exec struct {
+ Kind string `json:"kind,omitempty"`
+ Code *string `json:"code,omitempty"`
+ Image string `json:"image,omitempty"`
+ Init string `json:"init,omitempty"`
+ Main string `json:"main,omitempty"`
+ Components []string `json:"components,omitempty"` // List of fully qualified actions
+}
+
+type ActionListOptions struct {
+ Limit int `url:"limit"`
+ Skip int `url:"skip"`
+ Docs bool `url:"docs,omitempty"`
+}
+
+////////////////////
+// Action Methods //
+////////////////////
+
+func (s *ActionService) List(packageName string, options *ActionListOptions) ([]Action, *http.Response, error) {
+ var route string
+ var actions []Action
+
+ if (len(packageName) > 0) {
+ // Encode resource name as a path (with no query params) before inserting it into the URI
+ // This way any '?' chars in the name won't be treated as the beginning of the query params
+ packageName = (&url.URL{Path: packageName}).String()
+ route = fmt.Sprintf("actions/%s/", packageName)
+ } else {
+ route = fmt.Sprintf("actions")
+ }
+
+ routeUrl, err := addRouteOptions(route, options)
+ if err != nil {
+ Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+ errMsg := wski18n.T("Unable to add route options '{{.options}}'",
+ map[string]interface{}{"options": options})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, nil, whiskErr
+ }
+ Debug(DbgError, "Action list route with options: %s\n", route)
+
+ req, err := s.client.NewRequestUrl("GET", routeUrl, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+ if err != nil {
+ Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) error: '%s'\n", routeUrl, err)
+ errMsg := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": routeUrl, "err": err})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, nil, whiskErr
+ }
+
+ resp, err := s.client.Do(req, &actions, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return actions, resp, err
+}
+
+func (s *ActionService) Insert(action *Action, overwrite bool) (*Action, *http.Response, error) {
+ // Encode resource name as a path (with no query params) before inserting it into the URI
+ // This way any '?' chars in the name won't be treated as the beginning of the query params
+ actionName := (&url.URL{Path: action.Name}).String()
+ route := fmt.Sprintf("actions/%s?overwrite=%t", actionName, overwrite)
+ Debug(DbgInfo, "Action insert route: %s\n", route)
+
+ req, err := s.client.NewRequest("PUT", route, action, IncludeNamespaceInUrl)
+ if err != nil {
+ Debug(DbgError, "http.NewRequest(PUT, %s, %#v) error: '%s'\n", route, err, action)
+ errMsg := wski18n.T("Unable to create HTTP request for PUT '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, nil, whiskErr
+ }
+
+ a := new(Action)
+ resp, err := s.client.Do(req, &a, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return a, resp, nil
+}
+
+func (s *ActionService) Get(actionName string) (*Action, *http.Response, error) {
+ // Encode resource name as a path (with no query params) before inserting it into the URI
+ // This way any '?' chars in the name won't be treated as the beginning of the query params
+ actionName = (&url.URL{Path: actionName}).String()
+ route := fmt.Sprintf("actions/%s", actionName)
+
+ req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl)
+ if err != nil {
+ Debug(DbgError, "http.NewRequest(GET, %s, nil) error: '%s'\n", route, err)
+ errMsg := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, nil, whiskErr
+ }
+
+ a := new(Action)
+ resp, err := s.client.Do(req, &a, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return a, resp, nil
+}
+
+func (s *ActionService) Delete(actionName string) (*http.Response, error) {
+ // Encode resource name as a path (with no query params) before inserting it into the URI
+ // This way any '?' chars in the name won't be treated as the beginning of the query params
+ actionName = (&url.URL{Path: actionName}).String()
+ route := fmt.Sprintf("actions/%s", actionName)
+ Debug(DbgInfo, "HTTP route: %s\n", route)
+
+ req, err := s.client.NewRequest("DELETE", route, nil, IncludeNamespaceInUrl)
+ if err != nil {
+ Debug(DbgError, "http.NewRequest(DELETE, %s, nil) error: '%s'\n", route, err)
+ errMsg := wski18n.T("Unable to create HTTP request for DELETE '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, whiskErr
+ }
+
+ a := new(Action)
+ resp, err := s.client.Do(req, a, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+ return resp, err
+ }
+
+ return resp, nil
+}
+
+func (s *ActionService) Invoke(actionName string, payload interface{}, blocking bool, result bool) (map[string]interface {}, *http.Response, error) {
+ var res map[string]interface {}
+
+ // Encode resource name as a path (with no query params) before inserting it into the URI
+ // This way any '?' chars in the name won't be treated as the beginning of the query params
+ actionName = (&url.URL{Path: actionName}).String()
+ route := fmt.Sprintf("actions/%s?blocking=%t&result=%t", actionName, blocking, result)
+ Debug(DbgInfo, "HTTP route: %s\n", route)
+
+ req, err := s.client.NewRequest("POST", route, payload, IncludeNamespaceInUrl)
+ if err != nil {
+ Debug(DbgError, "http.NewRequest(POST, %s, %#v) error: '%s'\n", route, payload, err)
+ errMsg := wski18n.T("Unable to create HTTP request for POST '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, nil, whiskErr
+ }
+
+ resp, err := s.client.Do(req, &res, blocking)
+
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+ return res, resp, err
+ }
+
+ return res, resp, nil
+}
diff --git a/tools/cli/go-whisk/whisk/activation.go b/tools/cli/go-whisk/whisk/activation.go
new file mode 100644
index 0000000..2133878
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/activation.go
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk
+
+import (
+ "fmt"
+ "net/http"
+ "errors"
+ "net/url"
+ "../wski18n"
+)
+
+type ActivationService struct {
+ client *Client
+}
+
+type Activation struct {
+ Namespace string `json:"namespace"`
+ Name string `json:"name"`
+ Version string `json:"version"`
+ Subject string `json:"subject"`
+ ActivationID string `json:"activationId"`
+ Cause string `json:"cause,omitempty"`
+ Start int64 `json:"start"` // When action started (in milliseconds since January 1, 1970 UTC)
+ End int64 `json:"end"` // Since a 0 is a valid value from server, don't omit
+ Duration int64 `json:"duration"` // Only available for actions
+ Response `json:"response"`
+ Logs []string `json:"logs"`
+ Annotations KeyValueArr `json:"annotations"`
+ Publish *bool `json:"publish,omitempty"`
+
+}
+
+type Response struct {
+ Status string `json:"status"`
+ StatusCode int `json:"statusCode"`
+ Success bool `json:"success"`
+ Result *Result `json:"result,omitempty"`
+}
+
+type Result map[string]interface{}
+
+type ActivationListOptions struct {
+ Name string `url:"name,omitempty"`
+ Limit int `url:"limit"`
+ Skip int `url:"skip"`
+ Since int64 `url:"since,omitempty"`
+ Upto int64 `url:"upto,omitempty"`
+ Docs bool `url:"docs,omitempty"`
+}
+
+//MWD - This structure may no longer be needed as the log format is now a string and not JSON
+type Log struct {
+ Log string `json:"log,omitempty"`
+ Stream string `json:"stream,omitempty"`
+ Time string `json:"time,omitempty"`
+}
+
+func (s *ActivationService) List(options *ActivationListOptions) ([]Activation, *http.Response, error) {
+ // TODO :: for some reason /activations only works with "_" as namespace
+ s.client.Namespace = "_"
+ route := "activations"
+ routeUrl, err := addRouteOptions(route, options)
+ if err != nil {
+ Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+ errStr := wski18n.T("Unable to append options '{{.options}}' to URL route '{{.route}}': {{.err}}",
+ map[string]interface{}{"options": fmt.Sprintf("%#v", options), "route": route, "err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ req, err := s.client.NewRequestUrl("GET", routeUrl, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+ if err != nil {
+ Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) error: '%s'\n", route, err)
+ errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ Debug(DbgInfo, "Sending HTTP request - URL '%s'; req %#v\n", req.URL.String(), req)
+
+ var activations []Activation
+ resp, err := s.client.Do(req, &activations, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return activations, resp, nil
+
+}
+
+func (s *ActivationService) Get(activationID string) (*Activation, *http.Response, error) {
+ // TODO :: for some reason /activations/:id only works with "_" as namespace
+ s.client.Namespace = "_"
+
+ // Encode resource name as a path (with no query params) before inserting it into the URI
+ // This way any '?' chars in the name won't be treated as the beginning of the query params
+ activationID = (&url.URL{Path: activationID}).String()
+ route := fmt.Sprintf("activations/%s", activationID)
+
+ req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl)
+ if err != nil {
+ Debug(DbgError, "http.NewRequest(GET, %s) error: '%s'\n", route, err)
+ errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ Debug(DbgInfo, "Sending HTTP request - URL '%s'; req %#v\n", req.URL.String(), req)
+
+ a := new(Activation)
+ resp, err := s.client.Do(req, &a, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return a, resp, nil
+}
+
+func (s *ActivationService) Logs(activationID string) (*Activation, *http.Response, error) {
+ // TODO :: for some reason /activations/:id/logs only works with "_" as namespace
+ s.client.Namespace = "_"
+ // Encode resource name as a path (with no query params) before inserting it into the URI
+ // This way any '?' chars in the name won't be treated as the beginning of the query params
+ activationID = (&url.URL{Path: activationID}).String()
+ route := fmt.Sprintf("activations/%s/logs", activationID)
+
+ req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl)
+ if err != nil {
+ Debug(DbgError, "http.NewRequest(GET, %s) error: '%s'\n", route, err)
+ errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ Debug(DbgInfo, "Sending HTTP request - URL '%s'; req %#v\n", req.URL.String(), req)
+
+ activation := new(Activation)
+ resp, err := s.client.Do(req, &activation, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return activation, resp, nil
+}
+
+func (s *ActivationService) Result(activationID string) (*Response, *http.Response, error) {
+ // TODO :: for some reason /activations only works with "_" as namespace
+ s.client.Namespace = "_"
+ // Encode resource name as a path (with no query params) before inserting it into the URI
+ // This way any '?' chars in the name won't be treated as the beginning of the query params
+ activationID = (&url.URL{Path: activationID}).String()
+ route := fmt.Sprintf("activations/%s/result", activationID)
+
+ req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl)
+ if err != nil {
+ Debug(DbgError, "http.NewRequest(GET, %s) error: '%s'\n", route, err)
+ errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ Debug(DbgInfo, "Sending HTTP request - URL '%s'; req %#v\n", req.URL.String(), req)
+
+ r := new(Response)
+ resp, err := s.client.Do(req, &r, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return r, resp, nil
+
+}
diff --git a/tools/cli/go-whisk/whisk/api.go b/tools/cli/go-whisk/whisk/api.go
new file mode 100644
index 0000000..6a04fbf
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/api.go
@@ -0,0 +1,586 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk
+
+import (
+ "net/http"
+ "errors"
+ "../wski18n"
+)
+
+type ApiService struct {
+ client *Client
+}
+
+// wsk api create : Request, Response
+type ApiCreateRequest struct {
+ ApiDoc *Api `json:"apidoc,omitempty"`
+}
+type ApiCreateRequestOptions ApiOptions
+type ApiCreateResponse RetApi
+type ApiCreateResponseV2 RetApiV2
+
+// wsk api list : Request, Response
+type ApiListRequest struct {
+}
+type ApiListRequestOptions struct {
+ ApiOptions
+ Limit int `url:"limit"`
+ Skip int `url:"skip"`
+ Docs bool `url:"docs,omitempty"`
+}
+type ApiListResponse RetApiArray
+type ApiListResponseV2 RetApiArrayV2
+
+// wsk api get : Request, Response
+type ApiGetRequest struct {
+ Api
+}
+type ApiGetRequestOptions ApiOptions
+type ApiGetResponse RetApiArray
+type ApiGetResponseV2 RetApiArrayV2
+
+// wsk api delete : Request, Response
+type ApiDeleteRequest struct {
+ Api
+}
+type ApiDeleteRequestOptions ApiOptions
+type ApiDeleteResponse struct {}
+
+type Api struct {
+ Namespace string `json:"namespace,omitempty"`
+ ApiName string `json:"apiName,omitempty"`
+ GatewayBasePath string `json:"gatewayBasePath,omitempty"`
+ GatewayRelPath string `json:"gatewayPath,omitempty"`
+ GatewayMethod string `json:"gatewayMethod,omitempty"`
+ Id string `json:"id,omitempty"`
+ GatewayFullPath string `json:"gatewayFullPath,omitempty"`
+ Swagger string `json:"swagger,omitempty"`
+ Action *ApiAction `json:"action,omitempty"`
+}
+
+type ApiAction struct {
+ Name string `json:"name,omitempty"`
+ Namespace string `json:"namespace,omitempty"`
+ BackendMethod string `json:"backendMethod,omitempty"`
+ BackendUrl string `json:"backendUrl,omitempty"`
+ Auth string `json:"authkey,omitempty"`
+}
+
+type ApiOptions struct {
+ ActionName string `url:"action,omitempty"`
+ ApiBasePath string `url:"basepath,omitempty"`
+ ApiRelPath string `url:"relpath,omitempty"`
+ ApiVerb string `url:"operation,omitempty"`
+ ApiName string `url:"apiname,omitempty"`
+ SpaceGuid string `url:"spaceguid,omitempty"`
+ AccessToken string `url:"accesstoken,omitempty"`
+ ResponseType string `url:"responsetype,omitempty"`
+}
+
+type ApiUserAuth struct {
+ SpaceGuid string `json:"spaceguid,omitempty"`
+ AccessToken string `json:"accesstoken,omitempty"`
+}
+
+type RetApiArray struct {
+ Apis []ApiItem `json:"apis,omitempty"`
+}
+
+type ApiItem struct {
+ ApiId string `json:"id,omitempty"`
+ QueryKey string `json:"key,omitempty"`
+ ApiValue *RetApi `json:"value,omitempty"`
+}
+
+type RetApi struct {
+ Namespace string `json:"namespace"`
+ BaseUrl string `json:"gwApiUrl"`
+ Activated bool `json:"gwApiActivated"`
+ TenantId string `json:"tenantId"`
+ Swagger *ApiSwagger `json:"apidoc,omitempty"`
+}
+
+type ApiSwagger struct {
+ SwaggerName string `json:"swagger,omitempty"`
+ BasePath string `json:"basePath,omitempty"`
+ Info *ApiSwaggerInfo `json:"info,omitempty"`
+ Paths map[string]map[string]*ApiSwaggerOperationV1 `json:"paths,omitempty"`
+ XConfig map[string]map[string][]map[string]map[string]interface{} `json:"x-ibm-configuration,omitempty"`
+}
+
+type RetApiArrayV2 struct {
+ Apis []ApiItemV2 `json:"apis,omitempty"`
+}
+
+type ApiItemV2 struct {
+ ApiId string `json:"id,omitempty"`
+ QueryKey string `json:"key,omitempty"`
+ ApiValue *RetApiV2 `json:"value,omitempty"`
+}
+
+type RetApiV2 struct {
+ Namespace string `json:"namespace"`
+ BaseUrl string `json:"gwApiUrl"`
+ Activated bool `json:"gwApiActivated"`
+ TenantId string `json:"tenantId"`
+ Swagger *ApiSwaggerV2 `json:"apidoc,omitempty"`
+}
+
+type ApiSwaggerV2 struct {
+ SwaggerName string `json:"swagger,omitempty"`
+ BasePath string `json:"basePath,omitempty"`
+ Info *ApiSwaggerInfo `json:"info,omitempty"`
+ Paths map[string]map[string]*ApiSwaggerOperationV2 `json:"paths,omitempty"` // Paths["/a/path"]["get"] -> a generic object
+ SecurityDef interface{} `json:"securityDefinitions,omitempty"`
+ Security interface{} `json:"security,omitempty"`
+ XConfig interface{} `json:"x-ibm-configuration,omitempty"`
+ XRateLimit interface{} `json:"x-ibm-rate-limit,omitempty"`
+}
+
+type ApiSwaggerInfo struct {
+ Title string `json:"title,omitempty"`
+ Version string `json:"version,omitempty"`
+}
+
+type ApiSwaggerOperationV1 struct {
+ Responses interface{} `json:"responses"`
+ XOpenWhisk *ApiSwaggerOpXOpenWhiskV1 `json:"x-ibm-op-ext,omitempty"`
+}
+
+type ApiSwaggerOperationV2 struct {
+ OperationId string `json:"operationId"`
+ Responses interface{} `json:"responses"`
+ XOpenWhisk *ApiSwaggerOpXOpenWhiskV2 `json:"x-openwhisk,omitempty"`
+}
+
+type ApiSwaggerOpXOpenWhiskV1 struct {
+ ActionName string `json:"actionName"`
+ Namespace string `json:"actionNamespace"`
+ ActionUrlVerb string `json:"backendMethod"`
+ ActionUrl string `json:"backendUrl"`
+ Policies interface{} `json:"policies"`
+}
+
+type ApiSwaggerOpXOpenWhiskV2 struct {
+ ActionName string `json:"action"`
+ Namespace string `json:"namespace"`
+ Package string `json:"package"`
+ ApiUrl string `json:"url"`
+}
+
+var ApiVerbs map[string]bool = map[string]bool {
+ "GET": true,
+ "PUT": true,
+ "POST": true,
+ "DELETE": true,
+ "PATCH": true,
+ "HEAD": true,
+ "OPTIONS": true,
+}
+
+const (
+ Overwrite = true
+ DoNotOverwrite = false
+)
+
+////////////////////
+// Api Methods //
+////////////////////
+
+func (s *ApiService) List(apiListOptions *ApiListRequestOptions) (*ApiListResponse, *http.Response, error) {
+ route := "web/whisk.system/routemgmt/getApi.http"
+ Debug(DbgInfo, "Api GET/list route: %s\n", route)
+
+ routeUrl, err := addRouteOptions(route, apiListOptions)
+ if err != nil {
+ Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, apiListOptions, err)
+ errMsg := wski18n.T("Unable to add route options '{{.options}}'",
+ map[string]interface{}{"options": apiListOptions})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, nil, whiskErr
+ }
+ Debug(DbgInfo, "Api GET/list route with api options: %s\n", routeUrl)
+
+ req, err := s.client.NewRequestUrl("GET", routeUrl, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+ if err != nil {
+ Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) error: '%s'\n", routeUrl, err)
+ errMsg := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": routeUrl, "err": err})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, nil, whiskErr
+ }
+
+ apiArray := new(ApiListResponse)
+ resp, err := s.client.Do(req, &apiArray, ExitWithErrorOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return apiArray, resp, err
+}
+
+func (s *ApiService) Insert(api *ApiCreateRequest, options *ApiCreateRequestOptions, overwrite bool) (*ApiCreateResponse, *http.Response, error) {
+ route := "web/whisk.system/routemgmt/createApi.http"
+ Debug(DbgInfo, "Api PUT route: %s\n", route)
+
+ routeUrl, err := addRouteOptions(route, options)
+ if err != nil {
+ Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+ errMsg := wski18n.T("Unable to add route options '{{.options}}'",
+ map[string]interface{}{"options": options})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, nil, whiskErr
+ }
+ Debug(DbgError, "Api create route with options: %s\n", routeUrl)
+
+ req, err := s.client.NewRequestUrl("POST", routeUrl, api, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+ if err != nil {
+ Debug(DbgError, "http.NewRequestUrl(POST, %s, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson) error: '%s'\n", route, err)
+ errMsg := wski18n.T("Unable to create HTTP request for POST '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, nil, whiskErr
+ }
+
+ retApi := new(ApiCreateResponse)
+ resp, err := s.client.Do(req, &retApi, ExitWithErrorOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return retApi, resp, nil
+}
+
+func (s *ApiService) Get(api *ApiGetRequest, options *ApiGetRequestOptions) (*ApiGetResponse, *http.Response, error) {
+ route := "web/whisk.system/routemgmt/getApi.http"
+ Debug(DbgInfo, "Api GET route: %s\n", route)
+
+ routeUrl, err := addRouteOptions(route, options)
+ if err != nil {
+ Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+ errMsg := wski18n.T("Unable to add route options '{{.options}}'",
+ map[string]interface{}{"options": options})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, nil, whiskErr
+ }
+ Debug(DbgError, "Api get route with options: %s\n", routeUrl)
+
+ req, err := s.client.NewRequestUrl("GET", routeUrl, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+ if err != nil {
+ Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) error: '%s'\n", route, err)
+ errMsg := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, nil, whiskErr
+ }
+
+ retApi := new(ApiGetResponse)
+ resp, err := s.client.Do(req, &retApi, ExitWithErrorOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return retApi, resp, nil
+}
+
+func (s *ApiService) Delete(api *ApiDeleteRequest, options *ApiDeleteRequestOptions) (*http.Response, error) {
+ route := "web/whisk.system/routemgmt/deleteApi.http"
+ Debug(DbgInfo, "Api DELETE route: %s\n", route)
+
+ routeUrl, err := addRouteOptions(route, options)
+ if err != nil {
+ Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+ errMsg := wski18n.T("Unable to add route options '{{.options}}'",
+ map[string]interface{}{"options": options})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, whiskErr
+ }
+ Debug(DbgError, "Api DELETE route with options: %s\n", routeUrl)
+
+ req, err := s.client.NewRequestUrl("DELETE", routeUrl, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+ if err != nil {
+ Debug(DbgError, "http.NewRequestUrl(DELETE, %s, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) error: '%s'\n", route, err)
+ errMsg := wski18n.T("Unable to create HTTP request for DELETE '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, whiskErr
+ }
+
+ retApi := new(ApiDeleteResponse)
+ resp, err := s.client.Do(req, &retApi, ExitWithErrorOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+ return resp, err
+ }
+
+ return nil, nil
+}
+
+/////////////
+// V2 Cmds //
+/////////////
+func (s *ApiService) ListV2(apiListOptions *ApiListRequestOptions) (*ApiListResponseV2, *http.Response, error) {
+ route := "web/whisk.system/apimgmt/getApi.http"
+
+ routeUrl, err := addRouteOptions(route, apiListOptions)
+ if err != nil {
+ Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, apiListOptions, err)
+ errMsg := wski18n.T("Unable to add route options '{{.options}}'",
+ map[string]interface{}{"options": apiListOptions})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, nil, whiskErr
+ }
+ Debug(DbgInfo, "Api GET/list route with api options: %s\n", routeUrl)
+
+ req, err := s.client.NewRequestUrl("GET", routeUrl, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+ if err != nil {
+ Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson) error: '%s'\n", routeUrl, err)
+ errMsg := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": routeUrl, "err": err})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, nil, whiskErr
+ }
+
+ apiArray := new(ApiListResponseV2)
+ resp, err := s.client.Do(req, &apiArray, ExitWithErrorOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ err = validateApiListResponse(apiArray)
+ if err != nil {
+ Debug(DbgError, "Not a valid ApiListReponse object\n")
+ return nil, resp, err
+ }
+
+ return apiArray, resp, err
+}
+
+func (s *ApiService) InsertV2(api *ApiCreateRequest, options *ApiCreateRequestOptions, overwrite bool) (*ApiCreateResponseV2, *http.Response, error) {
+ route := "web/whisk.system/apimgmt/createApi.http"
+ Debug(DbgInfo, "Api PUT route: %s\n", route)
+
+ routeUrl, err := addRouteOptions(route, options)
+ if err != nil {
+ Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+ errMsg := wski18n.T("Unable to add route options '{{.options}}'",
+ map[string]interface{}{"options": options})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, nil, whiskErr
+ }
+ Debug(DbgError, "Api create route with options: %s\n", routeUrl)
+
+ req, err := s.client.NewRequestUrl("POST", routeUrl, api, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+ if err != nil {
+ Debug(DbgError, "http.NewRequestUrl(POST, %s, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson) error: '%s'\n", route, err)
+ errMsg := wski18n.T("Unable to create HTTP request for POST '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, nil, whiskErr
+ }
+
+ retApi := new(ApiCreateResponseV2)
+ resp, err := s.client.Do(req, &retApi, ExitWithErrorOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ err = validateApiSwaggerResponse(retApi.Swagger)
+ if err != nil {
+ Debug(DbgError, "Not a valid API creation response\n")
+ return nil, resp, err
+ }
+
+ return retApi, resp, nil
+}
+
+func (s *ApiService) GetV2(api *ApiGetRequest, options *ApiGetRequestOptions) (*ApiGetResponseV2, *http.Response, error) {
+ route := "web/whisk.system/apimgmt/getApi.http"
+ Debug(DbgInfo, "Api GET route: %s\n", route)
+
+ routeUrl, err := addRouteOptions(route, options)
+ if err != nil {
+ Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+ errMsg := wski18n.T("Unable to add route options '{{.options}}'",
+ map[string]interface{}{"options": options})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, nil, whiskErr
+ }
+ Debug(DbgError, "Api get route with options: %s\n", routeUrl)
+
+ req, err := s.client.NewRequestUrl("GET", routeUrl, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+ if err != nil {
+ Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson) error: '%s'\n", route, err)
+ errMsg := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, nil, whiskErr
+ }
+
+ retApi := new(ApiGetResponseV2)
+ resp, err := s.client.Do(req, &retApi, ExitWithErrorOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return retApi, resp, nil
+}
+
+func (s *ApiService) DeleteV2(api *ApiDeleteRequest, options *ApiDeleteRequestOptions) (*http.Response, error) {
+ route := "web/whisk.system/apimgmt/deleteApi.http"
+ Debug(DbgInfo, "Api DELETE route: %s\n", route)
+
+ routeUrl, err := addRouteOptions(route, options)
+ if err != nil {
+ Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+ errMsg := wski18n.T("Unable to add route options '{{.options}}'",
+ map[string]interface{}{"options": options})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, whiskErr
+ }
+ Debug(DbgError, "Api DELETE route with options: %s\n", routeUrl)
+
+ req, err := s.client.NewRequestUrl("DELETE", routeUrl, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+ if err != nil {
+ Debug(DbgError, "http.NewRequestUrl(DELETE, %s, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson) error: '%s'\n", route, err)
+ errMsg := wski18n.T("Unable to create HTTP request for DELETE '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+ NO_DISPLAY_USAGE)
+ return nil, whiskErr
+ }
+
+ retApi := new(ApiDeleteResponse)
+ resp, err := s.client.Do(req, &retApi, ExitWithErrorOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+ return resp, err
+ }
+
+ return nil, nil
+}
+
+
+func validateApiListResponse(apiList *ApiListResponseV2) error {
+ for i:=0; i<len(apiList.Apis); i++ {
+ if apiList.Apis[i].ApiValue == nil {
+ Debug(DbgError, "validateApiResponse: No value stanza in api %v\n", apiList.Apis[i])
+ errMsg := wski18n.T("Internal error. Missing value stanza in API configuration response")
+ whiskErr := MakeWskError(errors.New(errMsg), EXITCODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+ err := validateApiSwaggerResponse(apiList.Apis[i].ApiValue.Swagger)
+ if (err != nil) {
+ Debug(DbgError, "validateApiListResponse: Invalid Api: %v\n", apiList.Apis[i])
+ return err
+ }
+ }
+ return nil
+}
+
+func validateApiSwaggerResponse(swagger *ApiSwaggerV2) error {
+ if swagger == nil {
+ Debug(DbgError, "validateApiSwaggerResponse: No apidoc stanza in api\n")
+ errMsg := wski18n.T("Internal error. Missing apidoc stanza in API configuration")
+ whiskErr := MakeWskError(errors.New(errMsg), EXITCODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+ for path, _ := range swagger.Paths {
+ err := validateApiPath(swagger.Paths[path])
+ if err != nil {
+ Debug(DbgError, "validateApiResponse: Invalid Api Path object: %v\n", swagger.Paths[path])
+ return err
+ }
+ }
+
+ return nil
+}
+
+func validateApiPath(path map[string]*ApiSwaggerOperationV2) error {
+ for op, opv := range path {
+ err := validateApiOperation(op, opv)
+ if err != nil {
+ Debug(DbgError, "validateApiPath: Invalid Api operation object: %v\n", opv)
+ return err
+ }
+ }
+ return nil
+}
+
+func validateApiOperation(opName string, op *ApiSwaggerOperationV2) error {
+ if len(op.OperationId) == 0 {
+ Debug(DbgError, "validateApiResponse: No operationId field in operation %v\n", op)
+ errMsg := wski18n.T("Missing operationId field in API configuration for operation {{.op}}",
+ map[string]interface{}{"op": opName})
+ whiskErr := MakeWskError(errors.New(errMsg), EXITCODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+ if op.XOpenWhisk == nil {
+ Debug(DbgError, "validateApiResponse: No x-openwhisk stanza in operation %v\n", op)
+ errMsg := wski18n.T("Missing x-openwhisk stanza in API configuration for operation {{.op}}",
+ map[string]interface{}{"op": opName})
+ whiskErr := MakeWskError(errors.New(errMsg), EXITCODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+ if len(op.XOpenWhisk.Namespace) == 0 {
+ Debug(DbgError, "validateApiOperation: no x-openwhisk.namespace stanza in operation %v\n", op)
+ errMsg := wski18n.T("Missing x-openwhisk.namespace field in API configuration for operation {{.op}}",
+ map[string]interface{}{"op": opName})
+ whiskErr := MakeWskError(errors.New(errMsg), EXITCODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+
+ // Note: The op.XOpenWhisk.Package field can have a value of "", so don't enforce a value
+
+ if len(op.XOpenWhisk.ActionName) == 0 {
+ Debug(DbgError, "validateApiOperation: no x-openwhisk.action stanza in operation %v\n", op)
+ errMsg := wski18n.T("Missing x-openwhisk.action field in API configuration for operation {{.op}}",
+ map[string]interface{}{"op": opName})
+ whiskErr := MakeWskError(errors.New(errMsg), EXITCODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+ if len(op.XOpenWhisk.ApiUrl) == 0 {
+ Debug(DbgError, "validateApiOperation: no x-openwhisk.url stanza in operation %v\n", op)
+ errMsg := wski18n.T("Missing x-openwhisk.url field in API configuration for operation {{.op}}",
+ map[string]interface{}{"op": opName})
+ whiskErr := MakeWskError(errors.New(errMsg), EXITCODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return whiskErr
+ }
+ return nil
+}
diff --git a/tools/cli/go-whisk/whisk/client.go b/tools/cli/go-whisk/whisk/client.go
new file mode 100644
index 0000000..49222e4
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/client.go
@@ -0,0 +1,600 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk
+
+import (
+ "bytes"
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "crypto/tls"
+ "errors"
+ "reflect"
+ "../wski18n"
+ "strings"
+)
+
+const (
+ defaultBaseURL = "openwhisk.ng.bluemix.net"
+ AuthRequired = true
+ NoAuth = false
+ IncludeNamespaceInUrl = true
+ DoNotIncludeNamespaceInUrl = false
+ AppendOpenWhiskPathPrefix = true
+ DoNotAppendOpenWhiskPathPrefix = false
+ EncodeBodyAsJson = "json"
+ EncodeBodyAsFormData = "formdata"
+ ProcessTimeOut = true
+ DoNotProcessTimeOut = false
+ ExitWithErrorOnTimeout = true
+ ExitWithSuccessOnTimeout = false
+)
+
+type Client struct {
+ client *http.Client
+ *Config
+ Transport *http.Transport
+
+ Sdks *SdkService
+ Triggers *TriggerService
+ Actions *ActionService
+ Rules *RuleService
+ Activations *ActivationService
+ Packages *PackageService
+ Namespaces *NamespaceService
+ Info *InfoService
+ Apis *ApiService
+}
+
+type Config struct {
+ Namespace string // NOTE :: Default is "_"
+ AuthToken string
+ Host string
+ BaseURL *url.URL // NOTE :: Default is "openwhisk.ng.bluemix.net"
+ Version string
+ Verbose bool
+ Debug bool // For detailed tracing
+ Insecure bool
+}
+
+func NewClient(httpClient *http.Client, config *Config) (*Client, error) {
+
+ // Disable certificate checking in the dev environment if in insecure mode
+ if config.Insecure {
+ Debug(DbgInfo, "Disabling certificate checking.\n")
+
+ tlsConfig := &tls.Config{
+ InsecureSkipVerify: true,
+ }
+
+ http.DefaultClient.Transport = &http.Transport{
+ TLSClientConfig: tlsConfig,
+ }
+ }
+
+ if httpClient == nil {
+ httpClient = http.DefaultClient
+ }
+
+ var err error
+ if config.BaseURL == nil {
+ config.BaseURL, err = url.Parse(defaultBaseURL)
+ if err != nil {
+ Debug(DbgError, "url.Parse(%s) error: %s\n", defaultBaseURL, err)
+ errStr := wski18n.T("Unable to create request URL '{{.url}}': {{.err}}",
+ map[string]interface{}{"url": defaultBaseURL, "err": err})
+ werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, werr
+ }
+ }
+
+ if len(config.Namespace) == 0 {
+ config.Namespace = "_"
+ }
+
+ if len(config.Version) == 0 {
+ config.Version = "v1"
+ }
+
+ c := &Client{
+ client: httpClient,
+ Config: config,
+ }
+
+ c.Sdks = &SdkService{client: c}
+ c.Triggers = &TriggerService{client: c}
+ c.Actions = &ActionService{client: c}
+ c.Rules = &RuleService{client: c}
+ c.Activations = &ActivationService{client: c}
+ c.Packages = &PackageService{client: c}
+ c.Namespaces = &NamespaceService{client: c}
+ c.Info = &InfoService{client: c}
+ c.Apis = &ApiService{client: c}
+
+ return c, nil
+}
+
+///////////////////////////////
+// Request/Utility Functions //
+///////////////////////////////
+
+func (c *Client) NewRequest(method, urlStr string, body interface{}, includeNamespaceInUrl bool) (*http.Request, error) {
+ if (includeNamespaceInUrl) {
+ if c.Config.Namespace != "" {
+ urlStr = fmt.Sprintf("%s/namespaces/%s/%s", c.Config.Version, c.Config.Namespace, urlStr)
+ } else {
+ urlStr = fmt.Sprintf("%s/namespaces", c.Config.Version)
+ }
+ } else {
+ urlStr = fmt.Sprintf("%s/%s", c.Config.Version, urlStr)
+ }
+
+ urlStr = fmt.Sprintf("%s/%s", c.BaseURL.String(), urlStr)
+ u, err := url.Parse(urlStr)
+ if err != nil {
+ Debug(DbgError, "url.Parse(%s) error: %s\n", urlStr, err)
+ errStr := wski18n.T("Invalid request URL '{{.url}}': {{.err}}",
+ map[string]interface{}{"url": urlStr, "err": err})
+ werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, werr
+ }
+
+ var buf io.ReadWriter
+ if body != nil {
+ buf = new(bytes.Buffer)
+ encoder := json.NewEncoder(buf)
+ encoder.SetEscapeHTML(false)
+ err := encoder.Encode(body)
+
+ if err != nil {
+ Debug(DbgError, "json.Encode(%#v) error: %s\n", body, err)
+ errStr := wski18n.T("Error encoding request body: {{.err}}", map[string]interface{}{"err": err})
+ werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, werr
+ }
+ }
+
+ req, err := http.NewRequest(method, u.String(), buf)
+ if err != nil {
+ Debug(DbgError, "http.NewRequest(%v, %s, buf) error: %s\n", method, u.String(), err)
+ errStr := wski18n.T("Error initializing request: {{.err}}", map[string]interface{}{"err": err})
+ werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, werr
+ }
+ if req.Body != nil {
+ req.Header.Add("Content-Type", "application/json")
+ }
+
+ err = c.addAuthHeader(req, AuthRequired)
+ if err != nil {
+ Debug(DbgError, "addAuthHeader() error: %s\n", err)
+ errStr := wski18n.T("Unable to add the HTTP authentication header: {{.err}}",
+ map[string]interface{}{"err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, werr
+ }
+
+ return req, nil
+}
+
+func (c *Client) addAuthHeader(req *http.Request, authRequired bool) error {
+ if c.Config.AuthToken != "" {
+ encodedAuthToken := base64.StdEncoding.EncodeToString([]byte(c.Config.AuthToken))
+ req.Header.Add("Authorization", fmt.Sprintf("Basic %s", encodedAuthToken))
+ Debug(DbgInfo, "Adding basic auth header; using authkey\n")
+ } else {
+ if authRequired {
+ Debug(DbgError, "The required authorization key is not configured - neither set as a property nor set via the --auth CLI argument\n")
+ errStr := wski18n.T("Authorization key is not configured (--auth is required)")
+ werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_USAGE, DISPLAY_MSG, DISPLAY_USAGE)
+ return werr
+ }
+ }
+ return nil
+}
+
+// Do sends an API request and returns the API response. The API response is
+// JSON decoded and stored in the value pointed to by v, or returned as an
+// error if an API error has occurred. If v implements the io.Writer
+// interface, the raw response body will be written to v, without attempting to
+// first decode it.
+func (c *Client) Do(req *http.Request, v interface{}, ExitWithErrorOnTimeout bool) (*http.Response, error) {
+ var err error
+
+ if IsVerbose() {
+ fmt.Println("REQUEST:")
+ fmt.Printf("[%s]\t%s\n", req.Method, req.URL)
+ if len(req.Header) > 0 {
+ fmt.Println("Req Headers")
+ printJSON(req.Header)
+ }
+ if req.Body != nil {
+ fmt.Println("Req Body")
+ fmt.Println(req.Body)
+ Debug(DbgInfo, "Req Body (ASCII quoted string):\n%+q\n", req.Body)
+ }
+ }
+
+ // Issue the request to the Whisk server endpoint
+ resp, err := c.client.Do(req)
+ if err != nil {
+ Debug(DbgError, "HTTP Do() [req %s] error: %s\n", req.URL.String(), err)
+ werr := MakeWskError(err, EXITCODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, werr
+ }
+ // Don't "defer resp.Body.Close()" here because the body is reloaded to allow caller to
+ // do custom body parsing, such as handling per-route error responses.
+ Verbose("RESPONSE:")
+ Verbose("Got response with code %d\n", resp.StatusCode)
+ if (IsVerbose() && len(resp.Header) > 0) {
+ fmt.Println("Resp Headers")
+ printJSON(resp.Header)
+ }
+
+ // Read the response body
+ data, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ Debug(DbgError, "ioutil.ReadAll(resp.Body) error: %s\n", err)
+ werr := MakeWskError(err, EXITCODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return resp, werr
+ }
+ Verbose("Response body size is %d bytes\n", len(data))
+ Verbose("Response body received:\n%s\n", string(data))
+ Debug(DbgInfo, "Response body received (ASCII quoted string):\n%+q\n", string(data))
+
+ // Reload the response body to allow caller access to the body; otherwise,
+ // the caller will have any empty body to read
+ resp.Body = ioutil.NopCloser(bytes.NewBuffer(data))
+
+ // With the HTTP response status code and the HTTP body contents,
+ // the possible response scenarios are:
+ //
+ // 0. HTTP Success + Body indicating a whisk failure result
+ // 1. HTTP Success + Valid body matching request expectations
+ // 2. HTTP Success + No body expected
+ // 3. HTTP Success + Body does NOT match request expectations
+ // 4. HTTP Failure + No body
+ // 5. HTTP Failure + Body matching error format expectation
+ // 6. HTTP Failure + Body NOT matching error format expectation
+
+ // Handle 4. HTTP Failure + No body
+ // If this happens, just return no data and an error
+ if !IsHttpRespSuccess(resp) && data == nil {
+ Debug(DbgError, "HTTP failure %d + no body\n", resp.StatusCode)
+ werr := MakeWskError(errors.New(wski18n.T("Command failed due to an HTTP failure")), resp.StatusCode-256,
+ DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return resp, werr
+ }
+
+ // Handle 5. HTTP Failure + Body matching error format expectation, or body matching a whisk.error() response
+ // Handle 6. HTTP Failure + Body NOT matching error format expectation
+ if !IsHttpRespSuccess(resp) && data != nil {
+ return parseErrorResponse(resp, data, v)
+ }
+
+ // Handle 0. HTTP Success + Body indicating a whisk failure result
+ // NOTE: Need to ignore activation records send in response to 'wsk get activation NNN` as
+ // these will report the same original error giving the appearance that the command failed.
+ if (IsHttpRespSuccess(resp) && // HTTP Status == 200
+ data!=nil && // HTTP response body exists
+ v != nil &&
+ !strings.Contains(reflect.TypeOf(v).String(), "Activation") && // Request is not `wsk activation get`
+ !IsResponseResultSuccess(data)) { // HTTP response body has Whisk error result
+ Debug(DbgInfo, "Got successful HTTP; but activation response reports an error\n")
+ return parseErrorResponse(resp, data, v)
+ }
+
+ // Handle 2. HTTP Success + No body expected
+ if IsHttpRespSuccess(resp) && v == nil {
+ Debug(DbgInfo, "No interface provided; no HTTP response body expected\n")
+ return resp, nil
+ }
+
+ // Handle 1. HTTP Success + Valid body matching request expectations
+ // Handle 3. HTTP Success + Body does NOT match request expectations
+ if IsHttpRespSuccess(resp) && v != nil {
+
+ // If a timeout occurs, 202 HTTP status code is returned, and the caller wishes to handle such an event, return
+ // an error corresponding with the timeout
+ if ExitWithErrorOnTimeout && resp.StatusCode == EXITCODE_TIMED_OUT {
+ errMsg := wski18n.T("Request accepted, but processing not completed yet.")
+ err = MakeWskError(errors.New(errMsg), EXITCODE_TIMED_OUT, NO_DISPLAY_MSG, NO_DISPLAY_USAGE,
+ NO_MSG_DISPLAYED, NO_DISPLAY_PREFIX, NO_APPLICATION_ERR, TIMED_OUT)
+ }
+
+ return parseSuccessResponse(resp, data, v), err
+ }
+
+ // We should never get here, but just in case return failure to keep the compiler happy
+ werr := MakeWskError(errors.New(wski18n.T("Command failed due to an internal failure")), EXITCODE_ERR_GENERAL,
+ DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return resp, werr
+}
+
+func parseErrorResponse(resp *http.Response, data []byte, v interface{}) (*http.Response, error) {
+ Debug(DbgInfo, "HTTP failure %d + body\n", resp.StatusCode)
+
+ // Determine if an application error was received (#5)
+ errorResponse := &ErrorResponse{Response: resp}
+ err := json.Unmarshal(data, errorResponse)
+
+ // Determine if error is an application error or an error generated by API
+ if err == nil {
+ if errorResponse.Code == nil /*&& errorResponse.ErrMsg != nil */&& resp.StatusCode == 502 {
+ return parseApplicationError(resp, data, v)
+ } else if errorResponse.Code != nil && errorResponse.ErrMsg != nil {
+ Debug(DbgInfo, "HTTP failure %d; server error %s\n", resp.StatusCode, errorResponse)
+ werr := MakeWskError(errorResponse, resp.StatusCode - 256, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return resp, werr
+ }
+ }
+
+ // Body contents are unknown (#6)
+ Debug(DbgError, "HTTP response with unexpected body failed due to contents parsing error: '%v'\n", err)
+ errMsg := wski18n.T("The connection failed, or timed out. (HTTP status code {{.code}})",
+ map[string]interface{}{"code": resp.StatusCode})
+ whiskErr := MakeWskError(errors.New(errMsg), resp.StatusCode - 256, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return resp, whiskErr
+}
+
+func parseApplicationError(resp *http.Response, data []byte, v interface{}) (*http.Response, error) {
+ Debug(DbgInfo, "Parsing application error\n")
+
+ whiskErrorResponse := &WhiskErrorResponse{}
+ err := json.Unmarshal(data, whiskErrorResponse)
+
+ // Handle application errors that occur when --result option is false (#5)
+ if err == nil && whiskErrorResponse != nil && whiskErrorResponse.Response != nil && whiskErrorResponse.Response.Status != nil {
+ Debug(DbgInfo, "Detected response status `%s` that a whisk.error(\"%#v\") was returned\n",
+ *whiskErrorResponse.Response.Status, *whiskErrorResponse.Response.Result)
+ errMsg := wski18n.T("The following application error was received: {{.err}}",
+ map[string]interface{}{"err": *whiskErrorResponse.Response.Result})
+ whiskErr := MakeWskError(errors.New(errMsg), resp.StatusCode - 256, NO_DISPLAY_MSG, NO_DISPLAY_USAGE,
+ NO_MSG_DISPLAYED, DISPLAY_PREFIX, APPLICATION_ERR)
+ return parseSuccessResponse(resp, data, v), whiskErr
+ }
+
+ appErrResult := &AppErrorResult{}
+ err = json.Unmarshal(data, appErrResult)
+
+ // Handle application errors that occur with blocking invocations when --result option is true (#5)
+ if err == nil && appErrResult.Error != nil {
+ Debug(DbgInfo, "Error code is null, blocking with result invocation error has occured\n")
+ errMsg := fmt.Sprintf("%v", *appErrResult.Error)
+ Debug(DbgInfo, "Application error received: %s\n", errMsg)
+
+ whiskErr := MakeWskError(errors.New(errMsg), resp.StatusCode - 256, NO_DISPLAY_MSG, NO_DISPLAY_USAGE,
+ NO_MSG_DISPLAYED, DISPLAY_PREFIX, APPLICATION_ERR)
+ return parseSuccessResponse(resp, data, v), whiskErr
+ }
+
+ // Body contents are unknown (#6)
+ Debug(DbgError, "HTTP response with unexpected body failed due to contents parsing error: '%v'\n", err)
+ errMsg := wski18n.T("The connection failed, or timed out. (HTTP status code {{.code}})",
+ map[string]interface{}{"code": resp.StatusCode})
+ whiskErr := MakeWskError(errors.New(errMsg), resp.StatusCode - 256, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return resp, whiskErr
+}
+
+func parseSuccessResponse(resp *http.Response, data []byte, v interface{}) (*http.Response) {
+ Debug(DbgInfo, "Parsing HTTP response into struct type: %s\n", reflect.TypeOf(v))
+
+ dc := json.NewDecoder(strings.NewReader(string(data)))
+ dc.UseNumber()
+ err := dc.Decode(v)
+
+ // If the decode was successful, return the response without error (#1). Otherwise, the decode did not work, so the
+ // server response was unexpected (#3)
+ if err == nil {
+ Debug(DbgInfo, "Successful parse of HTTP response into struct type: %s\n", reflect.TypeOf(v))
+ return resp
+ } else {
+ Debug(DbgWarn, "Unsuccessful parse of HTTP response into struct type: %s; parse error '%v'\n", reflect.TypeOf(v), err)
+ Debug(DbgWarn, "Request was successful, so ignoring the following unexpected response body that could not be parsed: %s\n", data)
+ return resp
+ }
+}
+
+////////////
+// Errors //
+////////////
+
+// For containing the server response body when an error message is returned
+// Here's an example error response body with HTTP status code == 400
+// {
+// "error": "namespace contains invalid characters",
+// "code": 1422870
+// }
+type ErrorResponse struct {
+ Response *http.Response // HTTP response that caused this error
+ ErrMsg *interface{} `json:"error"` // error message string
+ Code *int64 `json:"code"` // validation error code
+}
+
+type AppErrorResult struct {
+ Error *interface{} `json:"error"`
+}
+
+type WhiskErrorResponse struct {
+ Response *WhiskResponse `json:"response"`
+}
+
+type WhiskResponse struct {
+ Result *WhiskResult `json:"result"`
+ Success bool `json:"success"`
+ Status *interface{} `json:"status"`
+}
+
+type WhiskResult struct {
+// Error *WhiskError `json:"error"` // whisk.error(<string>) and whisk.reject({msg:<string>}) result in two different kinds of 'error' JSON objects
+}
+
+type WhiskError struct {
+ Msg *string `json:"msg"`
+}
+
+func (r ErrorResponse) Error() string {
+ return wski18n.T("{{.msg}} (code {{.code}})",
+ map[string]interface{}{"msg": fmt.Sprintf("%v", *r.ErrMsg), "code": r.Code})
+}
+
+////////////////////////////
+// Basic Client Functions //
+////////////////////////////
+
+func IsHttpRespSuccess(r *http.Response) bool {
+ return r.StatusCode >= 200 && r.StatusCode <= 299
+}
+
+func IsResponseResultSuccess(data []byte) bool {
+ errResp := new(WhiskErrorResponse)
+ err := json.Unmarshal(data, &errResp)
+ if (err == nil && errResp.Response != nil) {
+ return errResp.Response.Success
+ }
+ Debug(DbgWarn, "IsResponseResultSuccess: failed to parse response result: %v\n", err)
+ return true;
+}
+
+//
+// Create a HTTP request object using URL stored in url.URL object
+// Arguments:
+// method - HTTP verb (i.e. "GET", "PUT", etc)
+// urlRelResource - *url.URL structure representing the relative resource URL, including query params
+// body - optional. Object whose contents will be JSON encoded and placed in HTTP request body
+// includeNamespaceInUrl - when true "/namespaces/NAMESPACE" is included in the final URL; otherwise not included.
+// appendOpenWhiskPath - when true, the OpenWhisk URL format is generated
+// encodeBodyAs - specifies body encoding (json or form data)
+// useAuthentication - when true, the basic Authorization is included with the configured authkey as the value
+func (c *Client) NewRequestUrl(
+ method string,
+ urlRelResource *url.URL,
+ body interface{},
+ includeNamespaceInUrl bool,
+ appendOpenWhiskPath bool,
+ encodeBodyAs string,
+ useAuthentication bool) (*http.Request, error) {
+ var requestUrl *url.URL
+ var err error
+
+ if (appendOpenWhiskPath) {
+ var urlVerNamespaceStr string
+ var verPathEncoded = (&url.URL{Path: c.Config.Version}).String()
+
+ if (includeNamespaceInUrl) {
+ if c.Config.Namespace != "" {
+ // Encode path parts before inserting them into the URI so that any '?' is correctly encoded
+ // as part of the path and not the start of the query params
+ verNamespaceEncoded := (&url.URL{Path: c.Config.Namespace}).String()
+ urlVerNamespaceStr = fmt.Sprintf("%s/namespaces/%s", verPathEncoded, verNamespaceEncoded)
+ } else {
+ urlVerNamespaceStr = fmt.Sprintf("%s/namespaces", verPathEncoded)
+ }
+ } else {
+ urlVerNamespaceStr = fmt.Sprintf("%s", verPathEncoded)
+ }
+
+ // Assemble the complete URL: base + version + [namespace] + resource_relative_path
+ Debug(DbgInfo, "basepath: %s, version/namespace path: %s, resource path: %s\n", c.BaseURL.String(), urlVerNamespaceStr, urlRelResource.String())
+ urlStr := fmt.Sprintf("%s/%s/%s", c.BaseURL.String(), urlVerNamespaceStr, urlRelResource.String())
+ requestUrl, err = url.Parse(urlStr)
+ if err != nil {
+ Debug(DbgError, "url.Parse(%s) error: %s\n", urlStr, err)
+ errStr := wski18n.T("Invalid request URL '{{.url}}': {{.err}}",
+ map[string]interface{}{"url": urlStr, "err": err})
+ werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, werr
+ }
+ } else {
+ Debug(DbgInfo, "basepath: %s, resource path: %s\n", c.BaseURL.String(), urlRelResource.String())
+ urlStr := fmt.Sprintf("%s/%s", c.BaseURL.String(), urlRelResource.String())
+ requestUrl, err = url.Parse(urlStr)
+ if err != nil {
+ Debug(DbgError, "url.Parse(%s) error: %s\n", urlStr, err)
+ errStr := wski18n.T("Invalid request URL '{{.url}}': {{.err}}",
+ map[string]interface{}{"url": urlStr, "err": err})
+ werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, werr
+ }
+ }
+
+ var buf io.ReadWriter
+ if body != nil {
+ if (encodeBodyAs == EncodeBodyAsJson) {
+ buf = new(bytes.Buffer)
+ encoder := json.NewEncoder(buf)
+ encoder.SetEscapeHTML(false)
+ err := encoder.Encode(body)
+
+ if err != nil {
+ Debug(DbgError, "json.Encode(%#v) error: %s\n", body, err)
+ errStr := wski18n.T("Error encoding request body: {{.err}}",
+ map[string]interface{}{"err": err})
+ werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, werr
+ }
+ } else if (encodeBodyAs == EncodeBodyAsFormData) {
+ if values, ok := body.(url.Values); ok {
+ buf = bytes.NewBufferString(values.Encode())
+ } else {
+ Debug(DbgError, "Invalid form data body: %v\n", body)
+ errStr := wski18n.T("Internal error. Form data encoding failure")
+ werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, werr
+ }
+ } else {
+ Debug(DbgError, "Invalid body encode type: %s\n", encodeBodyAs)
+ errStr := wski18n.T("Internal error. Invalid encoding type '{{.encodetype}}'",
+ map[string]interface{}{"encodetype": encodeBodyAs})
+ werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, werr
+ }
+ }
+
+ req, err := http.NewRequest(method, requestUrl.String(), buf)
+ if err != nil {
+ Debug(DbgError, "http.NewRequest(%v, %s, buf) error: %s\n", method, requestUrl.String(), err)
+ errStr := wski18n.T("Error initializing request: {{.err}}", map[string]interface{}{"err": err})
+ werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, werr
+ }
+ if (req.Body != nil && encodeBodyAs == EncodeBodyAsJson) {
+ req.Header.Add("Content-Type", "application/json")
+ }
+ if (req.Body != nil && encodeBodyAs == EncodeBodyAsFormData) {
+ req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
+ }
+
+ if useAuthentication {
+ err = c.addAuthHeader(req, AuthRequired)
+ if err != nil {
+ Debug(DbgError, "addAuthHeader() error: %s\n", err)
+ errStr := wski18n.T("Unable to add the HTTP authentication header: {{.err}}",
+ map[string]interface{}{"err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, werr
+ }
+ } else {
+ Debug(DbgInfo, "No auth header required\n")
+ }
+
+ return req, nil
+}
diff --git a/tools/cli/go-whisk/whisk/info.go b/tools/cli/go-whisk/whisk/info.go
new file mode 100644
index 0000000..815e753
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/info.go
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk
+
+import (
+ "net/http"
+ "net/url"
+ "errors"
+ "../wski18n"
+ "fmt"
+)
+
+type Info struct {
+ Whisk string `json:"whisk,omitempty"`
+ Version string `json:"version,omitempty"`
+ Build string `json:"build,omitempty"`
+ BuildNo string `json:"buildno,omitempty"`
+}
+
+type InfoService struct {
+ client *Client
+}
+
+func (s *InfoService) Get() (*Info, *http.Response, error) {
+ // make a request to c.BaseURL / v1
+ urlStr := fmt.Sprintf("%s/%s", s.client.BaseURL.String(), s.client.Config.Version)
+ u, err := url.Parse(urlStr)
+ if err != nil {
+ Debug(DbgError, "url.Parse(%s) error: %s\n", urlStr, err)
+ errStr := wski18n.T("Unable to URL parse '{{.version}}': {{.err}}",
+ map[string]interface{}{"version": urlStr, "err": err})
+ werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ req, err := http.NewRequest("GET", u.String(), nil)
+ if err != nil {
+ Debug(DbgError, "http.NewRequest(GET, %s) error: %s\n", u.String(), err)
+ errStr := wski18n.T("Unable to create HTTP request for GET '{{.url}}': {{.err}}",
+ map[string]interface{}{"url": u.String(), "err": err})
+ werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ Debug(DbgInfo, "Sending HTTP URL '%s'; req %#v\n", req.URL.String(), req)
+ info := new(Info)
+ resp, err := s.client.Do(req, &info, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+ return nil, nil, err
+ }
+
+ return info, resp, nil
+}
diff --git a/tools/cli/go-whisk/whisk/namespace.go b/tools/cli/go-whisk/whisk/namespace.go
new file mode 100644
index 0000000..5db8e2a
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/namespace.go
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk
+
+import (
+ "net/http"
+ "errors"
+ "../wski18n"
+)
+
+type Namespace struct {
+ Name string `json:"name"`
+ Contents `json:"contents,omitempty"`
+}
+
+type Contents struct {
+ Actions []Action `json:"actions"`
+ Packages []Package `json:"packages"`
+ Triggers []Trigger `json:"triggers"`
+ Rules []Rule `json:"rules"`
+}
+
+type NamespaceService struct {
+ client *Client
+}
+
+// get a list of available namespaces
+func (s *NamespaceService) List() ([]Namespace, *http.Response, error) {
+ // make a request to c.BaseURL / namespaces
+
+ // Create the request against the namespaces resource
+ s.client.Config.Namespace = ""
+ route := ""
+ req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl)
+ if err != nil {
+ Debug(DbgError, "s.client.NewRequest(GET) error: %s\n", err)
+ errStr := wski18n.T("Unable to create HTTP request for GET: {{.err}}",
+ map[string]interface{}{"err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ var namespaceNames []string
+ resp, err := s.client.Do(req, &namespaceNames, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ var namespaces []Namespace
+ for _, nsName := range namespaceNames {
+ ns := Namespace{
+ Name: nsName,
+ }
+ namespaces = append(namespaces, ns)
+ }
+
+ Debug(DbgInfo, "Returning []namespaces: %#v\n", namespaces)
+ return namespaces, resp, nil
+}
+
+func (s *NamespaceService) Get(namespace string) (*Namespace, *http.Response, error) {
+
+ if len(namespace) == 0 {
+ namespace = s.client.Config.Namespace
+ }
+
+ s.client.Namespace = namespace
+ resNamespace := &Namespace{
+ Name: namespace,
+ }
+
+ req, err := s.client.NewRequest("GET", "", nil, IncludeNamespaceInUrl)
+ if err != nil {
+ Debug(DbgError, "s.client.NewRequest(GET) error: %s\n", err)
+ errStr := wski18n.T("Unable to create HTTP request for GET: {{.err}}", map[string]interface{}{"err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return resNamespace, nil, werr
+ }
+
+ resp, err := s.client.Do(req, &resNamespace.Contents, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+ return resNamespace, resp, err
+ }
+
+ Debug(DbgInfo, "Returning namespace: %#v\n", resNamespace)
+
+ return resNamespace, resp, nil
+}
diff --git a/tools/cli/go-whisk/whisk/package.go b/tools/cli/go-whisk/whisk/package.go
new file mode 100644
index 0000000..2485f41
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/package.go
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk
+
+import (
+ "fmt"
+ "net/http"
+ "net/url"
+ "errors"
+ "../wski18n"
+)
+
+type PackageService struct {
+ client *Client
+}
+
+type PackageInterface interface {
+ GetName() string
+}
+
+// Use this struct to represent the package/binding sent from the Whisk server
+// Binding is a bool ???MWD20160602 now seeing Binding as a struct???
+type Package struct {
+ Namespace string `json:"namespace,omitempty"`
+ Name string `json:"name,omitempty"`
+ Version string `json:"version,omitempty"`
+ Publish *bool `json:"publish,omitempty"`
+ Annotations KeyValueArr `json:"annotations,omitempty"`
+ Parameters KeyValueArr `json:"parameters,omitempty"`
+ Binding *Binding `json:"binding,omitempty"`
+ Actions []Action `json:"actions,omitempty"`
+ Feeds []Action `json:"feeds,omitempty"`
+}
+func (p *Package) GetName() string {
+ return p.Name
+}
+
+// Use this struct when creating a binding
+// Publish is NOT optional; Binding is a namespace/name object, not a bool
+type BindingPackage struct {
+ Namespace string `json:"-"`
+ Name string `json:"-"`
+ Version string `json:"version,omitempty"`
+ Publish *bool `json:"publish,omitempty"`
+ Annotations KeyValueArr `json:"annotations,omitempty"`
+ Parameters KeyValueArr `json:"parameters,omitempty"`
+ Binding `json:"binding"`
+}
+func (p *BindingPackage) GetName() string {
+ return p.Name
+}
+
+type Binding struct {
+ Namespace string `json:"namespace,omitempty"`
+ Name string `json:"name,omitempty"`
+}
+
+type BindingUpdates struct {
+ Added []string `json:"added,omitempty"`
+ Updated []string `json:"updated,omitempty"`
+ Deleted []string `json:"deleted,omitempty"`
+}
+
+type PackageListOptions struct {
+ Public bool `url:"public,omitempty"`
+ Limit int `url:"limit"`
+ Skip int `url:"skip"`
+ Since int `url:"since,omitempty"`
+ Docs bool `url:"docs,omitempty"`
+}
+
+func (s *PackageService) List(options *PackageListOptions) ([]Package, *http.Response, error) {
+ route := fmt.Sprintf("packages")
+ routeUrl, err := addRouteOptions(route, options)
+ if err != nil {
+ Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+ errStr := wski18n.T("Unable to build request URL: {{.err}}", map[string]interface{}{"err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ req, err := s.client.NewRequestUrl("GET", routeUrl, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+ if err != nil {
+ Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired); error: '%s'\n", route, err)
+ errStr := wski18n.T("Unable to create GET HTTP request for '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ var packages []Package
+ resp, err := s.client.Do(req, &packages, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return packages, resp, err
+
+}
+
+func (s *PackageService) Get(packageName string) (*Package, *http.Response, error) {
+ // Encode resource name as a path (with no query params) before inserting it into the URI
+ // This way any '?' chars in the name won't be treated as the beginning of the query params
+ packageName = (&url.URL{Path: packageName}).String()
+ route := fmt.Sprintf("packages/%s", packageName)
+
+ req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl)
+ if err != nil {
+ Debug(DbgError, "http.NewRequest(GET, %s); error: '%s'\n", route, err)
+ errStr := wski18n.T("Unable to create GET HTTP request for '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ p := new(Package)
+ resp, err := s.client.Do(req, &p, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return p, resp, nil
+
+}
+
+func (s *PackageService) Insert(x_package PackageInterface, overwrite bool) (*Package, *http.Response, error) {
+ // Encode resource name as a path (with no query params) before inserting it into the URI
+ // This way any '?' chars in the name won't be treated as the beginning of the query params
+ packageName := (&url.URL{Path: x_package.GetName()}).String()
+ route := fmt.Sprintf("packages/%s?overwrite=%t", packageName, overwrite)
+
+ req, err := s.client.NewRequest("PUT", route, x_package, IncludeNamespaceInUrl)
+ if err != nil {
+ Debug(DbgError, "http.NewRequest(PUT, %s); error: '%s'\n", route, err)
+ errStr := wski18n.T("Unable to create PUT HTTP request for '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ p := new(Package)
+ resp, err := s.client.Do(req, &p, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return p, resp, nil
+}
+
+func (s *PackageService) Delete(packageName string) (*http.Response, error) {
+ // Encode resource name as a path (with no query params) before inserting it into the URI
+ // This way any '?' chars in the name won't be treated as the beginning of the query params
+ packageName = (&url.URL{Path: packageName}).String()
+ route := fmt.Sprintf("packages/%s", packageName)
+
+ req, err := s.client.NewRequest("DELETE", route, nil, IncludeNamespaceInUrl)
+ if err != nil {
+ Debug(DbgError, "http.NewRequest(DELETE, %s); error: '%s'\n", route, err)
+ errStr := wski18n.T("Unable to create DELETE HTTP request for '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, werr
+ }
+
+ resp, err := s.client.Do(req, nil, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+ return resp, err
+ }
+
+ return resp, nil
+}
+
+func (s *PackageService) Refresh() (*BindingUpdates, *http.Response, error) {
+ route := "packages/refresh"
+
+ req, err := s.client.NewRequest("POST", route, nil, IncludeNamespaceInUrl)
+ if err != nil {
+ Debug(DbgError, "http.NewRequest(POST, %s); error: '%s'\n", route, err)
+ errStr := wski18n.T("Unable to create POST HTTP request for '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ updates := &BindingUpdates{}
+ resp, err := s.client.Do(req, updates, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return updates, resp, nil
+}
diff --git a/tools/cli/go-whisk/whisk/rule.go b/tools/cli/go-whisk/whisk/rule.go
new file mode 100644
index 0000000..eb6d04d
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/rule.go
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+ "errors"
+ "net/url"
+ "../wski18n"
+)
+
+type RuleService struct {
+ client *Client
+}
+
+type Rule struct {
+ Namespace string `json:"namespace,omitempty"`
+ Name string `json:"name,omitempty"`
+ Version string `json:"version,omitempty"`
+ Status string `json:"status"`
+ Trigger interface{} `json:"trigger"`
+ Action interface{} `json:"action"`
+ Publish *bool `json:"publish,omitempty"`
+
+}
+
+type RuleListOptions struct {
+ Limit int `url:"limit"`
+ Skip int `url:"skip"`
+ Docs bool `url:"docs,omitempty"`
+}
+
+func (s *RuleService) List(options *RuleListOptions) ([]Rule, *http.Response, error) {
+ route := "rules"
+ routeUrl, err := addRouteOptions(route, options)
+ if err != nil {
+ Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+ errStr := wski18n.T("Unable to append options '{{.options}}' to URL route '{{.route}}': {{.err}}",
+ map[string]interface{}{"options": fmt.Sprintf("%#v", options), "route": route, "err": err})
+ werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ req, err := s.client.NewRequestUrl("GET", routeUrl, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+ if err != nil {
+ Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired); error: '%s'\n", route, err)
+ errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ var rules []Rule
+ resp, err := s.client.Do(req, &rules, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return rules, resp, err
+}
+
+func (s *RuleService) Insert(rule *Rule, overwrite bool) (*Rule, *http.Response, error) {
+ // Encode resource name as a path (with no query params) before inserting it into the URI
+ // This way any '?' chars in the name won't be treated as the beginning of the query params
+ ruleName := (&url.URL{Path: rule.Name}).String()
+ route := fmt.Sprintf("rules/%s?overwrite=%t", ruleName, overwrite)
+
+ req, err := s.client.NewRequest("PUT", route, rule, IncludeNamespaceInUrl)
+ if err != nil {
+ Debug(DbgError, "http.NewRequest(PUT, %s); error: '%s'\n", route, err)
+ errStr := wski18n.T("Unable to create HTTP request for PUT '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ r := new(Rule)
+ resp, err := s.client.Do(req, &r, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return r, resp, nil
+}
+
+func (s *RuleService) Get(ruleName string) (*Rule, *http.Response, error) {
+ // Encode resource name as a path (with no query params) before inserting it into the URI
+ // This way any '?' chars in the name won't be treated as the beginning of the query params
+ ruleName = (&url.URL{Path: ruleName}).String()
+ route := fmt.Sprintf("rules/%s", ruleName)
+
+ req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl)
+ if err != nil {
+ Debug(DbgError, "http.NewRequest(GET, %s); error: '%s'\n", route, err)
+ errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ r := new(Rule)
+ resp, err := s.client.Do(req, &r, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return r, resp, nil
+}
+
+func (s *RuleService) Delete(ruleName string) (*http.Response, error) {
+ // Encode resource name as a path (with no query params) before inserting it into the URI
+ // This way any '?' chars in the name won't be treated as the beginning of the query params
+ ruleName = (&url.URL{Path: ruleName}).String()
+ route := fmt.Sprintf("rules/%s", ruleName)
+
+ req, err := s.client.NewRequest("DELETE", route, nil, IncludeNamespaceInUrl)
+ if err != nil {
+ Debug(DbgError, "http.NewRequest(DELETE, %s); error: '%s'\n", route, err)
+ errStr := wski18n.T("Unable to create HTTP request for DELETE '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, werr
+ }
+
+ resp, err := s.client.Do(req, nil, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+ return resp, err
+ }
+
+ return resp, nil
+}
+
+func (s *RuleService) SetState(ruleName string, state string) (*Rule, *http.Response, error) {
+ state = strings.ToLower(state)
+ if state != "active" && state != "inactive" {
+ errStr := wski18n.T("Internal error. Invalid state option '{{.state}}'. Valid options are \"active\" and \"inactive\".",
+ map[string]interface{}{"state": state})
+ werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ // Encode resource name as a path (with no query params) before inserting it into the URI
+ // This way any '?' chars in the name won't be treated as the beginning of the query params
+ ruleName = (&url.URL{Path: ruleName}).String()
+ route := fmt.Sprintf("rules/%s", ruleName)
+
+ ruleState := &Rule{ Status: state }
+
+ req, err := s.client.NewRequest("POST", route, ruleState, IncludeNamespaceInUrl)
+ if err != nil {
+ Debug(DbgError, "http.NewRequest(POST, %s); error: '%s'\n", route, err)
+ errStr := wski18n.T("Unable to create HTTP request for POST '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ r := new(Rule)
+ resp, err := s.client.Do(req, &r, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return r, resp, nil
+}
diff --git a/tools/cli/go-whisk/whisk/sdk.go b/tools/cli/go-whisk/whisk/sdk.go
new file mode 100644
index 0000000..d4e2859
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/sdk.go
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk
+
+import (
+ "fmt"
+ "errors"
+ "net/http"
+ "../wski18n"
+)
+
+type SdkService struct {
+ client *Client
+}
+
+// Structure for SDK request responses
+type Sdk struct {
+ // TODO :: Add SDK fields
+}
+
+type SdkRequest struct {
+ // TODO :: Add SDK
+}
+
+// Install artifact {component = docker || swift || iOS}
+func (s *SdkService) Install(relFileUrl string) (*http.Response, error) {
+
+ urlStr := fmt.Sprintf("https://%s/%s", s.client.Config.BaseURL.Host, relFileUrl)
+
+ req, err := http.NewRequest("GET", urlStr, nil)
+ if err != nil {
+ Debug(DbgError, "http.NewRequest(GET, %s, nil) error: %s\n", urlStr, err)
+ errStr := wski18n.T("Unable to create HTTP request for GET '{{.url}}': {{.err}}",
+ map[string]interface{}{"url": urlStr, "err": err})
+ werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, werr
+ }
+
+ if IsVerbose() {
+ fmt.Println("REQUEST:")
+ fmt.Printf("[%s]\t%s\n", req.Method, req.URL)
+ if len(req.Header) > 0 {
+ fmt.Println("Req Headers")
+ printJSON(req.Header)
+ }
+ if req.Body != nil {
+ fmt.Println("Req Body")
+ fmt.Println(req.Body)
+ }
+ }
+
+ // Directly use the HTTP client, not the Whisk CLI client, so that the response body is left alone
+ resp, err := s.client.client.Do(req)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+ return resp, err
+ }
+
+ return resp, nil
+}
diff --git a/tools/cli/go-whisk/whisk/shared.go b/tools/cli/go-whisk/whisk/shared.go
new file mode 100644
index 0000000..46094d6
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/shared.go
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk
+
+import "encoding/json"
+
+type KeyValue struct {
+ Key string `json:"key"`
+ Value interface{} `json:"value"`
+}
+
+type KeyValueArr []KeyValue
+
+type Annotations []map[string]interface{}
+
+type Parameters *json.RawMessage
+
+type Limits struct {
+ Timeout *int `json:"timeout,omitempty"`
+ Memory *int `json:"memory,omitempty"`
+ Logsize *int `json:"logs,omitempty"`
+}
diff --git a/tools/cli/go-whisk/whisk/trace.go b/tools/cli/go-whisk/whisk/trace.go
new file mode 100644
index 0000000..1e35417
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/trace.go
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk
+
+import (
+ "fmt"
+ "runtime"
+ "strings"
+ "os"
+)
+
+type DebugLevel string
+const (
+ DbgInfo DebugLevel = "Inf"
+ DbgWarn DebugLevel = "Wrn"
+ DbgError DebugLevel = "Err"
+ DbgFatal DebugLevel = "Ftl"
+)
+
+const MaxNameLen int = 25
+
+var isVerbose bool
+var isDebug bool
+
+func init() {
+ if len(os.Getenv("WSK_CLI_DEBUG")) > 0 { // Useful for tracing init() code, before parms are parsed
+ SetDebug(true)
+ }
+}
+
+func SetDebug(b bool) {
+ isDebug = b
+}
+
+func SetVerbose (b bool) {
+ isVerbose = b
+}
+
+func IsVerbose() bool {
+ return isVerbose || isDebug
+}
+
+/* Function for tracing debug level messages to stdout
+ Output format:
+ [file-or-function-name]:line-#:[DebugLevel] The formated message without any appended \n
+ */
+func Debug(dl DebugLevel, msgFormat string, args ...interface{}) {
+ if isDebug {
+ pc, file, line, _ := runtime.Caller(1)
+ fcn := runtime.FuncForPC(pc)
+ msg := fmt.Sprintf(msgFormat, args...)
+ fcnName := fcn.Name()
+
+ // Cobra command Run/RunE functions are anonymous, so the function name is unfriendly,
+ // so use a file name instead
+ if strings.Contains(fcnName, "commands.glob.") || strings.Contains(fcnName, "whisk.glob.") {
+ fcnName = file
+ }
+
+ // Only interesting the the trailing function/file name characters
+ if len(fcnName) > MaxNameLen {
+ fcnName = fcnName[len(fcnName)-MaxNameLen:]
+ }
+ fmt.Printf("[%-25s]:%03d:[%3s] %v", fcnName, line, dl, msg)
+ }
+}
+
+/* Function for tracing debug level messages to stdout
+ Output format:
+ [file-or-function-name]:line-#:[DebugLevel] The formated message without any appended \n
+ */
+func Verbose(msgFormat string, args ...interface{}) {
+ if IsVerbose() {
+ msg := fmt.Sprintf(msgFormat, args...)
+ fmt.Printf("%v", msg)
+ }
+}
+
+
diff --git a/tools/cli/go-whisk/whisk/trigger.go b/tools/cli/go-whisk/whisk/trigger.go
new file mode 100644
index 0000000..6d800e9
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/trigger.go
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk
+
+import (
+ "fmt"
+ "net/http"
+ "errors"
+ "net/url"
+ "../wski18n"
+)
+
+type TriggerService struct {
+ client *Client
+}
+
+type Trigger struct {
+ Namespace string `json:"namespace,omitempty"`
+ Name string `json:"name,omityempty"`
+ Version string `json:"version,omitempty"`
+ ActivationId string `json:"activationId,omitempty"`
+ Annotations KeyValueArr `json:"annotations,omitempty"`
+ Parameters KeyValueArr `json:"parameters,omitempty"`
+ Limits *Limits `json:"limits,omitempty"`
+ Publish *bool `json:"publish,omitempty"`
+
+}
+
+type TriggerListOptions struct {
+ Limit int `url:"limit"`
+ Skip int `url:"skip"`
+ Docs bool `url:"docs,omitempty"`
+}
+
+func (s *TriggerService) List(options *TriggerListOptions) ([]Trigger, *http.Response, error) {
+ route := "triggers"
+ routeUrl, err := addRouteOptions(route, options)
+ if err != nil {
+ Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+ errStr := wski18n.T("Unable to append options '{{.options}}' to URL route '{{.route}}': {{.err}}",
+ map[string]interface{}{"options": fmt.Sprintf("%#v", options), "route": route, "err": err})
+ werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ req, err := s.client.NewRequestUrl("GET", routeUrl, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+ if err != nil {
+ Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired); error: '%s'\n", route, err)
+ errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ var triggers []Trigger
+ resp, err := s.client.Do(req, &triggers, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return triggers, resp, nil
+}
+
+func (s *TriggerService) Insert(trigger *Trigger, overwrite bool) (*Trigger, *http.Response, error) {
+ // Encode resource name as a path (with no query params) before inserting it into the URI
+ // This way any '?' chars in the name won't be treated as the beginning of the query params
+ triggerName := (&url.URL{Path: trigger.Name}).String()
+ route := fmt.Sprintf("triggers/%s?overwrite=%t", triggerName, overwrite)
+
+ routeUrl, err := url.Parse(route)
+ if err != nil {
+ Debug(DbgError, "url.Parse(%s) error: %s\n", route, err)
+ errStr := wski18n.T("Invalid request URL '{{.url}}': {{.err}}",
+ map[string]interface{}{"url": route, "err": err})
+ werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ req, err := s.client.NewRequestUrl("PUT", routeUrl, trigger, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+ if err != nil {
+ Debug(DbgError, "http.NewRequestUrl(PUT, %s, %+v, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired); error: '%s'\n", routeUrl, trigger, err)
+ errStr := wski18n.T("Unable to create HTTP request for PUT '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": routeUrl, "err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ t := new(Trigger)
+ resp, err := s.client.Do(req, &t, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return t, resp, nil
+
+}
+
+func (s *TriggerService) Get(triggerName string) (*Trigger, *http.Response, error) {
+ // Encode resource name as a path (with no query params) before inserting it into the URI
+ // This way any '?' chars in the name won't be treated as the beginning of the query params
+ triggerName = (&url.URL{Path: triggerName}).String()
+ route := fmt.Sprintf("triggers/%s", triggerName)
+
+ req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl)
+ if err != nil {
+ Debug(DbgError, "http.NewRequest(GET, %s); error: '%s'\n", route, err)
+ errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ t := new(Trigger)
+ resp, err := s.client.Do(req, &t, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return t, resp, nil
+
+}
+
+func (s *TriggerService) Delete(triggerName string) (*Trigger, *http.Response, error) {
+ // Encode resource name as a path (with no query params) before inserting it into the URI
+ // This way any '?' chars in the name won't be treated as the beginning of the query params
+ triggerName = (&url.URL{Path: triggerName}).String()
+ route := fmt.Sprintf("triggers/%s", triggerName)
+
+ req, err := s.client.NewRequest("DELETE", route, nil, IncludeNamespaceInUrl)
+ if err != nil {
+ Debug(DbgError, "http.NewRequest(DELETE, %s); error: '%s'\n", route, err)
+ errStr := wski18n.T("Unable to create HTTP request for DELETE '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ t := new(Trigger)
+ resp, err := s.client.Do(req, &t, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return t, resp, nil
+}
+
+func (s *TriggerService) Fire(triggerName string, payload interface{}) (*Trigger, *http.Response, error) {
+ // Encode resource name as a path (with no query params) before inserting it into the URI
+ // This way any '?' chars in the name won't be treated as the beginning of the query params
+ triggerName = (&url.URL{Path: triggerName}).String()
+ route := fmt.Sprintf("triggers/%s", triggerName)
+
+ req, err := s.client.NewRequest("POST", route, payload, IncludeNamespaceInUrl)
+ if err != nil {
+ Debug(DbgError," http.NewRequest(POST, %s); error: '%s'\n", route, err)
+ errStr := wski18n.T("Unable to create HTTP request for POST '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, nil, werr
+ }
+
+ t := new(Trigger)
+ resp, err := s.client.Do(req, &t, ExitWithSuccessOnTimeout)
+ if err != nil {
+ Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+ return nil, resp, err
+ }
+
+ return t, resp, nil
+}
diff --git a/tools/cli/go-whisk/whisk/util.go b/tools/cli/go-whisk/whisk/util.go
new file mode 100644
index 0000000..ce78697
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/util.go
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk
+
+import (
+ "errors"
+ "fmt"
+ "net/url"
+ "reflect"
+
+ "github.com/fatih/color"
+ "github.com/google/go-querystring/query"
+ "github.com/hokaccha/go-prettyjson"
+ "../wski18n"
+)
+
+// addOptions adds the parameters in opt as URL query parameters to s. opt
+// must be a struct whose fields may contain "url" tags.
+func addRouteOptions(route string, options interface{}) (*url.URL, error) {
+ Debug(DbgInfo, "Adding options %+v to route '%s'\n", options, route)
+ u, err := url.Parse(route)
+ if err != nil {
+ Debug(DbgError,"url.Parse(%s) error: %s\n", route, err)
+ errStr := wski18n.T("Unable to parse URL '{{.route}}': {{.err}}",
+ map[string]interface{}{"route": route, "err": err})
+ werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, werr
+ }
+
+ v := reflect.ValueOf(options)
+ if v.Kind() == reflect.Ptr && v.IsNil() {
+ return u, nil
+ }
+
+ qs, err := query.Values(options)
+ if err != nil {
+ Debug(DbgError,"query.Values(%#v) error: %s\n", options, err)
+ errStr := wski18n.T("Unable to process URL query options '{{.options}}': {{.err}}",
+ map[string]interface{}{"options": fmt.Sprintf("%#v", options), "err": err})
+ werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+ return nil, werr
+ }
+
+ u.RawQuery = qs.Encode()
+ Debug(DbgInfo,"Returning route options '%s' from input struct %+v\n", u.String(), options)
+ return u, nil
+}
+
+func printJSON(v interface{}) {
+ output, _ := prettyjson.Marshal(v)
+ fmt.Fprintln(color.Output, string(output))
+}
diff --git a/tools/cli/go-whisk/whisk/wskerror.go b/tools/cli/go-whisk/whisk/wskerror.go
new file mode 100644
index 0000000..6969152
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/wskerror.go
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk
+
+const EXITCODE_ERR_GENERAL int = 1
+const EXITCODE_ERR_USAGE int = 2
+const EXITCODE_ERR_NETWORK int = 3
+const EXITCODE_ERR_HTTP_RESP int = 4
+const NOT_ALLOWED int = 149
+const EXITCODE_TIMED_OUT int = 202
+
+const DISPLAY_MSG bool = true
+const NO_DISPLAY_MSG bool = false
+const DISPLAY_USAGE bool = true
+const NO_DISPLAY_USAGE bool = false
+const NO_MSG_DISPLAYED bool = false
+const DISPLAY_PREFIX bool = true
+const NO_DISPLAY_PREFIX bool = false
+const APPLICATION_ERR bool = true
+const NO_APPLICATION_ERR bool = false
+const TIMED_OUT bool = true
+
+type WskError struct {
+ RootErr error // Parent error
+ ExitCode int // Error code to be returned to the OS
+ DisplayMsg bool // When true, the error message should be displayed to console
+ MsgDisplayed bool // When true, the error message has already been displayed, don't display it again
+ DisplayUsage bool // When true, the CLI usage should be displayed before exiting
+ DisplayPrefix bool // When true, the CLI will prefix an error message with "error: "
+ ApplicationError bool // When true, the error is a result of an application failure
+ TimedOut bool // When True, the error is a result of a timeout
+}
+
+/*
+Prints the error message contained inside an WskError. An error prefix may, or may not be displayed depending on the
+WskError's setting for DisplayPrefix.
+
+Parameters:
+ err - WskError object used to display an error message from
+ */
+func (whiskError WskError) Error() string {
+ return whiskError.RootErr.Error()
+}
+
+/*
+Instantiate a WskError structure
+Parameters:
+ error - RootErr. object implementing the error interface
+ int - ExitCode. Used if error object does not have an exit code OR if ExitCodeOverride is true
+ bool - DisplayMsg. If true, the error message should be displayed on the console
+ bool - DisplayUsage. If true, the command usage syntax/help should be displayed on the console
+ bool - MsgDisplayed. If true, the error message has been displayed on the console
+ bool - DisplayPreview. If true, the error message will be prefixed with "error: "
+ bool - TimedOut. If true, the error is a result of a timeout
+*/
+func MakeWskError (err error, exitCode int, flags ...bool ) (resWhiskError *WskError) {
+ resWhiskError = &WskError{
+ RootErr: err,
+ ExitCode: exitCode,
+ DisplayMsg: false,
+ DisplayUsage: false,
+ MsgDisplayed: false,
+ DisplayPrefix: true,
+ ApplicationError: false,
+ TimedOut: false,
+ }
+
+ if len(flags) > 0 { resWhiskError.DisplayMsg = flags[0] }
+ if len(flags) > 1 { resWhiskError.DisplayUsage = flags[1] }
+ if len(flags) > 2 { resWhiskError.MsgDisplayed = flags[2] }
+ if len(flags) > 3 { resWhiskError.DisplayPrefix = flags[3] }
+ if len(flags) > 4 { resWhiskError.ApplicationError = flags[4] }
+ if len(flags) > 5 { resWhiskError.TimedOut = flags[5] }
+
+ return resWhiskError
+}
+
+/*
+Instantiate a WskError structure
+Parameters:
+ error - RootErr. object implementing the error interface
+ WskError - WskError being wrappered. It's exitcode will be used as this WskError's exitcode. Ignored if nil
+ int - ExitCode. Used if error object is nil or if the error object is not a WskError
+ bool - DisplayMsg. If true, the error message should be displayed on the console
+ bool - DisplayUsage. If true, the command usage syntax/help should be displayed on the console
+ bool - MsgDisplayed. If true, the error message has been displayed on the console
+ bool - ApplicationError. If true, the error is a result of an application error
+ bool - TimedOut. If true, the error resulted from a timeout
+*/
+func MakeWskErrorFromWskError (baseError error, whiskError error, exitCode int, flags ...bool) (resWhiskError *WskError) {
+
+ // Get the exit code, and flags from the existing Whisk error
+ if whiskError != nil {
+
+ // Ensure the Whisk error is a pointer
+ switch errorType := whiskError.(type) {
+ case *WskError:
+ resWhiskError = errorType
+ case WskError:
+ resWhiskError = &errorType
+ }
+
+ if resWhiskError != nil {
+ exitCode, flags = getWhiskErrorProperties(resWhiskError, flags...)
+ }
+ }
+
+ return MakeWskError(baseError, exitCode, flags...)
+}
+
+/*
+Returns the settings from a WskError. Values returned will include ExitCode, DisplayMsg, DisplayUsage, MsgDisplayed,
+DisplayPrefix, TimedOut.
+
+Parameters:
+ whiskError - WskError to examine.
+ flags - Boolean values that may override the WskError object's values for DisplayMsg, DisplayUsage,
+ MsgDisplayed, ApplicationError, TimedOut.
+ */
+func getWhiskErrorProperties(whiskError *WskError, flags ...bool) (int, []bool) {
+ if len(flags) > 0 {
+ flags[0] = whiskError.DisplayMsg
+ } else {
+ flags = append(flags, whiskError.DisplayMsg)
+ }
+
+ if len(flags) > 1 {
+ flags[1] = whiskError.DisplayUsage || flags[1]
+ } else {
+ flags = append(flags, whiskError.DisplayUsage)
+ }
+
+ if len(flags) > 2 {
+ flags[2] = whiskError.MsgDisplayed || flags[2]
+ } else {
+ flags = append(flags, whiskError.MsgDisplayed)
+ }
+
+ if len(flags) > 3 {
+ flags[3] = whiskError.DisplayPrefix || flags[3]
+ } else {
+ flags = append(flags, whiskError.DisplayPrefix)
+ }
+
+ if len(flags) > 4 {
+ flags[4] = whiskError.ApplicationError || flags[4]
+ } else {
+ flags = append(flags, whiskError.ApplicationError)
+ }
+
+ if len(flags) > 5 {
+ flags[5] = whiskError.TimedOut || flags[5]
+ } else {
+ flags = append(flags, whiskError.TimedOut)
+ }
+
+ return whiskError.ExitCode, flags
+}
diff --git a/tools/cli/go-whisk/wski18n/detection.go b/tools/cli/go-whisk/wski18n/detection.go
new file mode 100644
index 0000000..03d9be8
--- /dev/null
+++ b/tools/cli/go-whisk/wski18n/detection.go
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package wski18n
+
+import "github.com/cloudfoundry/jibber_jabber"
+
+type Detector interface {
+ DetectLocale() string
+ DetectLanguage() string
+}
+
+type JibberJabberDetector struct{}
+
+func (d *JibberJabberDetector) DetectLocale() string {
+ userLocale, err := jibber_jabber.DetectIETF()
+ if err != nil {
+ userLocale = ""
+ }
+ return userLocale
+}
+
+func (d *JibberJabberDetector) DetectLanguage() string {
+ lang, err := jibber_jabber.DetectLanguage()
+ if err != nil {
+ lang = ""
+ }
+ return lang
+}
diff --git a/tools/cli/go-whisk/wski18n/i18n.go b/tools/cli/go-whisk/wski18n/i18n.go
new file mode 100644
index 0000000..3c68b5f
--- /dev/null
+++ b/tools/cli/go-whisk/wski18n/i18n.go
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package wski18n
+
+import (
+ "path/filepath"
+ "strings"
+
+ goi18n "github.com/nicksnyder/go-i18n/i18n"
+)
+
+const (
+ DEFAULT_LOCALE = "en_US"
+)
+
+var SUPPORTED_LOCALES = []string{
+ "de_DE",
+ "en_US",
+ "es_ES",
+ "fr_FR",
+ "it_IT",
+ "ja_JA",
+ "ko_KR",
+ "pt_BR",
+ "zh_Hans",
+ "zh_Hant",
+}
+
+var resourcePath = filepath.Join("wski18n", "resources")
+
+func GetResourcePath() string {
+ return resourcePath
+}
+
+func SetResourcePath(path string) {
+ resourcePath = path
+}
+
+var T goi18n.TranslateFunc
+var curLocale string
+
+func init() {
+ curLocale = Init(new(JibberJabberDetector))
+}
+
+func CurLocale() string {
+ return curLocale
+}
+
+func Locale(detector Detector) string {
+
+ // Use default locale until strings are translated
+ /*sysLocale := normalize(detector.DetectLocale())
+ if isSupported(sysLocale) {
+ return sysLocale
+ }
+
+ locale := defaultLocaleForLang(detector.DetectLanguage())
+ if locale != "" {
+ return locale
+ }*/
+
+ return DEFAULT_LOCALE
+}
+
+func Init(detector Detector) string {
+ l := Locale(detector)
+ InitWithLocale(l)
+ return l
+}
+
+func InitWithLocale(locale string) {
+ err := loadFromAsset(locale)
+ if err != nil {
+ panic(err)
+ }
+ T = goi18n.MustTfunc(locale)
+}
+
+func loadFromAsset(locale string) (err error) {
+ assetName := locale + ".all.json"
+ assetKey := filepath.Join(resourcePath, assetName)
+ bytes, err := Asset(assetKey)
+ if err != nil {
+ return
+ }
+ err = goi18n.ParseTranslationFileBytes(assetName, bytes)
+ return
+}
+
+func normalize(locale string) string {
+ locale = strings.ToLower(strings.Replace(locale, "-", "_", 1))
+ for _, l := range SUPPORTED_LOCALES {
+ if strings.EqualFold(locale, l) {
+ return l
+ }
+ }
+ switch locale {
+ case "zh_cn", "zh_sg":
+ return "zh_Hans"
+ case "zh_hk", "zh_tw":
+ return "zh_Hant"
+ }
+ return locale
+}
+
+func isSupported(locale string) bool {
+ for _, l := range SUPPORTED_LOCALES {
+ if strings.EqualFold(locale, l) {
+ return true
+ }
+ }
+ return false
+}
+
+func defaultLocaleForLang(lang string) string {
+ if lang != "" {
+ lang = strings.ToLower(lang)
+ for _, l := range SUPPORTED_LOCALES {
+ if lang == LangOfLocale(l) {
+ return l
+ }
+ }
+ }
+ return ""
+}
+
+func LangOfLocale(locale string) string {
+ if len(locale) < 2 {
+ return ""
+ }
+ return locale[0:2]
+}
diff --git a/tools/cli/go-whisk/wski18n/resources/de_DE.all.json b/tools/cli/go-whisk/wski18n/resources/de_DE.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk/wski18n/resources/en_US.all.json b/tools/cli/go-whisk/wski18n/resources/en_US.all.json
new file mode 100644
index 0000000..e473202
--- /dev/null
+++ b/tools/cli/go-whisk/wski18n/resources/en_US.all.json
@@ -0,0 +1,126 @@
+[
+ {
+ "id": "Unable to add route options '{{.options}}'",
+ "translation": "Unable to add route options: {{.options}}"
+ },
+ {
+ "id": "Unable to create HTTP request for PUT '{{.route}}': {{.err}}",
+ "translation": "Unable to create HTTP request for PUT '{{.route}}': {{.err}}"
+ },
+ {
+ "id": "Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+ "translation": "Unable to create HTTP request for GET '{{.route}}': {{.err}}"
+ },
+ {
+ "id": "Unable to create HTTP request for DELETE '{{.route}}': {{.err}}",
+ "translation": "Unable to create HTTP request for DELETE '{{.route}}': {{.err}}"
+ },
+ {
+ "id": "Unable to create HTTP request for POST '{{.route}}': {{.err}}",
+ "translation": "Unable to create HTTP request for POST '{{.route}}': {{.err}}"
+ },
+ {
+ "id": "Unable to append options '{{.options}}' to URL route '{{.route}}': {{.err}}",
+ "translation": "Unable to append options '{{.options}}' to URL route '{{.route}}': {{.err}}"
+ },
+ {
+ "id": "Unable to create request URL '{{.url}}': {{.err}}",
+ "translation": "Unable to create request URL '{{.url}}': {{.err}}"
+ },
+ {
+ "id": "Invalid request URL '{{.url}}': {{.err}}",
+ "translation": "Invalid request URL '{{.url}}': {{.err}}"
+ },
+ {
+ "id": "Error encoding request body: {{.err}}",
+ "translation": "Error encoding request body: {{.err}}"
+ },
+ {
+ "id": "Error initializing request: {{.err}}",
+ "translation": "Error initializing request: {{.err}}"
+ },
+ {
+ "id": "Unable to add the HTTP authentication header: {{.err}}",
+ "translation": "Unable to add the HTTP authentication header: {{.err}}"
+ },
+ {
+ "id": "Authorization key is not configured (--auth is required)",
+ "translation": "Authorization key is not configured (--auth is required)"
+ },
+ {
+ "id": "Command failed due to an HTTP failure",
+ "translation": "Command failed due to an HTTP failure"
+ },
+ {
+ "id": "No interface provided; no HTTP response body expected",
+ "translation": "No interface provided; no HTTP response body expected"
+ },
+ {
+ "id": "Command failed due to an internal failure",
+ "translation": "Command failed due to an internal failure"
+ },
+ {
+ "id": "The following application error was received: {{.err}}",
+ "translation": "The following application error was received: {{.err}}"
+ },
+ {
+ "id": "Invalid request URL '{{.url}}': {{.err}}",
+ "translation": "Invalid request URL '{{.url}}': {{.err}}"
+ },
+ {
+ "id": "Unable to add the HTTP authentication header: {{.err}}",
+ "translation": "Unable to add the HTTP authentication header: {{.err}}"
+ },
+ {
+ "id": "Unable to URL parse '{{.version}}': {{.err}}",
+ "translation": "Unable to URL parse '{{.version}}': {{.err}}"
+ },
+ {
+ "id": "Unable to create HTTP request for GET '{{.url}}': {{.err}}",
+ "translation": "Unable to create HTTP request for GET '{{.url}}': {{.err}}"
+ },
+ {
+ "id": "Unable to create HTTP request for GET: {{.err}}",
+ "translation": "Unable to create HTTP request for GET: {{.err}}"
+ },
+ {
+ "id": "Unable to build request URL: {{.err}}",
+ "translation": "Unable to build request URL: {{.err}}"
+ },
+ {
+ "id": "Unable to create GET HTTP request for '{{.route}}': {{.err}}",
+ "translation": "Unable to create GET HTTP request for '{{.route}}': {{.err}}"
+ },
+ {
+ "id": "Unable to create PUT HTTP request for '{{.route}}': {{.err}}",
+ "translation": "Unable to create PUT HTTP request for '{{.route}}': {{.err}}"
+ },
+ {
+ "id": "Unable to create DELETE HTTP request for '{{.route}}': {{.err}}",
+ "translation": "Unable to create DELETE HTTP request for '{{.route}}': {{.err}}"
+ },
+ {
+ "id": "Unable to create POST HTTP request for '{{.route}}': {{.err}}",
+ "translation": "Unable to create POST HTTP request for '{{.route}}': {{.err}}"
+ },
+ {
+ "id": "Internal error. Invalid state option '{{.state}}'. Valid options are \"active\" and \"inactive\".",
+ "translation": "Internal error. Invalid state option '{{.state}}'. Valid options are \"active\" and \"inactive\"."
+ },
+ {
+ "id": "Unable to parse URL '{{.route}}': {{.err}}",
+ "translation": "Unable to parse URL '{{.route}}': {{.err}}"
+ },
+ {
+ "id": "Unable to process URL query options '{{.options}}': {{.err}}",
+ "translation": "Unable to process URL query options '{{.options}}': {{.err}}"
+ },
+ {
+ "id": "{{.msg}} (code {{.code}})",
+ "translation": "{{.msg}} (code {{.code}})"
+ },
+ {
+ "id": "The connection failed, or timed out. (HTTP status code {{.code}})",
+ "translation": "The connection failed, or timed out. (HTTP status code {{.code}})"
+ }
+]
diff --git a/tools/cli/go-whisk/wski18n/resources/es_ES.all.json b/tools/cli/go-whisk/wski18n/resources/es_ES.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk/wski18n/resources/fr_FR.all.json b/tools/cli/go-whisk/wski18n/resources/fr_FR.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk/wski18n/resources/it_IT.all.json b/tools/cli/go-whisk/wski18n/resources/it_IT.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk/wski18n/resources/ja_JA.all.json b/tools/cli/go-whisk/wski18n/resources/ja_JA.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk/wski18n/resources/ko_KR.all.json b/tools/cli/go-whisk/wski18n/resources/ko_KR.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk/wski18n/resources/pt_BR.all.json b/tools/cli/go-whisk/wski18n/resources/pt_BR.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk/wski18n/resources/zh_Hans.all.json b/tools/cli/go-whisk/wski18n/resources/zh_Hans.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk/wski18n/resources/zh_Hant.all.json b/tools/cli/go-whisk/wski18n/resources/zh_Hant.all.json
new file mode 100644
index 0000000..e69de29
--
To stop receiving notification emails like this one, please contact
['"commits@openwhisk.apache.org" <co...@openwhisk.apache.org>'].