Merging the repositories of openwhisk-cli and historic go-whisk-cli.
diff --git a/.appveyor.yml b/.appveyor.yml
new file mode 100644
index 0000000..d4c2d15
--- /dev/null
+++ b/.appveyor.yml
@@ -0,0 +1,13 @@
+clone_folder: c:\gopath\src\github.com\openwhisk\openwhisk-cli
+
+environment:
+ GOPATH: c:\gopath
+
+build: false
+
+install:
+ - echo %PATH%
+ - echo %GOPATH%
+ - set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
+ - go version
+ - go env
\ No newline at end of file
diff --git a/.idea/libraries/GOPATH__openwhisk_cli_.xml b/.idea/libraries/GOPATH__openwhisk_cli_.xml
new file mode 100644
index 0000000..a0e31a5
--- /dev/null
+++ b/.idea/libraries/GOPATH__openwhisk_cli_.xml
@@ -0,0 +1,18 @@
+<component name="libraryTable">
+ <library name="GOPATH <openwhisk-cli>">
+ <CLASSES>
+ <root url="file://$USER_HOME$/go/src/gopkg.in" />
+ <root url="file://$USER_HOME$/go/src/github.com" />
+ <root url="file://$USER_HOME$/go/src/golang.org" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="file://$USER_HOME$/go/src/gopkg.in" />
+ <root url="file://$USER_HOME$/go/src/github.com" />
+ <root url="file://$USER_HOME$/go/src/golang.org" />
+ </SOURCES>
+ <excluded>
+ <root url="file://$PROJECT_DIR$" />
+ </excluded>
+ </library>
+</component>
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..d32d55d
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,73 @@
+# A Travis CI configuration file.
+
+language: go
+
+matrix:
+ include:
+ - os: linux
+ sudo: required
+ go: 1.7
+ env:
+ secure: 0c6mnduuUXScXl4MWyrT1oAk22SqAGzc+7KNuWUtk6berX189UzVvVY919HS/it/gHgMCMWlixoChzh9k2qYeCeDgTwR1rYWuA1Q/ACNL07vCtCwOMTihdVrOz53dy5l82AJC86cfH8Zr0cpNOyRCbCoSozmJ4GQ0lWG5lbSUD1BbNnENjP6IYAxIOfDid75UZU0iA/ebI27xImATYSrIlK1zImtKH/YYeqbsGq8jP0p9A8mg8ey0ECin+Z26AXiy9UTSvj3ujgxaS0nPwEmXu5FcA6Ad0Pr78TGvBcnv92s+MHBQfnPUuQjibrRPAquxsGN9GCWZ58gWPq4+Fg67B63dR1wtSmNEIIbXkLVuZnb2zuhh8KEHuUDVTZLjgzpVcR0J2NtDGsC09HDb88SHrGV+eFejrUVny8g/nPmvmMofX2IAhDuam7rEGVOUdY3lLDC00uZprDjZdXUBto83VmOG5OGPQ7Xe5tz4Ek+s2y3iTq9qW+BrwLO55SFYe+BTDdfSY1HiPVfc+bRbScPiOYXr+080g9zFulCePlZPrWrBCtALiPE69etGrj5FooY9+gQidpfKJ03Qy8vG4l8wl4kHjnGzFwkbnuAHoEzjMaImvjl5nAC/24z9bVUau3AMKmqZ9XrTjSpGgo10MVZwSnA+kWj7wYLKaQcCWceLrU=
+ services: docker
+ - os: osx
+ go: 1.7
+
+git:
+ depth: 3
+
+install:
+ - export DEPLOY_BUILD_READY=false
+ - go get -u github.com/golang/lint/golint
+ - go get -u github.com/stretchr/testify
+ - go get -u github.com/spf13/viper
+
+script:
+ - make lint
+ - make build
+ - make test
+ - export PATH=$PATH:$TRAVIS_BUILD_DIR;
+ - make native_test;
+ - if [ "$TRAVIS_OS_NAME" == "linux" ] ; then
+ export OPENWHISK_HOME="$(dirname "$TRAVIS_BUILD_DIR")/openwhisk";
+ ./tools/travis/install_openwhisk.sh;
+ make integration_test;
+ fi
+
+after_script:
+ - make clean
+
+after_success:
+ - DEPLOY_BUILD_READY=true
+ # This tag is automatically generated for the latest merged commit in master branch.
+ - if [ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_EVENT_TYPE" == "push" ] && [ "$TRAVIS_OS_NAME" == "linux" ] ; then
+ git config --global user.email "builds@travis-ci.com";
+ git config --global user.name "Travis CI";
+ export GIT_TAG="latest";
+ git tag -d $GIT_TAG;
+ git tag $GIT_TAG -a -m "Generated tag from Travis CI build $TRAVIS_BUILD_NUMBER";
+ git push -f -q https://$API_TOKEN@github.com/openwhisk/openwhisk-cli $GIT_TAG;
+ fi
+
+before_deploy:
+ - go get github.com/inconshreveable/mousetrap
+ - go get github.com/mattn/go-isatty
+ - export build_file_name=wsk
+ - export zip_file_name=OpenWhisk_CLI
+ - chmod +x tools/travis/build_tag_releases.sh
+ - ./tools/travis/build_tag_releases.sh $build_file_name $zip_file_name
+ - export RELEASE_PKG_FILE=$(ls $zip_file_name-*.zip)
+ - echo "Deploying $RELEASE_PKG_FILE to GitHub releases."
+
+deploy:
+ provider: releases
+ api_key:
+ secure: Yh1aYiM/qIWkPMSVjGUq1g9TjpACjavQ00QAqp4oqghNZc6xBcmdzsfD2VjzVPHleNI1FIZyjJ1x6laRfWBzRkAcJcjUHXA2bO/V0jqePVmgVm75WwTZ/9EaWIJeAg5CQMm5DGS28Yhc60C0ut3ZzKMWGTiKb73UADXPTGd/tjndxjfksX/THXPuInKB9QZesmluBAC2am/x/6J311WA2wqe0p1+9JFwMr8XwIcwzCwgi/d9CFpS1RnVpLE/ORSgmN/dFbZ7A/qVbx377QoxKiEB0jmUwi13f7REFAw18JdgzbQCH3X4HNu9pCJwHEAq3lP2CfmHbAXcViBeji/Xh9PPJVV9TYqO+uT8oPxCPJND1A/3O2xJ8LyZ/FP2bWqG/Ds/8SZCvxfOR/X77opUeZ4qAp7HJMVCsFi3TsnmzxCe0BOxCppVJLhoSZ2rOAPJi9mKgS/Z/VA5VhNNmnPtkReEWK4vT9h3/iCwv9anvC0RKeLckSHpCm5C5otNXtV4L990fL5L5krMatxynHnCmmhYeLg/Ns+5ncax58Y8hmhnhzTqbPGHpe79bJRfvwRI9lboq7kEj4x5O/M16TKRfQ8ZU5UHvrCPdlTfT7NUXRGZkvWX20X6Ta/DRROTF+xZGiq7da3Oi+xyNDx/LmymfR49thjzgIPXVZolknGYQ9Q=
+ file_glob: true
+ file: ${zip_file_name}-*.zip
+ overwrite: true
+ skip_cleanup: true
+ on:
+ repo: openwhisk/openwhisk-cli
+ tags: true
+ condition: "$DEPLOY_BUILD_READY = true"
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..17a2556
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,27 @@
+FROM golang:1.7
+
+# 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
+
+ADD . /src/github.com/openwhisk/openwhisk-cli
+
+# 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/openwhisk/openwhisk-cli && /bin/godep restore -v
+
+# wsk binary will be placed under a build folder
+RUN mkdir /src/github.com/openwhisk/openwhisk-cli/build
+
+ARG CLI_OS
+ARG CLI_ARCH
+
+# Build the Go wsk CLI binaries and compress resultant binaries
+RUN chmod +x /src/github.com/openwhisk/openwhisk-cli/build.sh
+RUN cd /src/github.com/openwhisk/openwhisk-cli && ./build.sh
diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index 7a72290..9c0fd26 100644
--- a/Godeps/Godeps.json
+++ b/Godeps/Godeps.json
@@ -1,15 +1,20 @@
{
- "ImportPath": "github.ibm.com/BlueMix-Fabric/go-whisk-cli",
- "GoVersion": "go1.5",
+ "ImportPath": "github.com/openwhisk/openwhisk-cli",
+ "GoVersion": "go1.",
+ "GodepVersion": "v74",
"Deps": [
{
+ "ImportPath": "github.com/cloudfoundry/jibber_jabber",
+ "Rev": "bcc4c8345a21301bf47c032ff42dd1aae2fe3027"
+ },
+ {
"ImportPath": "github.com/fatih/color",
- "Comment": "v0.1-14-g7a5857d",
- "Rev": "7a5857db0b2752a436d8461d88c42dea0ee191c0"
+ "Comment": "v0.1-19-g87d4004",
+ "Rev": "87d4004f2ab62d0d255e0a38f1680aa534549fe3"
},
{
"ImportPath": "github.com/google/go-querystring/query",
- "Rev": "2a60fc2ba6c19de80291203597d752e9ba58e4c0"
+ "Rev": "9235644dd9e52eeae6fa48efd539fdc351a0af53"
},
{
"ImportPath": "github.com/hokaccha/go-prettyjson",
@@ -21,31 +26,60 @@
},
{
"ImportPath": "github.com/mattn/go-colorable",
- "Rev": "9fdad7c47650b7d2e1da50644c1f4ba7f172f252"
+ "Comment": "v0.0.6-9-gd228849",
+ "Rev": "d228849504861217f796da67fae4f6e347643f15"
},
{
"ImportPath": "github.com/mattn/go-isatty",
- "Rev": "56b76bdf51f7708750eac80fa38b952bb9f32639"
+ "Rev": "66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8"
},
{
"ImportPath": "github.com/mitchellh/go-homedir",
- "Rev": "d682a8f0cf139663a984ff12528da460ca963de9"
+ "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": "65a708cee0a4424f4e353d031ce440643e312f92"
+ "Rev": "1238ba19d24b0b9ceee2094e1cb31947d45c3e86"
},
{
"ImportPath": "github.com/spf13/pflag",
- "Rev": "7f60f83a2c81bc3c3c0d5297f61ddfa68da9d3b7"
- },
- {
- "ImportPath": "github.ibm.com/BlueMix-Fabric/go-whisk/whisk",
- "Rev": "f6fd6fbaeb0a99ec76f84f7a867d236a5a0309ad"
+ "Rev": "367864438f1b1a3c7db4da06a2f55b144e6784e0"
},
{
"ImportPath": "golang.org/x/sys/unix",
- "Rev": "50c6bc5e4292a1d4e65c6e9be5f53be28bcbe28e"
- }
+ "Rev": "7f918dd405547ecb864d14a8ecbbfe205b5f930f"
+ },
+ {
+ "ImportPath": "gopkg.in/yaml.v2",
+ "Rev": "e4d366fc3c7938e2958e662b4258c7a89e1f0e3e"
+ },
+ {
+ "ImportPath": "github.com/openwhisk/openwhisk-client-go/whisk",
+ "Rev": "5c216065673c15e35e0baa8feca9b986a6b73517"
+ },
+ {
+ "ImportPath": "github.com/openwhisk/openwhisk-client-go/wski18n",
+ "Rev": "5c216065673c15e35e0baa8feca9b986a6b73517"
+ }
]
}
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..4b5f304
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,52 @@
+SOURCEDIR=.
+
+SOURCES := $(shell find $(SOURCEDIR) -name '*.go')
+BINARY=wsk
+
+VERSION=1.0.0
+
+BUILD=`git rev-parse HEAD`
+
+deps:
+ @echo "Installing dependencies"
+ go get -d -t ./...
+
+LDFLAGS=-ldflags "-X main.Version=`date -u '+%Y-%m-%dT%H:%M:%S'` -X main.Build=`git rev-parse HEAD` "
+
+updatedeps:
+ @echo "Updating all dependencies"
+ @go get -d -u -f -fix -t ./...
+
+# Build the project
+build: deps
+ go build ${LDFLAGS} -o ${BINARY}
+
+test:
+ @echo "Launch the unit tests."
+ go test ./... -tags=unit
+
+native_test:
+ @echo "Launch the native tests for the commands."
+ go test -v ./... -tags=native
+
+# Run the integration test against OpenWhisk
+integration_test:
+ @echo "Launch the integration tests."
+ go test -v ./... -tags=integration
+
+format:
+ @echo "Formatting"
+ go fmt ./...
+
+lint: format
+ @echo "Linting"
+ golint .
+
+install:
+ go install
+
+# Cleans our project: deletes binaries
+clean:
+ if [ -f ${BINARY} ] ; then rm ${BINARY}; fi
+
+.PHONY: clean install
\ No newline at end of file
diff --git a/README.md b/README.md
index 7aaaaf4..ec41e8c 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,83 @@
-# Whisk Command Line App
+# OpenWhisk Command Line Interface `wsk`
-`$ git clone git@github.ibm.com:BlueMix-Fabric/go-whisk.git $GOPATH/src/github.ibm.com/BlueMix-Fabric/go-whisk`
+The OpenWhisk Command Line Interface (OpenWhisk CLI) is a unified tool that provides a consistent interface to
+interact with OpenWhisk services. With this tool to download and configure, you are able to manage OpenWhisk services
+from the command line and automate them through scripts.
-`$ git clone git@github.ibm.com:BlueMix-Fabric/go-whisk-cli.git $GOPATH/src/github.ibm.com/BlueMix-Fabric/go-whisk-cli`
-`$ cd $GOPATH/src/github.ibm.com/BlueMix-Fabric/go-whisk-cli`
+# Where to download the binary of OpenWhisk CLI
-`$ go build -o wsk main.go`
+The OpenWhisk CLI is available on the release page: [click here to download](https://github.com/openwhisk/openwhisk-cli/releases).
+We currently have binaries available for Linux, Mac OS and windows under amd64 architecture. You can download the
+binary, which fits your local environment.
-`$ ./wsk -h`
----
+# How to build the binary locally
-[Whisk Command Line Tutorial](https://github.rtp.raleigh.ibm.com/whisk-development/whisk/blob/master/blue/docs/tutorial/WhiskCliTutorial.md)
+You can also choose to build the binary locally based on the source code. First, install the prerequisites to
+download and build OpenWhisk CLI: [installing Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).
+
+Then, download the source code via the Git command:
+
+```
+$ git clone https://github.com/openwhisk/openwhisk-cli.git
+```
+
+OpenWhisk CLI(`wsk`) is produced in a Docker container during the build process which is copied from the
+Docker container to the local file system in the following directory: bin. This binary will be platform
+specific, it will only run on the operating system, and CPU architecture that matches the build machine.
+
+## Build the binary with Go
+
+The binary can be built by Go build command. Make sure that you have Go installed: [installing Go](https://golang.org/doc/install).
+
+After that, open an terminal, go to the directory of OpenWhisk CLI home directory, and build the binary via
+the following command:
+
+```
+$ go build -o wsk
+```
+
+If you would like to build the binary for a specific operating system, you may add the arguments GOOS and
+GOARCH into the Go build command. Since it is only applicable under amd64 architecture, you have to set GOARCH
+to amd64. GOOS can be set to "linux" "darwin" or "windows".
+
+For example, run the following command to build the binary for Linux:
+
+```
+$ GOOS=linux GOARCH=amd64 go build -o wsk
+```
+
+If it is executed successfully, you can find your binary `wsk` directly under OpenWhisk CLI home directory.
+
+## Build the binary with Docker and Gradle
+
+This is the second choice for you to build the binary. Make sure that you have Docker and gradle on your machine:
+[installing Docker](https://docs.docker.com/engine/installation/) and [installing Gradle](https://gradle.org/install) for your local machine.
+
+After that, open an terminal, go to the directory of OpenWhisk CLI home directory, and
+build the binary via the following command under Linux or Mac:
+
+```
+$ ./gradlew distDocker
+```
+
+or run the following command for Windows:
+
+```
+$ ./gradlew.bat distDocker
+```
+
+Finally, you can find the binary `wsk` or `wsk.exe` in the bin folder under the OpenWhisk CLI home directory.
+
+
+# How to use the binary
+
+When you have the binary, you can copy the binary to any folder, and add folder into the system PATH in order to
+run the OpenWhisk CLI command. To get the CLI command help, execute the following command:
+
+```
+$ wsk --help
+```
+
+To get CLI command debug information, include the -d, or --debug flag when executing this command.
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..4204c1a
--- /dev/null
+++ b/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/openwhisk/openwhisk-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/build.sh b/build.sh
new file mode 100644
index 0000000..4a7fca8
--- /dev/null
+++ b/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/openwhisk/openwhisk-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/commands/action.go b/commands/action.go
index 61e1274..2393a51 100644
--- a/commands/action.go
+++ b/commands/action.go
@@ -1,371 +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 (
- "archive/tar"
- "compress/gzip"
- "encoding/base64"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path/filepath"
- "regexp"
- "strings"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "path/filepath"
+ "io"
+ "strings"
- "github.ibm.com/BlueMix-Fabric/go-whisk/whisk"
+ "github.com/openwhisk/openwhisk-client-go/whisk"
+ "github.com/openwhisk/openwhisk-cli/wski18n"
- "github.com/fatih/color"
- "github.com/spf13/cobra"
+ "github.com/fatih/color"
+ "github.com/spf13/cobra"
+ "github.com/mattn/go-colorable"
)
-//////////////
-// Commands //
-//////////////
+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: "work with actions",
+ Use: "action",
+ Short: wski18n.T("work with actions"),
}
var actionCreateCmd = &cobra.Command{
- Use: "create <name string> <artifact string>",
- Short: "create a new action",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
- action, err := parseAction(cmd, args)
- if err != nil {
- fmt.Println(err)
- return
- }
- action, _, err = client.Actions.Insert(action, false)
- if err != nil {
- fmt.Println(err)
- return
- }
+ if whiskErr := checkArgs(
+ args,
+ 2,
+ 2,
+ "Action create",
+ wski18n.T("An action name and action are required.")); whiskErr != nil {
+ return whiskErr
+ }
- fmt.Printf("%s created action %s", color.GreenString("ok:"), boldString(action.Name))
- },
+ 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 <name string> <artifact string>",
- Short: "update an existing action",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
- action, err := parseAction(cmd, args)
- if err != nil {
- fmt.Println(err)
- return
- }
- action, _, err = client.Actions.Insert(action, true)
- if err != nil {
- fmt.Println(err)
- return
- }
+ if whiskErr := checkArgs(
+ args,
+ 1,
+ 2,
+ "Action update",
+ wski18n.T("An action name is required. An action is optional.")); whiskErr != nil {
+ return whiskErr
+ }
- fmt.Printf("%s updated action %s", color.GreenString("ok:"), boldString(action.Name))
+ 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 <name string> <payload string>",
- Short: "invoke action",
- Run: func(cmd *cobra.Command, args []string) {
+ 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
- var err error
- var actionName, payloadArg string
- if len(args) < 1 || len(args) > 2 {
- err = errors.New("Invalid argument list")
- fmt.Println(err)
- return
- }
+ if whiskErr := checkArgs(
+ args,
+ 1,
+ 1,
+ "Action invoke",
+ wski18n.T("An action name is required.")); whiskErr != nil {
+ return whiskErr
+ }
- actionName = args[0]
+ if qualifiedName, err = parseQualifiedName(args[0]); err != nil {
+ return parseQualifiedNameError(args[0], err)
+ }
- payload := map[string]interface{}{}
+ client.Namespace = qualifiedName.namespace
+ paramArgs = flags.common.param
- if len(flags.common.param) > 0 {
- parameters, err := parseParameters(flags.common.param)
- if err != nil {
- fmt.Printf("error: %s", err)
- return
- }
+ 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}
- for _, param := range parameters {
- payload[param.Key] = param.Value
- }
- }
+ res, _, err := client.Actions.Invoke(
+ qualifiedName.entityName,
+ parameters,
+ flags.common.blocking,
+ flags.action.result)
- if len(args) == 2 {
- payloadArg = args[1]
- reader := strings.NewReader(payloadArg)
- err = json.NewDecoder(reader).Decode(&payload)
- if err != nil {
- payload["payload"] = payloadArg
- }
- }
+ return handleInvocationResponse(qualifiedName, parameters, res, err)
+ },
+}
- activation, _, err := client.Actions.Invoke(actionName, payload, flags.common.blocking)
- if err != nil {
- fmt.Printf("error: %s", err)
- return
- }
+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)
+ }
+ }
+ }
- if flags.common.blocking && flags.action.result {
- printJSON(activation.Response.Result)
- } else if flags.common.blocking {
- fmt.Printf("%s invoked %s with id %s\n", color.GreenString("ok:"), boldString(actionName), boldString(activation.ActivationID))
- boldPrintf("response:\n")
- printJSON(activation.Response)
- } else {
- fmt.Printf("%s invoked %s with id %s\n", color.GreenString("ok:"), boldString(actionName), boldString(activation.ActivationID))
- }
-
- },
+ return err
}
var actionGetCmd = &cobra.Command{
- Use: "get <name string>",
- Short: "get action",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
+ if whiskErr := checkArgs(args, 1, 2, "Action get", wski18n.T("An action name is required.")); whiskErr != nil {
+ return whiskErr
+ }
- var err error
- if len(args) != 1 {
- err = errors.New("Invalid argument")
- fmt.Println(err)
- return
- }
+ if len(args) > 1 {
+ field = args[1]
- actionName := args[0]
- action, _, err := client.Actions.Get(actionName)
- if err != nil {
- fmt.Printf("error: %s", err)
- return
- }
- // print out response
+ if !fieldExists(&whisk.Action{}, field) {
+ return invalidFieldFilterError(field)
+ }
+ }
- if flags.common.summary {
- fmt.Printf("%s /%s/%s\n", boldString("action"), action.Namespace, action.Name)
- } else {
- fmt.Printf("%s got action %s\n", color.GreenString("ok:"), boldString(actionName))
- printJSON(action)
- }
+ 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 <name string>",
- Short: "delete action",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
- actionName := args[0]
- _, err := client.Actions.Delete(actionName)
- if err != nil {
- fmt.Printf("error: %s", err)
- return
- }
- // print out response
- fmt.Printf("%s deleted action %s\n", color.GreenString("ok:"), boldString(actionName))
- },
+ 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 string>",
- Short: "list all actions",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
- var err error
- qName := qualifiedName{}
- if len(args) == 1 {
- qName, err = parseQualifiedName(args[0])
- if err != nil {
- fmt.Printf("error: %s", err)
- return
- }
- ns := qName.namespace
- if len(ns) == 0 {
- err = errors.New("No valid namespace detected. Make sure that namespace argument is preceded by a \"/\"")
- fmt.Printf("error: %s\n", err)
- return
- }
+ if len(args) == 1 {
+ if qualifiedName, err = parseQualifiedName(args[0]); err != nil {
+ return parseQualifiedNameError(args[0], err)
+ }
- client.Namespace = ns
+ 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
+ }
- if pkg := qName.packageName; len(pkg) > 0 {
- // todo :: scope call to package
- }
- }
+ options := &whisk.ActionListOptions{
+ Skip: flags.common.skip,
+ Limit: flags.common.limit,
+ }
- 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)
+ }
- actions, _, err := client.Actions.List(options)
- if err != nil {
- fmt.Printf("error: %s", err)
- return
- }
- printList(actions)
- },
+ printList(actions)
+
+ return nil
+ },
}
-func parseAction(cmd *cobra.Command, args []string) (*whisk.Action, error) {
- var err error
- var actionName, artifact string
- if len(args) < 1 || len(args) > 2 {
- err = errors.New("Invalid argument list")
- return nil, err
- }
+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{}
- actionName = args[0]
+ qualifiedName := QualifiedName{}
- if len(args) == 2 {
- artifact = args[1]
- }
+ if qualifiedName, err = parseQualifiedName(args[0]); err != nil {
+ return nil, parseQualifiedNameError(args[0], err)
+ }
- exec := whisk.Exec{}
+ if len(args) == 2 {
+ artifact = args[1]
+ }
- parameters, err := parseParameters(flags.common.param)
- if err != nil {
- return nil, err
- }
+ 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)
- annotations, err := parseAnnotations(flags.common.annotation)
- if err != nil {
- return nil, err
- }
+ paramArgs = flags.common.param
+ annotArgs = flags.common.annotation
- limits := whisk.Limits{
- Timeout: flags.action.timeout,
- Memory: flags.action.memory,
- }
+ if len(paramArgs) > 0 {
+ if parameters, err = getJSONFromStrings(paramArgs, true); err != nil {
+ return nil, getJSONFromStringsParamError(paramArgs, true, err)
+ }
- if flags.action.docker {
- exec.Image = artifact
- } else if flags.action.copy {
- existingAction, _, err := client.Actions.Get(actionName)
- if err != nil {
- return nil, err
- }
- exec = existingAction.Exec
- } else if flags.action.sequence {
- currentNamespace := client.Config.Namespace
- client.Config.Namespace = "whisk.system"
- pipeAction, _, err := client.Actions.Get("system/pipe")
- if err != nil {
- return nil, err
- }
- exec = pipeAction.Exec
- client.Config.Namespace = currentNamespace
- } else if artifact != "" {
- stat, err := os.Stat(artifact)
- if err != nil {
- // file does not exist
- return nil, err
- }
+ action.Parameters = parameters.(whisk.KeyValueArr)
+ }
- file, err := ioutil.ReadFile(artifact)
- if err != nil {
- return nil, err
- }
+ if len(annotArgs) > 0 {
+ if annotations, err = getJSONFromStrings(annotArgs, true); err != nil {
+ return nil, getJSONFromStringsAnnotError(annotArgs, true, err)
+ }
- exec.Code = string(file)
+ action.Annotations = annotations.(whisk.KeyValueArr)
+ }
- if matched, _ := regexp.MatchString(".swift$", stat.Name()); matched {
- exec.Kind = "swift"
- } else {
- exec.Kind = "nodejs"
- }
- }
+ if flags.action.copy {
+ copiedQualifiedName := QualifiedName{}
- if flags.action.lib != "" {
- file, err := os.Open(flags.action.lib)
- if err != nil {
- return nil, err
- }
+ if copiedQualifiedName, err = parseQualifiedName(args[1]); err != nil {
+ return nil, parseQualifiedNameError(args[1], err)
+ }
- var r io.Reader
- switch ext := filepath.Ext(file.Name()); ext {
- case "tar":
- r = tar.NewReader(file)
- case "gzip":
- r, err = gzip.NewReader(file)
- default:
- err = fmt.Errorf("Unrecognized file compression %s", ext)
- }
- if err != nil {
- return nil, err
- }
- lib, err := ioutil.ReadAll(r)
- if err != nil {
- return nil, err
- }
+ client.Namespace = copiedQualifiedName.namespace
- exec.Init = base64.StdEncoding.EncodeToString(lib)
- }
+ if existingAction, _, err = client.Actions.Get(copiedQualifiedName.entityName); err != nil {
+ return nil, actionGetError(copiedQualifiedName.entityName, err)
+ }
- action := &whisk.Action{
- Name: actionName,
- Publish: flags.action.shared,
- Exec: exec,
- Annotations: annotations,
- Parameters: parameters,
- Limits: limits,
- }
+ 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)
+ }
- return action, nil
+ 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
}
-///////////
-// Flags //
-///////////
+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 does not exist"))
+ } 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"))
- actionCreateCmd.Flags().BoolVar(&flags.action.docker, "docker", false, "treat artifact as docker image path on dockerhub")
- actionCreateCmd.Flags().BoolVar(&flags.action.copy, "copy", false, "treat artifact as the name of an existing action")
- actionCreateCmd.Flags().BoolVar(&flags.action.sequence, "sequence", false, "treat artifact as comma separated sequence of actions to invoke")
- actionCreateCmd.Flags().BoolVar(&flags.action.shared, "shared", false, "shared action (default: private)")
- actionCreateCmd.Flags().StringVar(&flags.action.lib, "lib", "", "add library to artifact (must be a gzipped tar file)")
- actionCreateCmd.Flags().StringVar(&flags.action.xPackage, "package", "", "package")
- actionCreateCmd.Flags().IntVarP(&flags.action.timeout, "timeout", "t", 0, "the timeout limit in miliseconds when the action will be terminated")
- actionCreateCmd.Flags().IntVarP(&flags.action.memory, "memory", "m", 0, "the memory limit in MB of the container that runs the action")
- actionCreateCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", []string{}, "annotations")
- actionCreateCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, "default parameters")
+ 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"))
- actionUpdateCmd.Flags().BoolVar(&flags.action.docker, "docker", false, "treat artifact as docker image path on dockerhub")
- actionUpdateCmd.Flags().BoolVar(&flags.action.copy, "copy", false, "treat artifact as the name of an existing action")
- actionUpdateCmd.Flags().BoolVar(&flags.action.sequence, "sequence", false, "treat artifact as comma separated sequence of actions to invoke")
- actionUpdateCmd.Flags().BoolVar(&flags.action.shared, "shared", false, "shared action (default: private)")
- actionUpdateCmd.Flags().StringVar(&flags.action.lib, "lib", "", "add library to artifact (must be a gzipped tar file)")
- actionUpdateCmd.Flags().StringVar(&flags.action.xPackage, "package", "", "package")
- actionUpdateCmd.Flags().IntVarP(&flags.action.timeout, "timeout", "t", 0, "the timeout limit in miliseconds when the action will be terminated")
- actionUpdateCmd.Flags().IntVarP(&flags.action.memory, "memory", "m", 0, "the memory limit in MB of the container that runs the action")
- actionUpdateCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", []string{}, "annotations")
- actionUpdateCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, "default parameters")
+ 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)"))
- actionInvokeCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, "parameters")
- actionInvokeCmd.Flags().BoolVarP(&flags.common.blocking, "blocking", "b", false, "blocking invoke")
- actionInvokeCmd.Flags().BoolVarP(&flags.action.result, "result", "r", false, "show only activation result if a blocking activation (unless there is a failure)")
+ actionGetCmd.Flags().BoolVarP(&flags.common.summary, "summary", "s", false, wski18n.T("summarize action details"))
- actionGetCmd.Flags().BoolVarP(&flags.common.summary, "summary", "s", false, "summarize entity 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"))
- actionListCmd.Flags().IntVarP(&flags.common.skip, "skip", "s", 0, "skip this many entitites from the head of the collection")
- actionListCmd.Flags().IntVarP(&flags.common.limit, "limit", "l", 30, "only return this many entities from the collection")
- actionListCmd.Flags().BoolVar(&flags.common.full, "full", false, "include full entity description")
-
- actionCmd.AddCommand(
- actionCreateCmd,
- actionUpdateCmd,
- actionInvokeCmd,
- actionGetCmd,
- actionDeleteCmd,
- actionListCmd,
- )
+ actionCmd.AddCommand(
+ actionCreateCmd,
+ actionUpdateCmd,
+ actionInvokeCmd,
+ actionGetCmd,
+ actionDeleteCmd,
+ actionListCmd,
+ )
}
diff --git a/commands/activation.go b/commands/activation.go
index 8408aad..df0b2d5 100644
--- a/commands/activation.go
+++ b/commands/activation.go
@@ -1,262 +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"
+ "errors"
+ "fmt"
+ "os"
+ "os/signal"
+ "syscall"
+ "time"
- "github.ibm.com/BlueMix-Fabric/go-whisk/whisk"
+ "github.com/openwhisk/openwhisk-client-go/whisk"
+ "github.com/openwhisk/openwhisk-cli/wski18n"
- "github.com/fatih/color"
- "github.com/spf13/cobra"
+ "github.com/fatih/color"
+ "github.com/spf13/cobra"
)
const (
- PollInterval = time.Second * 2
- Delay = time.Second * 5
+ PollInterval = time.Second * 2
+ Delay = time.Second * 5
)
// activationCmd represents the activation command
var activationCmd = &cobra.Command{
- Use: "activation",
- Short: "work with activations",
+ Use: "activation",
+ Short: wski18n.T("work with activations"),
}
var activationListCmd = &cobra.Command{
- Use: "list <namespace string>",
- Short: "list activations",
+ 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{}
- Run: func(cmd *cobra.Command, args []string) {
- var err error
- qName := qualifiedName{}
- if len(args) == 1 {
- qName, err = parseQualifiedName(args[0])
- if err != nil {
- fmt.Printf("error: %s", err)
- return
- }
- ns := qName.namespace
- if len(ns) == 0 {
- err = errors.New("No valid namespace detected. Make sure that namespace argument is preceded by a \"/\"")
- fmt.Printf("error: %s\n", err)
- return
- }
+ // 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
+ 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
+ }
- if pkg := qName.packageName; len(pkg) > 0 {
- // todo :: scope call to package
- }
- }
+ 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
+ }
- options := &whisk.ActivationListOptions{
- Name: flags.activation.action,
- 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 {
- fmt.Println(err)
- return
- }
- printList(activations)
- },
+ // 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 <id string>",
- Short: "get activation",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
- if len(args) != 1 {
- err := errors.New("Invalid ID argument")
- fmt.Println(err)
- return
- }
- id := args[0]
- activation, _, err := client.Activations.Get(id)
- if err != nil {
- fmt.Println(err)
- return
- }
+ if whiskErr := checkArgs(args, 1, 2, "Activation get",
+ wski18n.T("An activation ID is required.")); whiskErr != nil {
+ return whiskErr
+ }
- if flags.common.summary {
- fmt.Printf("activation result for /%s/%s (%s at %s)", activation.Namespace, activation.Name, activation.Response.Status, time.Unix(activation.End/1000, 0))
- printJSON(activation.Response.Result)
- } else {
- fmt.Printf("%s got activation %s\n", color.GreenString("ok:"), boldString(id))
- printJSON(activation)
- }
+ 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",
- Short: "get the logs of an activation",
+ 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 {
- Run: func(cmd *cobra.Command, args []string) {
- if len(args) != 1 {
- err := errors.New("Invalid ID argument")
- fmt.Println(err)
- return
- }
+ 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 {
- fmt.Println(err)
- return
- }
+ 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
+ }
- fmt.Printf("%s got activation %s logs\n", color.GreenString("ok:"), boldString(id))
-
- printJSON(activation.Logs)
- },
+ printActivationLogs(activation.Logs)
+ return nil
+ },
}
var activationResultCmd = &cobra.Command{
- Use: "result",
- Short: "get the result of an activation",
+ 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 {
- Run: func(cmd *cobra.Command, args []string) {
- if len(args) != 1 {
- err := errors.New("Invalid ID argument")
- fmt.Println(err)
- return
- }
+ 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 {
- fmt.Println(err)
- return
- }
+ 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
+ }
- fmt.Printf("%s got activation %s result\n", color.GreenString("ok:"), boldString(id))
- printJSON(result)
- },
+ printJSON(result.Result)
+ return nil
+ },
}
var activationPollCmd = &cobra.Command{
- Use: "poll <namespace string>",
- Short: "poll continuously for log messages from currently running actions",
+ 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)
- Run: func(cmd *cobra.Command, args []string) {
- var name string
- if len(args) == 1 {
- name = args[0]
- }
+ 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("Poll terminated")
- os.Exit(1)
- }()
- fmt.Println("Enter Ctrl-c to exit.")
+ 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."))
- pollSince := time.Now()
- reported := []string{}
+ // 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, _, _ := client.Activations.List(options)
- if len(activationList) > 0 {
- lastActivation := activationList[0]
- pollSince = time.Unix(lastActivation.Start+1, 0).Add(Delay)
- }
- } else {
- t0 := time.Now()
+ 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
- duration, err := time.ParseDuration(fmt.Sprintf("%ds %dm %dh",
- flags.activation.sinceSeconds,
- flags.activation.sinceMinutes,
- flags.activation.sinceHours+
- flags.activation.sinceDays*24,
- ))
- if err == nil {
- pollSince = t0.Add(-duration)
- }
- }
+ // 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.Println("Polling for logs")
- localStartTime := time.Now()
- for {
- if flags.activation.exit > 0 {
- localDuration := time.Since(localStartTime)
- if int(localDuration.Seconds()) > flags.activation.exit {
- return
- }
- }
+ fmt.Printf(wski18n.T("Polling for activation logs\n"))
+ whisk.Verbose("Polling starts from %s\n", time.Unix(pollSince/1000, 0))
+ localStartTime := time.Now()
- options := &whisk.ActivationListOptions{
- Name: name,
- Since: pollSince.Unix(),
- Docs: true,
- }
+ // 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 {
- continue
- }
+ 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 {
- for _, id := range reported {
- if id == activation.ActivationID {
- continue
- }
- }
- fmt.Printf("\nActivation: %s (%s)\n", activation.Name, activation.ActivationID)
- printJSON(activation.Logs)
-
- reported = append(reported, activation.ActivationID)
- if activationTime := time.Unix(activation.Start, 0); activationTime.After(pollSince) {
- pollSince = activationTime
- }
- }
- time.Sleep(time.Second * 2)
- }
- },
+ 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"))
- activationListCmd.Flags().StringVarP(&flags.activation.action, "action", "a", "", "retrieve activations for action")
- activationListCmd.Flags().IntVarP(&flags.common.skip, "skip", "s", 0, "skip this many entitites from the head of the collection")
- activationListCmd.Flags().IntVarP(&flags.common.limit, "limit", "l", 30, "only return this many entities from the collection")
- activationListCmd.Flags().BoolVarP(&flags.common.full, "full", "f", false, "include full entity description")
- activationListCmd.Flags().Int64Var(&flags.activation.upto, "upto", 0, "return activations with timestamps earlier than UPTO; measured in miliseconds since Th, 01, Jan 1970")
- activationListCmd.Flags().Int64Var(&flags.activation.since, "since", 0, "return activations with timestamps earlier than UPTO; measured in miliseconds since Th, 01, Jan 1970")
+ activationGetCmd.Flags().BoolVarP(&flags.common.summary, "summary", "s", false, wski18n.T("summarize activation details"))
- activationGetCmd.Flags().BoolVarP(&flags.common.summary, "summary", "s", false, "summarize entity 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"))
- activationPollCmd.Flags().IntVarP(&flags.activation.exit, "exit", "e", 0, "exit after this many seconds")
- activationPollCmd.Flags().IntVar(&flags.activation.sinceSeconds, "since-seconds", 0, "start polling for activations this many seconds ago")
- activationPollCmd.Flags().IntVar(&flags.activation.sinceMinutes, "since-minutes", 0, "start polling for activations this many minutes ago")
- activationPollCmd.Flags().IntVar(&flags.activation.sinceHours, "since-hours", 0, "start polling for activations this many hours ago")
- activationPollCmd.Flags().IntVar(&flags.activation.sinceDays, "since-days", 0, "start polling for activations this many days ago")
-
- activationCmd.AddCommand(
- activationListCmd,
- activationGetCmd,
- activationLogsCmd,
- activationResultCmd,
- activationPollCmd,
- )
+ activationCmd.AddCommand(
+ activationListCmd,
+ activationGetCmd,
+ activationLogsCmd,
+ activationResultCmd,
+ activationPollCmd,
+ )
}
diff --git a/commands/api.go b/commands/api.go
new file mode 100644
index 0000000..430dc38
--- /dev/null
+++ b/commands/api.go
@@ -0,0 +1,1487 @@
+/*
+ * 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"
+ "reflect"
+ "strconv"
+ "strings"
+
+ "github.com/openwhisk/openwhisk-client-go/whisk"
+ "github.com/openwhisk/openwhisk-cli/wski18n"
+
+ "github.com/fatih/color"
+ "github.com/spf13/cobra"
+ "encoding/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.MakeWskErrorFromWskError(errors.New(errMsg), err, 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)
+ props, _ := readProps(Properties.PropsFile)
+ apiCreateReqOptions.SpaceGuid = strings.Split(props["AUTH"], ":")[0]
+ apiCreateReqOptions.AccessToken = "DUMMY_TOKEN"
+ if len(props["APIGW_ACCESS_TOKEN"]) > 0 {
+ apiCreateReqOptions.AccessToken = props["APIGW_ACCESS_TOKEN"]
+ }
+ 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
+ }
+
+ apiGetReq := new(whisk.ApiGetRequest)
+ apiGetReqOptions := new(whisk.ApiGetRequestOptions)
+ apiGetReqOptions.ApiBasePath = args[0]
+ props, _ := readProps(Properties.PropsFile)
+ apiGetReqOptions.SpaceGuid = strings.Split(props["AUTH"], ":")[0]
+ apiGetReqOptions.AccessToken = "DUMMY_TOKEN"
+ if len(props["APIGW_ACCESS_TOKEN"]) > 0 {
+ apiGetReqOptions.AccessToken = props["APIGW_ACCESS_TOKEN"]
+ }
+
+ 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
+ }
+ 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 {
+
+ 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)
+ props, _ := readProps(Properties.PropsFile)
+ apiDeleteReqOptions.SpaceGuid = strings.Split(props["AUTH"], ":")[0]
+ apiDeleteReqOptions.AccessToken = "DUMMY_TOKEN"
+ if len(props["APIGW_ACCESS_TOKEN"]) > 0 {
+ apiDeleteReqOptions.AccessToken = props["APIGW_ACCESS_TOKEN"]
+ }
+
+ // 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
+
+ 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
+ }
+
+ props, _ := readProps(Properties.PropsFile)
+ spaceguid := strings.Split(props["AUTH"], ":")[0]
+ var accesstoken string = "DUMMY_TOKEN"
+ if len(props["APIGW_ACCESS_TOKEN"]) > 0 {
+ accesstoken = props["APIGW_ACCESS_TOKEN"]
+ }
+
+ // Get API request body
+ apiGetReq := new(whisk.ApiGetRequest)
+ apiGetReq.Namespace = client.Config.Namespace
+ // Get API request options
+ apiGetReqOptions := new(whisk.ApiGetRequestOptions)
+ apiGetReqOptions.AccessToken = accesstoken
+ apiGetReqOptions.SpaceGuid = spaceguid
+
+ // List API request query parameters
+ apiListReqOptions := new(whisk.ApiListRequestOptions)
+ apiListReqOptions.Limit = flags.common.limit
+ apiListReqOptions.Skip = flags.common.skip
+ apiListReqOptions.AccessToken = accesstoken
+ apiListReqOptions.SpaceGuid = spaceguid
+
+ if (len(args) == 0) {
+ 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 {
+ // 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.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, (*whisk.ApiOptions)(apiGetReqOptions))
+ }
+ } 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, (*whisk.ApiOptions)(apiGetReqOptions))))
+ maxApiNameSize := min(30, max(len("API Name"), getLargestApiNameSizeV2(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++ {
+ printFilteredListRowV2(retApiArray.Apis[i].ApiValue, (*whisk.ApiOptions)(apiGetReqOptions), 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, 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, "printFilteredListApiV2: comparing api relpath: %s\n", path)
+ if ( len(api.ApiRelPath) == 0 || path == api.ApiRelPath) {
+ 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(api.ApiVerb) == 0 || strings.ToLower(op) == strings.ToLower(api.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, 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, "printFilteredListRowV2: comparing api relpath: %s\n", path)
+ if ( len(api.ApiRelPath) == 0 || path == api.ApiRelPath) {
+ 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(api.ApiVerb) == 0 || strings.ToLower(op) == strings.ToLower(api.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, 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 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, 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 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
+ }
+
+ // 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
+}
+
+///////////
+// 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"))
+ 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/commands/commands.go b/commands/commands.go
index 133dc37..30947f7 100644
--- a/commands/commands.go
+++ b/commands/commands.go
@@ -1,47 +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 (
- "fmt"
- "net/http"
- "net/url"
- "os"
+ "errors"
+ "fmt"
+ "net/http"
+ "os"
- "github.ibm.com/BlueMix-Fabric/go-whisk/whisk"
+ "github.com/openwhisk/openwhisk-client-go/whisk"
+ "github.com/openwhisk/openwhisk-cli/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
+ var err error
- err = loadProperties()
- if err != nil {
- fmt.Println(err)
- os.Exit(-1)
- }
+ err = loadProperties()
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "loadProperties() error: %s\n", err)
+ fmt.Println(err)
+ os.Exit(whisk.EXITCODE_ERR_GENERAL)
+ }
+}
- baseURL, err := url.Parse(Properties.APIHost)
- if err != nil {
- fmt.Println(err)
- os.Exit(-1)
- }
+func getKeyValueArgs(args []string, argIndex int, parsedArgs []string) ([]string, []string, error) {
+ var whiskErr error
+ var key string
+ var value string
- clientConfig := &whisk.Config{
- AuthToken: Properties.Auth,
- Namespace: Properties.Namespace,
- BaseURL: baseURL,
- Version: Properties.APIVersion,
- }
+ 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)
+ }
- // Setup client
- client, err = whisk.NewClient(http.DefaultClient, clientConfig)
- if err != nil {
- fmt.Println(err)
- os.Exit(-1)
- }
+ 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 {
- return WskCmd.Execute()
+ 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/commands/flags.go b/commands/flags.go
index 5b30078..e48ac93 100644
--- a/commands/flags.go
+++ b/commands/flags.go
@@ -1,73 +1,130 @@
+/*
+ * 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
- auth string
- apihost string
- apiversion string
- }
- common struct {
- blocking bool
- annotation []string
- param []string
- shared bool // 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
- }
+ global struct {
+ verbose bool
+ debug bool
+ auth string
+ apihost string
+ apiversion string
+ insecure bool
+ }
- property struct {
- auth bool
- apihost bool
- apiversion bool
- namespace bool
- cliversion bool
- apibuild bool
- all bool
- apihostSet string
- apiversionSet string
- namespaceSet string
- }
+ 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
+ }
- action struct {
- docker bool
- copy bool
- pipe bool
- shared bool
- sequence bool
- lib string
- timeout int
- memory int
- result bool
- xPackage 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
+ }
- 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
- }
+ action struct {
+ docker bool
+ copy bool
+ pipe bool
+ web string
+ sequence bool
+ timeout int
+ memory int
+ logsize int
+ result bool
+ kind string
+ main string
+ }
- xPackage struct {
- serviceGUID 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 {
- enable bool
- disable bool
- }
+ // 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/commands/namespace.go b/commands/namespace.go
index e552a44..613c7ec 100644
--- a/commands/namespace.go
+++ b/commands/namespace.go
@@ -1,68 +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"
+ "fmt"
+ "errors"
- "github.com/spf13/cobra"
+ "github.com/spf13/cobra"
+ "github.com/fatih/color"
+
+ "github.com/openwhisk/openwhisk-client-go/whisk"
+ "github.com/openwhisk/openwhisk-cli/wski18n"
)
-// ruleCmd represents the rule command
+// namespaceCmd represents the namespace command
var namespaceCmd = &cobra.Command{
- Use: "namespace",
- Short: "work with namespaces",
+ Use: "namespace",
+ Short: wski18n.T("work with namespaces"),
}
var namespaceListCmd = &cobra.Command{
- Use: "list",
- Short: "list available namespaces",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
- // 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 {
- fmt.Println(err)
- return
- }
- printList(namespaces)
- },
+ 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 string>",
- Short: "get triggers, actions, and rules in the registry for a namespace",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
- var nsName string
- if len(args) > 0 {
- nsName = args[0]
- }
+ if whiskErr := checkArgs(args, 0, 1, "Namespace get",
+ wski18n.T("An optional namespace is the only valid argument.")); whiskErr != nil {
+ return whiskErr
+ }
- namespace, _, err := client.Namespaces.Get(nsName)
- if err != nil {
- fmt.Println(err)
- return
- }
+ // 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
+ }
+ }
- fmt.Printf("entities in namespace: %s\n", boldString(namespace.Name))
- printList(namespace.Contents.Packages)
- printList(namespace.Contents.Actions)
- printList(namespace.Contents.Triggers)
- printList(namespace.Contents.Rules)
+ 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
+ },
}
-// listCmd is a shortcut for "wsk namespace get _"
var listCmd = &cobra.Command{
- Use: "list <namespace string>",
- Short: "list triggers, actions, and rules in the registry for a namespace",
- Run: namespaceGetCmd.Run,
+ 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,
- )
+ namespaceCmd.AddCommand(
+ namespaceListCmd,
+ namespaceGetCmd,
+ )
}
diff --git a/commands/package.go b/commands/package.go
index 5d6a310..939eff5 100644
--- a/commands/package.go
+++ b/commands/package.go
@@ -1,353 +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"
+ "errors"
+ "fmt"
+ "net/http"
- "github.ibm.com/BlueMix-Fabric/go-whisk/whisk"
+ "github.com/openwhisk/openwhisk-client-go/whisk"
+ "github.com/openwhisk/openwhisk-cli/wski18n"
- "github.com/fatih/color"
- "github.com/spf13/cobra"
+ "github.com/fatih/color"
+ "github.com/spf13/cobra"
)
var packageCmd = &cobra.Command{
- Use: "package",
- Short: "work with packages",
+ Use: "package",
+ Short: wski18n.T("work with packages"),
}
var packageBindCmd = &cobra.Command{
- Use: "bind <package string> <name string>",
- Short: "bind parameters to the package",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
- fmt.Println("TODO :: this command has been commented out because it is out of date")
+ if whiskErr := checkArgs(args, 2, 2, "Package bind",
+ wski18n.T("A package name and binding name are required.")); whiskErr != nil {
+ return whiskErr
+ }
- // var err error
- // if len(args) != 2 {
- // err = errors.New("Invalid argument list")
- // fmt.Println(err)
- // return
- // }
- //
- // bindingArg := args[0]
- // packageName := args[1]
- //
- // parameters, err := parseParameters(flags.common.param)
- // if err != nil {
- // fmt.Println(err)
- // return
- // }
- //
- // annotations, err := parseAnnotations(flags.common.annotation)
- // if err != nil {
- // fmt.Println(err)
- // return
- // }
- //
- // parsedBindingArg := strings.Split(bindingArg, ":")
- // bindingName := parsedBindingArg[0]
- // var bindingNamespace string
- // if len(parsedBindingArg) == 1 {
- // bindingNamespace = client.Config.Namespace
- // } else if len(parsedBindingArg) == 2 {
- // bindingNamespace = parsedBindingArg[1]
- // } else {
- // err = fmt.Errorf("Invalid binding argument %s", bindingArg)
- // fmt.Println(err)
- // return
- // }
- //
- // binding := whisk.Binding{
- // Name: bindingName,
- // Namespace: bindingNamespace,
- // }
- //
- // p := &whisk.Package{
- // Name: packageName,
- // Publish: flags.common.shared,
- // Annotations: annotations,
- // Parameters: parameters,
- // Binding: binding,
- // }
- // p, _, err = client.Packages.Insert(p, false)
- // if err != nil {
- // fmt.Println(err)
- // return
- // }
- //
- // printJSON(p)
- },
+ 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 <name string>",
- Short: "create a new package",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
- var err error
- if len(args) != 1 {
- err = errors.New("Invalid argument")
- fmt.Println(err)
- return
- }
+ if whiskErr := checkArgs(args, 1, 1, "Package create", wski18n.T("A package name is required.")); whiskErr != nil {
+ return whiskErr
+ }
- packageName := args[0]
+ 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
- parameters, err := parseParameters(flags.common.param)
- if err != nil {
- fmt.Println(err)
- return
- }
+ 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
+ }
- annotations, err := parseAnnotations(flags.common.annotation)
- if err != nil {
- fmt.Println(err)
- return
- }
+ whisk.Debug(whisk.DbgInfo, "Parsing parameters: %#v\n", flags.common.param)
+ parameters, err := getJSONFromStrings(flags.common.param, true)
- p := &whisk.Package{
- Name: packageName,
- Publish: flags.common.shared,
- Annotations: annotations,
- Parameters: parameters,
- }
- p, _, err = client.Packages.Insert(p, false)
- if err != nil {
- fmt.Println(err)
- return
- }
+ 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
+ }
- fmt.Printf("%s created package %s\n", color.GreenString("ok:"), boldString(p.Name))
- },
+ 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 <name string>",
- Short: "update an existing package",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
- // TODO :: parse annotations
- // TODO ::parse parameters
- var err error
- if len(args) != 1 {
- err = errors.New("Invalid argument")
- fmt.Println(err)
- return
- }
+ if whiskErr := checkArgs(args, 1, 1, "Package update", wski18n.T("A package name is required.")); whiskErr != nil {
+ return whiskErr
+ }
- packageName := args[0]
+ 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
- parameters, err := parseParameters(flags.common.param)
- if err != nil {
- fmt.Println(err)
- return
- }
+ 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
+ }
- annotations, err := parseAnnotations(flags.common.annotation)
- if err != nil {
- fmt.Println(err)
- return
- }
+ whisk.Debug(whisk.DbgInfo, "Parsing parameters: %#v\n", flags.common.param)
+ parameters, err := getJSONFromStrings(flags.common.param, true)
- p := &whisk.Package{
- Name: packageName,
- Publish: flags.common.shared,
- Annotations: annotations,
- Parameters: parameters,
- }
+ 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
+ }
- p, _, err = client.Packages.Insert(p, true)
- if err != nil {
- fmt.Println(err)
- return
- }
+ 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
+ }
- fmt.Printf("%s updated package %s\n", color.GreenString("ok:"), boldString(p.Name))
+ 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 <name string>",
- Short: "get package",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
- var err error
- if len(args) != 1 {
- err = errors.New("Invalid argument")
- fmt.Println(err)
- return
- }
+ if whiskErr := checkArgs(args, 1, 2, "Package get", wski18n.T("A package name is required.")); whiskErr != nil {
+ return whiskErr
+ }
- packageName := args[0]
+ if len(args) > 1 {
+ field = args[1]
- xPackage, _, err := client.Packages.Get(packageName)
- if err != nil {
- fmt.Println(err)
- return
- }
+ 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
+ }
+ }
- if flags.common.summary {
- fmt.Printf("%s /%s/%s\n", boldString("package"), xPackage.Namespace, xPackage.Name)
- } else {
- fmt.Printf("%s got package %s\n", color.GreenString("ok:"), boldString(packageName))
- printJSON(xPackage)
- }
- },
+ 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 <name string>",
- Short: "delete package",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
- var err error
- if len(args) != 1 {
- err = errors.New("Invalid argument")
- fmt.Println(err)
- return
- }
+ if whiskErr := checkArgs(args, 1, 1, "Package delete", wski18n.T("A package name is required.")); whiskErr != nil {
+ return whiskErr
+ }
- packageName := args[0]
+ 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(packageName)
- if err != nil {
- fmt.Println(err)
- return
- }
+ _, 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.Printf("%s deleted package %s\n", color.GreenString("ok:"), boldString(packageName))
- },
+ 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 string>",
- Short: "list all packages",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
- var err error
- qName := qualifiedName{}
- if len(args) == 1 {
- qName, err = parseQualifiedName(args[0])
- if err != nil {
- fmt.Printf("error: %s", err)
- return
- }
- ns := qName.namespace
- if len(ns) == 0 {
- err = errors.New("No valid namespace detected. Make sure that namespace argument is preceded by a \"/\"")
- fmt.Printf("error: %s\n", err)
- return
- }
+ 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
- }
+ 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
+ }
- options := &whisk.PackageListOptions{
- Skip: flags.common.skip,
- Limit: flags.common.limit,
- Public: flags.common.shared,
- Docs: flags.common.full,
- }
+ if flags.common.shared == "yes" {
+ shared = true
+ } else {
+ shared = false
+ }
- packages, _, err := client.Packages.List(options)
- if err != nil {
- fmt.Println(err)
- return
- }
+ options := &whisk.PackageListOptions{
+ Skip: flags.common.skip,
+ Limit: flags.common.limit,
+ Public: shared,
+ }
- printList(packages)
- },
+ 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 string>",
- Short: "refresh package bindings",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
- var err error
+ 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 len(args) == 1 {
- namespace := args[0]
- currentNamespace := client.Config.Namespace
- client.Config.Namespace = namespace
- defer func() {
- client.Config.Namespace = currentNamespace
- }()
- }
+ 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
+ }
+ }
+ }
- updates, resp, err := client.Packages.Refresh()
- if err != nil {
- fmt.Println(err)
- return
- }
+ currentNamespace := client.Config.Namespace
+ client.Config.Namespace = qName.namespace
- switch resp.StatusCode {
- case http.StatusOK:
- fmt.Printf("\n%s refreshed successfully\n", client.Config.Namespace)
+ defer func() {
+ client.Config.Namespace = currentNamespace
+ }()
- if len(updates.Added) > 0 {
- fmt.Println("created bindings:")
- printJSON(updates.Added)
- } else {
- fmt.Println("no bindings created")
- }
+ 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)
- if len(updates.Updated) > 0 {
- fmt.Println("updated bindings:")
- printJSON(updates.Updated)
- } else {
- fmt.Println("no bindings updated")
- }
+ switch resp.StatusCode {
+ case http.StatusOK:
+ fmt.Printf(wski18n.T("{{.name}} refreshed successfully\n",
+ map[string]interface{}{"name": client.Config.Namespace}))
- if len(updates.Deleted) > 0 {
- fmt.Println("deleted bindings:")
- printJSON(updates.Deleted)
- } else {
- fmt.Println("no bindings deleted")
- }
+ fmt.Println(wski18n.T("created bindings:"))
- case http.StatusNotImplemented:
- fmt.Println("error: This feature is not implemented in the targeted deployment")
- return
- default:
- fmt.Println("error: ", resp.Status)
- return
- }
+ 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"))
- packageCreateCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", []string{}, "annotations")
- packageCreateCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, "default parameters")
- packageCreateCmd.Flags().StringVarP(&flags.xPackage.serviceGUID, "service_guid", "s", "", "a unique identifier of the service")
- packageCreateCmd.Flags().BoolVar(&flags.common.shared, "shared", false, "shared action (default: 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"))
- packageUpdateCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", []string{}, "annotations")
- packageUpdateCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, "default parameters")
- packageUpdateCmd.Flags().StringVarP(&flags.xPackage.serviceGUID, "service_guid", "s", "", "a unique identifier of the service")
- packageUpdateCmd.Flags().BoolVar(&flags.common.shared, "shared", false, "shared action (default: private)")
+ packageGetCmd.Flags().BoolVarP(&flags.common.summary, "summary", "s", false, wski18n.T("summarize package details"))
- packageGetCmd.Flags().BoolVarP(&flags.common.summary, "summary", "s", false, "summarize entity 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"))
- packageBindCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", []string{}, "annotations")
- packageBindCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, "default parameters")
+ 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"))
- packageListCmd.Flags().BoolVar(&flags.common.shared, "shared", false, "include publicly shared entities in the result")
- packageListCmd.Flags().IntVarP(&flags.common.skip, "skip", "s", 0, "skip this many entities from the head of the collection")
- packageListCmd.Flags().IntVarP(&flags.common.limit, "limit", "l", 0, "only return this many entities from the collection")
- packageListCmd.Flags().BoolVar(&flags.common.full, "full", false, "include full entity description")
-
- packageCmd.AddCommand(
- packageBindCmd,
- packageCreateCmd,
- packageUpdateCmd,
- packageGetCmd,
- packageDeleteCmd,
- packageListCmd,
- packageRefreshCmd,
- )
+ packageCmd.AddCommand(
+ packageBindCmd,
+ packageCreateCmd,
+ packageUpdateCmd,
+ packageGetCmd,
+ packageDeleteCmd,
+ packageListCmd,
+ packageRefreshCmd,
+ )
}
diff --git a/commands/property.go b/commands/property.go
index 38a2ee5..ed6b49e 100644
--- a/commands/property.go
+++ b/commands/property.go
@@ -1,335 +1,501 @@
+/*
+ * 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"
- "fmt"
- "net/url"
- "os"
- "strings"
+ "errors"
+ "fmt"
+ "os"
- "github.com/mitchellh/go-homedir"
- "github.com/spf13/cobra"
+ "github.com/mitchellh/go-homedir"
+ "github.com/spf13/cobra"
+ "github.com/fatih/color"
+
+ "github.com/openwhisk/openwhisk-client-go/whisk"
+ "github.com/openwhisk/openwhisk-cli/wski18n"
)
var Properties struct {
- Auth string
- APIHost string
- APIVersion string
- APIBuild string
- CLIVersion string
- Namespace string
- PropsFile string
+ 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: "work with whisk properties",
+ Use: "property",
+ Short: wski18n.T("work with whisk properties"),
}
+//
+// Set one or more openwhisk property values
+//
var propertySetCmd = &cobra.Command{
- Use: "set",
- Short: "set property",
- Run: func(cmd *cobra.Command, args []string) {
- // get current props
- props, err := readProps(Properties.PropsFile)
- if err != nil {
- fmt.Println(err)
- return
- }
+ 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
- // read in each flag, update if necessary
+ // 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
+ }
- if auth := flags.global.auth; len(auth) > 0 {
- props["AUTH"] = auth
- fmt.Println("ok: whisk auth set")
- }
+ // read in each flag, update if necessary
- if apiHost := flags.global.apihost; len(apiHost) > 0 {
- props["APIHOST"] = apiHost
- fmt.Println("ok: whisk API host set to ", apiHost)
- }
+ if auth := flags.global.auth; len(auth) > 0 {
+ props["AUTH"] = auth
+ client.Config.AuthToken = auth
+ okMsg += fmt.Sprintf(
+ wski18n.T("{{.ok}} whisk auth set to {{.auth}}\n",
+ map[string]interface{}{"ok": color.GreenString("ok:"), "auth": boldString(auth)}))
+ }
- if apiVersion := flags.global.apiversion; len(apiVersion) > 0 {
- props["APIVERSION"] = apiVersion
- fmt.Println("ok: whisk API version set to ", apiVersion)
- }
+ if apiHost := flags.property.apihostSet; len(apiHost) > 0 {
+ baseURL, err := getURLBase(apiHost, DefaultOpenWhiskApiPath)
- if namespace := flags.property.namespaceSet; len(namespace) > 0 {
+ 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)}))
+ }
+ }
- namespaces, _, err := client.Namespaces.List()
- if err != nil {
- fmt.Println(err)
- return
- }
+ 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)}))
+ }
- var validNamespace bool
- for _, ns := range namespaces {
- if ns.Name == namespace {
- validNamespace = true
- }
- }
+ 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)}))
+ }
+ }
+ }
- if !validNamespace {
- err = fmt.Errorf("Invalid namespace %s", namespace)
- fmt.Println(err)
- return
- }
+ 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)
+ }
- props["NAMESPACE"] = namespace
- fmt.Println("ok: whisk namespace set to ", namespace)
- }
+ if (werr != nil) {
+ return werr
+ }
- err = writeProps(Properties.PropsFile, props)
- if err != nil {
- fmt.Println(err)
- return
- }
-
- },
+ return nil
+ },
}
var propertyUnsetCmd = &cobra.Command{
- Use: "unset",
- Short: "unset property",
- Run: func(cmd *cobra.Command, args []string) {
- props, err := readProps(Properties.PropsFile)
- if err != nil {
- fmt.Println(err)
- return
- }
+ 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
+ // read in each flag, update if necessary
- if flags.property.auth {
- delete(props, "AUTH")
- }
+ if flags.property.auth {
+ delete(props, "AUTH")
+ okMsg += fmt.Sprintf(
+ wski18n.T("{{.ok}} whisk auth unset",
+ map[string]interface{}{"ok": color.GreenString("ok:")}))
+ if len(DefaultAuth) > 0 {
+ okMsg += fmt.Sprintf(
+ wski18n.T("; the default value of {{.default}} will be used.\n",
+ map[string]interface{}{"default": boldString(DefaultAuth)}))
+ } else {
+ okMsg += fmt.Sprint(
+ wski18n.T("; there is no default value that can be used.\n"))
+ }
+ }
- if flags.property.namespace {
- delete(props, "NAMESPACE")
- }
+ 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")
- }
+ if flags.property.apihost {
+ delete(props, "APIHOST")
+ okMsg += fmt.Sprintf(
+ wski18n.T("{{.ok}} whisk API host unset",
+ map[string]interface{}{"ok": color.GreenString("ok:")}))
+ if len(DefaultAPIHost) > 0 {
+ okMsg += fmt.Sprintf(
+ wski18n.T("; the default value of {{.default}} will be used.\n",
+ map[string]interface{}{"default": boldString(DefaultAPIHost)}))
+ } else {
+ okMsg += fmt.Sprint(
+ wski18n.T("; there is no default value that can be used.\n"))
+ }
+ }
- if flags.property.apiversion {
- delete(props, "APIVERSION")
- }
+ 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 {
- fmt.Println(err)
- return
- }
- },
+ 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: "get property",
- Run: func(cmd *cobra.Command, args []string) {
+ Use: "get",
+ Short: wski18n.T("get property"),
+ SilenceUsage: true,
+ SilenceErrors: true,
+ PreRunE: setupClientConfig,
+ RunE: func(cmd *cobra.Command, args []string) error {
- if flags.property.all || flags.property.auth {
- fmt.Println("whisk auth\t\t", Properties.Auth)
- }
+ // 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.apihost {
- fmt.Println("whisk API host\t\t", Properties.APIHost)
- }
+ 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.apiversion {
- fmt.Println("whisk API version\t", Properties.APIVersion)
- }
+ 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.cliversion {
- fmt.Println("whisk CLI version\t", Properties.CLIVersion)
- }
+ 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.Println("whisk namespace\t\t", Properties.Namespace)
- }
+ 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.apibuild {
- info, _, err := client.Info.Get()
- if err != nil {
- fmt.Println(err)
- } else {
- fmt.Println("whisk API build\t\t", info.Build)
- }
- }
+ 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,
- )
+ propertyCmd.AddCommand(
+ propertySetCmd,
+ propertyUnsetCmd,
+ propertyGetCmd,
+ )
- // need to set property flags as booleans instead of strings... perhaps with boolApihost...
- propertyGetCmd.Flags().BoolVarP(&flags.property.auth, "auth", "u", false, "authorization key")
- propertyGetCmd.Flags().BoolVar(&flags.property.apihost, "apihost", false, "whisk API host")
- propertyGetCmd.Flags().BoolVar(&flags.property.apiversion, "apiversion", false, "whisk API version")
- propertyGetCmd.Flags().BoolVar(&flags.property.apibuild, "apibuild", false, "whisk API build version")
- propertyGetCmd.Flags().BoolVar(&flags.property.cliversion, "cliversion", false, "whisk CLI version")
- propertyGetCmd.Flags().BoolVar(&flags.property.namespace, "namespace", false, "authorization key")
- propertyGetCmd.Flags().BoolVar(&flags.property.all, "all", false, "all properties")
+ // 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", "", "authorization key")
- propertySetCmd.Flags().StringVar(&flags.property.apihostSet, "apihost", "", "whisk API host")
- propertySetCmd.Flags().StringVar(&flags.property.apiversionSet, "apiversion", "", "whisk API version")
- propertySetCmd.Flags().StringVar(&flags.property.namespaceSet, "namespace", "", "whisk namespace")
+ 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().BoolVarP(&flags.property.auth, "auth", "u", false, "authorization key")
- propertyUnsetCmd.Flags().BoolVar(&flags.property.apihost, "apihost", false, "whisk API host")
- propertyUnsetCmd.Flags().BoolVar(&flags.property.apiversion, "apiversion", false, "whisk API version")
- propertyUnsetCmd.Flags().BoolVar(&flags.property.namespace, "namespace", false, "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 = ""
- Properties.Namespace = "_"
- Properties.APIHost = "https://openwhisk.ng.bluemix.net/api/"
- Properties.APIBuild = ""
- Properties.APIVersion = "v1"
- Properties.CLIVersion = ""
- Properties.PropsFile = "~/.wskprops"
+ 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
+ var err error
- setDefaultProperties()
+ setDefaultProperties()
- Properties.PropsFile, err = homedir.Expand(Properties.PropsFile)
- if err != nil {
- return err
- }
+ 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 {
- return err
- }
+ 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, hasProp := props["AUTH"]; hasProp {
+ Properties.Auth = authToken
+ }
- if authToken := os.Getenv("WHISK_AUTH"); len(authToken) > 0 {
- 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, hasProp := props["APIVERSION"]; hasProp {
+ Properties.APIVersion = apiVersion
+ }
- if apiVersion := os.Getenv("WHISK_APIVERSION"); len(apiVersion) > 0 {
- 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, hasProp := props["APIHOST"]; hasProp {
+ Properties.APIHost = apiHost
+ }
- if apiHost := os.Getenv("WHISK_APIHOST"); len(apiHost) > 0 {
- Properties.APIHost = apiHost
- }
+ if apiHost := os.Getenv("WHISK_APIHOST"); len(apiHost) > 0 {
+ Properties.APIHost = apiHost
+ }
- if namespace, hasProp := props["NAMESPACE"]; hasProp {
- Properties.Namespace = namespace
- }
+ if namespace, hasProp := props["NAMESPACE"]; hasProp && len(namespace) > 0 {
+ Properties.Namespace = namespace
+ }
- if namespace := os.Getenv("WHISK_NAMESPACE"); len(namespace) > 0 {
- Properties.Namespace = namespace
- }
+ if namespace := os.Getenv("WHISK_NAMESPACE"); len(namespace) > 0 {
+ Properties.Namespace = namespace
+ }
- return nil
+ return nil
}
-func parseConfigFlags(cmd *cobra.Command, args []string) {
+func parseConfigFlags(cmd *cobra.Command, args []string) error {
- if auth := flags.global.auth; len(auth) > 0 {
- Properties.Auth = auth
- client.Config.AuthToken = auth
- }
+ 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
- client.Config.Namespace = namespace
- }
+ 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
- client.Config.Version = apiVersion
- }
+ 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 {
- fmt.Println(apiHost)
- Properties.APIHost = apiHost
- u, err := url.ParseRequestURI(apiHost)
- if err == nil {
- client.Config.BaseURL = u
- } else {
- fmt.Println(err)
- os.Exit(-1)
- }
- }
+ if apiHost := flags.global.apihost; len(apiHost) > 0 {
+ Properties.APIHost = apiHost
- if flags.global.verbose {
- client.Config.Verbose = flags.global.verbose
- }
-}
+ if client != nil {
+ client.Config.Host = apiHost
+ baseURL, err := getURLBase(apiHost, DefaultOpenWhiskApiPath)
-func readProps(path string) (map[string]string, error) {
+ 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
+ }
+ }
- props := map[string]string{}
+ if flags.global.debug {
+ whisk.SetDebug(true)
+ }
+ if flags.global.verbose {
+ whisk.SetVerbose(true)
+ }
- file, err := os.Open(path)
- if err != nil {
- // If file does not exist, just return props
- 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 {
- return err
- }
- 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 {
- return err
- }
- }
- return nil
+ return nil
}
diff --git a/commands/rule.go b/commands/rule.go
index 3fc1cd6..b537aff 100644
--- a/commands/rule.go
+++ b/commands/rule.go
@@ -1,269 +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"
+ "errors"
+ "fmt"
- "github.ibm.com/BlueMix-Fabric/go-whisk/whisk"
+ "github.com/openwhisk/openwhisk-client-go/whisk"
+ "github.com/openwhisk/openwhisk-cli/wski18n"
- "github.com/fatih/color"
- "github.com/spf13/cobra"
+ "github.com/fatih/color"
+ "github.com/spf13/cobra"
)
// ruleCmd represents the rule command
var ruleCmd = &cobra.Command{
- Use: "rule",
- Short: "work with rules",
+ Use: "rule",
+ Short: wski18n.T("work with rules"),
}
var ruleEnableCmd = &cobra.Command{
- Use: "enable <name string>",
- Short: "enable rule",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
+ if whiskErr := checkArgs(args, 1, 1, "Rule enable", wski18n.T("A rule name is required.")); whiskErr != nil {
+ return whiskErr
+ }
- var err error
- if len(args) != 1 {
- err = errors.New("Invalid argument")
- fmt.Println(err)
- return
- }
+ 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
+ }
- ruleName := args[0]
+ client.Namespace = qName.namespace
+ ruleName := qName.entityName
- _, _, err = client.Rules.SetState(ruleName, "enable")
- if err != nil {
- fmt.Println(err)
- return
- }
- fmt.Printf("%s enabled rule %s\n", color.GreenString("ok:"), boldString(ruleName))
- },
+ _, _, 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 <name string>",
- Short: "disable rule",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
+ if whiskErr := checkArgs(args, 1, 1, "Rule disable", wski18n.T("A rule name is required.")); whiskErr != nil {
+ return whiskErr
+ }
- var err error
- if len(args) != 1 {
- err = errors.New("Invalid argument")
- fmt.Println(err)
- return
- }
+ 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
+ }
- ruleName := args[0]
+ client.Namespace = qName.namespace
+ ruleName := qName.entityName
- _, _, err = client.Rules.SetState(ruleName, "disable")
- if err != nil {
- fmt.Println(err)
- return
- }
- fmt.Printf("%s disabled rule %s\n", color.GreenString("ok:"), boldString(ruleName))
- },
+ _, _, 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 <name string>",
- Short: "get rule status",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
- // TODO: how is this different than "rule get" ??
- fmt.Println("rule status called")
- },
+ 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 <name string> <trigger string> <action string>",
- Short: "create new rule",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
- var err error
- if len(args) != 3 {
- err = errors.New("Invalid argument list")
- fmt.Println(err)
- return
- }
+ if whiskErr := checkArgs(args, 3, 3, "Rule create",
+ wski18n.T("A rule, trigger and action name are required.")); whiskErr != nil {
+ return whiskErr
+ }
- ruleName := args[0]
- triggerName := args[1]
- actionName := args[2]
+ 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
+ }
- rule := &whisk.Rule{
- Name: ruleName,
- Trigger: triggerName,
- Action: actionName,
- Publish: flags.common.shared,
- }
+ client.Namespace = qName.namespace
+ ruleName := qName.entityName
+ triggerName := getQualifiedName(args[1], Properties.Namespace)
+ actionName := getQualifiedName(args[2], Properties.Namespace)
- rule, _, err = client.Rules.Insert(rule, false)
- if err != nil {
- fmt.Println(err)
- return
- }
+ rule := &whisk.Rule{
+ Name: ruleName,
+ Trigger: triggerName,
+ Action: actionName,
+ }
- if flags.rule.enable {
- rule, _, err = client.Rules.SetState(ruleName, "enabled")
- if err != nil {
- fmt.Println(err)
- return
- }
- }
+ 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.Printf("%s created rule %s\n", color.GreenString("ok:"), boldString(ruleName))
- },
+ 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 <name string> <trigger string> <action string>",
- Short: "update existing rule",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
- var err error
- if len(args) != 3 {
- err = errors.New("Invalid argument list")
- fmt.Println(err)
- return
- }
+ if whiskErr := checkArgs(args, 3, 3, "Rule update",
+ wski18n.T("A rule, trigger and action name are required.")); whiskErr != nil {
+ return whiskErr
+ }
- ruleName := args[0]
- triggerName := args[1]
- actionName := args[2]
+ 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
+ }
- rule := &whisk.Rule{
- Name: ruleName,
- Trigger: triggerName,
- Action: actionName,
- Publish: flags.common.shared,
- }
+ client.Namespace = qName.namespace
+ ruleName := qName.entityName
+ triggerName := getQualifiedName(args[1], Properties.Namespace)
+ actionName := getQualifiedName(args[2], Properties.Namespace)
- rule, _, err = client.Rules.Insert(rule, true)
- if err != nil {
- fmt.Println(err)
- return
- }
- fmt.Printf("%s updated rule %s\n", color.GreenString("ok:"), boldString(ruleName))
- },
+ 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 <name string>",
- Short: "get rule",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
- var err error
- if len(args) != 1 {
- err = errors.New("Invalid argument")
- fmt.Println(err)
- return
- }
+ if whiskErr := checkArgs(args, 1, 2, "Rule get", wski18n.T("A rule name is required.")); whiskErr != nil {
+ return whiskErr
+ }
- ruleName := args[0]
+ if len(args) > 1 {
+ field = args[1]
- rule, _, err := client.Rules.Get(ruleName)
- if err != nil {
- fmt.Println(err)
- return
- }
- fmt.Printf("%s got rule %s\n", color.GreenString("ok:"), boldString(ruleName))
- printJSON(rule)
- },
+ 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 <name string>",
- Short: "delete rule",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
- var err error
- if len(args) != 1 {
- err = errors.New("Invalid argument")
- fmt.Println(err)
- return
- }
+ if whiskErr := checkArgs(args, 1, 1, "Rule delete", wski18n.T("A rule name is required.")); whiskErr != nil {
+ return whiskErr
+ }
- ruleName := args[0]
+ 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
+ }
- if flags.rule.disable {
- _, _, err := client.Rules.SetState(ruleName, "disabled")
- if err != nil {
- fmt.Println(err)
- return
- }
- }
+ client.Namespace = qName.namespace
+ ruleName := qName.entityName
- _, err = client.Rules.Delete(ruleName)
- if err != nil {
- fmt.Println(err)
- return
- }
- fmt.Printf("%s deleted rule %s\n", color.GreenString("ok:"), boldString(ruleName))
- },
+ 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 string>",
- Short: "list all rules",
+ 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{}
- Run: func(cmd *cobra.Command, args []string) {
- var err error
- qName := qualifiedName{}
- if len(args) == 1 {
- qName, err = parseQualifiedName(args[0])
- if err != nil {
- fmt.Printf("error: %s", err)
- return
- }
- ns := qName.namespace
- if len(ns) == 0 {
- err = errors.New("No valid namespace detected. Make sure that namespace argument is preceded by a \"/\"")
- fmt.Printf("error: %s\n", err)
- return
- }
+ 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
+ }
- client.Namespace = ns
+ ruleListOptions := &whisk.RuleListOptions{
+ Skip: flags.common.skip,
+ Limit: flags.common.limit,
+ }
- if pkg := qName.packageName; len(pkg) > 0 {
- // todo :: scope call to package
- }
- }
-
- ruleListOptions := &whisk.RuleListOptions{
- Skip: flags.common.skip,
- Limit: flags.common.limit,
- }
-
- rules, _, err := client.Rules.List(ruleListOptions)
- if err != nil {
- fmt.Println(err)
- return
- }
- printList(rules)
- },
+ 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"))
- ruleCreateCmd.Flags().BoolVar(&flags.common.shared, "shared", false, "shared action (default: private)")
- ruleCreateCmd.Flags().BoolVar(&flags.rule.enable, "enable", false, "autmatically enable rule after creating it")
+ ruleGetCmd.Flags().BoolVarP(&flags.rule.summary, "summary", "s", false, wski18n.T("summarize rule details"))
- ruleUpdateCmd.Flags().BoolVar(&flags.common.shared, "shared", false, "shared action (default: private)")
+ 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"))
- ruleDeleteCmd.Flags().BoolVar(&flags.rule.disable, "disable", false, "autmatically disable rule before deleting it")
-
- ruleListCmd.Flags().IntVarP(&flags.common.skip, "skip", "s", 0, "skip this many entities from the head of the collection")
- ruleListCmd.Flags().IntVarP(&flags.common.limit, "limit", "l", 30, "only return this many entities from the collection")
-
- ruleCmd.AddCommand(
- ruleEnableCmd,
- ruleDisableCmd,
- ruleStatusCmd,
- ruleCreateCmd,
- ruleUpdateCmd,
- ruleGetCmd,
- ruleDeleteCmd,
- ruleListCmd,
- )
+ ruleCmd.AddCommand(
+ ruleCreateCmd,
+ ruleEnableCmd,
+ ruleDisableCmd,
+ ruleStatusCmd,
+ ruleUpdateCmd,
+ ruleGetCmd,
+ ruleDeleteCmd,
+ ruleListCmd,
+ )
}
diff --git a/commands/sdk.go b/commands/sdk.go
index 85cee68..aed876a 100644
--- a/commands/sdk.go
+++ b/commands/sdk.go
@@ -1,112 +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"
- "os"
- "os/exec"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "strings"
- "github.com/spf13/cobra"
+ "github.com/spf13/cobra"
+
+ "github.com/openwhisk/openwhisk-client-go/whisk"
+ "github.com/openwhisk/openwhisk-cli/wski18n"
)
// sdkCmd represents the sdk command
var sdkCmd = &cobra.Command{
- Use: "sdk",
- Short: "work with the sdk",
+ Use: "sdk",
+ Short: wski18n.T("work with the sdk"),
}
-var sdkInstallCmd = &cobra.Command{
- Use: "install <component string:{docker,swift,iOS}>",
- Short: "install artifacts",
+type sdkInfo struct {
+ UrlPath string
+ FileName string
+ isGzTar bool
+ IsGzip bool
+ IsZip bool
+ IsTar bool
+ Unpack bool
+ UnpackDir string
+}
- Run: func(cmd *cobra.Command, args []string) {
- var err error
- if len(args) != 1 {
- err := errors.New("Invalid component argument")
- fmt.Println(err)
- return
- }
- component := args[0]
- switch component {
- case "docker":
- err = dockerInstall()
- case "swift":
- err = swiftInstall()
- case "iOS":
- err = iOSInstall()
- default:
- err = errors.New("Invalid component argument")
- }
- if err != nil {
- fmt.Println(err)
- return
- }
- },
+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
- tarFile := "blackbox-0.1.0.tar.gz"
- blackboxDir := "dockerSkeleton"
- if _, err = os.Stat(tarFile); err == nil {
- err = fmt.Errorf("The path %s already exists. Please deleted it and retry.", tarFile)
- return err
- }
+ var err error
- if _, err = os.Stat(blackboxDir); err == nil {
- err = fmt.Errorf("The path %s already exists. Please deleted it and retry.", blackboxDir)
- return err
- }
+ 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
+ }
- downloadCmd := exec.Command("wget", "--quiet", "--no-check-certificate", "https://whisk.sl.cloud9.ibm.com/"+tarFile)
+ 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
+ }
- if err = downloadCmd.Run(); err != nil {
- err = errors.New("Download of docker skeleton failed")
- return err
- }
-
- installCmd := exec.Command("tar", "pxf", tarFile)
-
- if err = installCmd.Run(); err != nil {
- err = errors.New("Could not install docker skeleton")
- return err
- }
-
- rmCmd := exec.Command("rm", tarFile)
- if err = rmCmd.Run(); err != nil {
- // Don't really care...
- }
-
- fmt.Println("The docker skeleton is now installed at the current directory.")
-
- return nil
-}
-
-func swiftInstall() error {
- fmt.Println("swift SDK coming soon")
- return nil
+ fmt.Println(wski18n.T("The docker skeleton is now installed at the current directory."))
+ return nil
}
func iOSInstall() error {
- var err error
- zipFile := "WhiskIOSStarterApp.zip"
- if _, err = os.Stat(zipFile); err == nil {
- err = fmt.Errorf("The path %s already exists. Please delete it and try again", zipFile)
- return err
- }
+ var err error
- url := fmt.Sprintf("https://%s/%s", Properties.APIHost, zipFile)
- downloadCmd := exec.Command("wget", "--quiet", "--no-check-certificate", url)
- if err = downloadCmd.Run(); err != nil {
- err = errors.New("Download of iOS Whisk starter app failed.")
- return err
- }
+ 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("Downloaded iOS whisk starter app. Unzip %s and open the project in Xcode", zipFile)
+ 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
+}
- 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)
+ 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/commands/trigger.go b/commands/trigger.go
index 3323caa..31cdb27 100644
--- a/commands/trigger.go
+++ b/commands/trigger.go
@@ -1,271 +1,550 @@
+/*
+ * 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/json"
- "errors"
- "fmt"
- "strings"
+ "errors"
+ "fmt"
- "github.ibm.com/BlueMix-Fabric/go-whisk/whisk"
+ "github.com/openwhisk/openwhisk-client-go/whisk"
+ "github.com/openwhisk/openwhisk-cli/wski18n"
- "github.com/spf13/cobra"
+ "github.com/spf13/cobra"
+ "github.com/fatih/color"
)
// triggerCmd represents the trigger command
var triggerCmd = &cobra.Command{
- Use: "trigger",
- Short: "work with triggers",
+ Use: "trigger",
+ Short: wski18n.T("work with triggers"),
}
var triggerFireCmd = &cobra.Command{
- Use: "fire <name string> <payload string>",
- Short: "fire trigger event",
+ 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{}
- Run: func(cmd *cobra.Command, args []string) {
+ if whiskErr := checkArgs(args, 1, 2, "Trigger fire",
+ wski18n.T("A trigger name is required. A payload is optional.")); whiskErr != nil {
+ return whiskErr
+ }
- var err error
- var triggerName, payloadArg string
- if len(args) < 1 || len(args) > 2 {
- err = errors.New("Invalid argument list")
- fmt.Println(err)
- return
- }
+ 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)
- triggerName = args[0]
+ return whiskErr
+ }
- payload := map[string]interface{}{}
+ client.Namespace = qName.namespace
- if len(flags.common.param) > 0 {
- parameters, err := parseParameters(flags.common.param)
- if err != nil {
- fmt.Printf("error: %s", err)
- return
- }
+ // 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...)
+ }
- for _, param := range parameters {
- payload[param.Key] = param.Value
- }
- }
+ 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
+ }
+ }
- if len(args) == 2 {
- payloadArg = args[1]
- reader := strings.NewReader(payloadArg)
- err = json.NewDecoder(reader).Decode(&payload)
- if err != nil {
- payload["payload"] = payloadArg
- }
- }
+ 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
+ }
- _, _, err = client.Triggers.Fire(triggerName, payload)
-
- if err != nil {
- fmt.Println(err)
- return
- }
-
- fmt.Println("ok: fired trigger")
- },
+ 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",
- Short: "create new trigger",
+ 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 != "")
- Run: func(cmd *cobra.Command, args []string) {
+ if whiskErr := checkArgs(args, 1, 1, "Trigger create",
+ wski18n.T("A trigger name is required.")); whiskErr != nil {
+ return whiskErr
+ }
- var err error
- if len(args) != 1 {
- err = errors.New("Invalid argument")
- fmt.Println(err)
- return
- }
+ 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
+ }
- triggerName := args[0]
+ client.Namespace = qName.namespace
- parameters, err := parseParameters(flags.common.param)
- if err != nil {
- fmt.Println(err)
- return
- }
- annotations, err := parseAnnotations(flags.common.annotation)
- if err != nil {
- fmt.Println(err)
- return
- }
+ 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
+ }
- trigger := &whisk.Trigger{
- Name: triggerName,
- Parameters: parameters,
- Annotations: annotations,
- }
+ 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("lifecycleEvent", "CREATE"))
+ flags.common.param = append(flags.common.param, getFormattedJSON("triggerName", fullTriggerName))
+ flags.common.param = append(flags.common.param, getFormattedJSON("authKey", client.Config.AuthToken))
+ }
- trigger, _, err = client.Triggers.Insert(trigger, false)
- if err != nil {
- fmt.Println(err)
- return
- }
+ // 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)
- fmt.Println("ok: created trigger")
+ 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
+ }
- printJSON(trigger)
- },
+ // 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",
- Short: "update existing trigger",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
+ if whiskErr := checkArgs(args, 1, 1, "Trigger update",
+ wski18n.T("A trigger name is required.")); whiskErr != nil {
+ return whiskErr
+ }
- var err error
- if len(args) != 1 {
- err = errors.New("Invalid argument")
- fmt.Println(err)
- return
- }
+ 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)
- triggerName := args[0]
- parameters, err := parseParameters(flags.common.param)
- if err != nil {
- fmt.Println(err)
- return
- }
- annotations, err := parseAnnotations(flags.common.annotation)
- if err != nil {
- fmt.Println(err)
- return
- }
+ return whiskErr
+ }
- trigger := &whisk.Trigger{
- Name: triggerName,
- Parameters: parameters,
- Annotations: annotations,
- }
+ client.Namespace = qName.namespace
- trigger, _, err = client.Triggers.Insert(trigger, true)
+ // 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]
- if err != nil {
- fmt.Println(err)
- return
- }
+ whisk.Debug(whisk.DbgInfo, "Parsing parameters: %#v\n", flags.common.param)
+ parameters, err := getJSONFromStrings(flags.common.param, true)
- fmt.Println("ok: updated trigger")
+ 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
+ }
- printJSON(trigger)
- },
+ 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",
- Short: "get trigger",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
- var err error
- if len(args) != 1 {
- err = errors.New("Invalid argument")
- fmt.Println(err)
- return
- }
+ if whiskErr := checkArgs(args, 1, 2, "Trigger get", wski18n.T("A trigger name is required.")); whiskErr != nil {
+ return whiskErr
+ }
- triggerName := args[0]
+ if len(args) > 1 {
+ field = args[1]
- trigger, _, err := client.Triggers.Get(triggerName)
- if err != nil {
- fmt.Println(err)
- return
- }
- fmt.Println("ok: got trigger ", triggerName)
- printJSON(trigger)
- },
+ 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 <name string>",
- Short: "delete trigger",
+ 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
- Run: func(cmd *cobra.Command, args []string) {
- var err error
- if len(args) != 1 {
- err = errors.New("Invalid argument")
- fmt.Println(err)
- return
- }
+ if whiskErr := checkArgs(args, 1, 1, "Trigger delete",
+ wski18n.T("A trigger name is required.")); whiskErr != nil {
+ return whiskErr
+ }
- ruleName := args[0]
+ 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)
- _, err = client.Triggers.Delete(ruleName)
- if err != nil {
- fmt.Println(err)
- return
- }
- fmt.Println("ok: deleted rule ", ruleName)
- },
+ return whiskErr
+ }
+
+ 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
+ }
+
+ // 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 {
+ fullTriggerName := fmt.Sprintf("/%s/%s", qName.namespace, qName.entityName)
+ flags.common.param = append(flags.common.param, getFormattedJSON("lifecycleEvent", "DELETE"))
+ flags.common.param = append(flags.common.param, getFormattedJSON("triggerName", fullTriggerName))
+ flags.common.param = append(flags.common.param, getFormattedJSON("authKey", client.Config.AuthToken))
+
+ err = configureFeed(qName.entityName, fullFeedName)
+ if err != nil {
+ whisk.Debug(whisk.DbgError, "configureFeed(%s, %s) failed: %s\n", qName.entityName, flags.common.feed,
+ 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 string>",
- Short: "list all triggers",
+ 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
+ }
- Run: func(cmd *cobra.Command, args []string) {
- var err error
- qName := qualifiedName{}
- if len(args) == 1 {
- qName, err = parseQualifiedName(args[0])
- if err != nil {
- fmt.Printf("error: %s", err)
- return
- }
- ns := qName.namespace
- if len(ns) == 0 {
- err = errors.New("No valid namespace detected. Make sure that namespace argument is preceded by a \"/\"")
- fmt.Printf("error: %s\n", err)
- return
- }
+ 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
+ },
+}
- client.Namespace = ns
+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)
+ }
- if pkg := qName.packageName; len(pkg) > 0 {
- // todo :: scope call to package
- }
- }
+ return err
+}
- options := &whisk.TriggerListOptions{
- Skip: flags.common.skip,
- Limit: flags.common.limit,
- }
- triggers, _, err := client.Triggers.List(options)
- if err != nil {
- fmt.Println(err)
- return
- }
- fmt.Println(triggers)
- printJSON(triggers)
- },
+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`"))
- triggerCreateCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", []string{}, "annotations")
- triggerCreateCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, "default parameters")
- triggerCreateCmd.Flags().BoolVar(&flags.common.shared, "shared", false, "shared action (default: private)")
+ 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"))
- triggerUpdateCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", []string{}, "annotations")
- triggerUpdateCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, "default parameters")
- triggerUpdateCmd.Flags().BoolVar(&flags.common.shared, "shared", false, "shared action (default: private)")
+ triggerGetCmd.Flags().BoolVarP(&flags.trigger.summary, "summary", "s", false, wski18n.T("summarize trigger details"))
- triggerFireCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, "default parameters")
+ 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, "skip this many entities from the head of the collection")
- triggerListCmd.Flags().IntVarP(&flags.common.limit, "limit", "l", 0, "only return this many entities from the collection")
+ 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,
- )
+ triggerCmd.AddCommand(
+ triggerFireCmd,
+ triggerCreateCmd,
+ triggerUpdateCmd,
+ triggerGetCmd,
+ triggerDeleteCmd,
+ triggerListCmd,
+ )
}
diff --git a/commands/util.go b/commands/util.go
index 829f7d4..ccabcd1 100644
--- a/commands/util.go
+++ b/commands/util.go
@@ -1,231 +1,1070 @@
+/*
+ * 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"
- "strings"
- "github.ibm.com/BlueMix-Fabric/go-whisk/whisk"
+ "bufio"
+ "errors"
+ "fmt"
+ "strings"
- "github.com/fatih/color"
- prettyjson "github.com/hokaccha/go-prettyjson"
+ "github.com/openwhisk/openwhisk-client-go/whisk"
+ "github.com/openwhisk/openwhisk-cli/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
- packageName string
- entityName string
+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)
+func (qName QualifiedName) String() string {
+ output := []string{}
- return strings.Join(output, "")
+ 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, "")
}
-func parseQualifiedName(name string) (qName qualifiedName, err error) {
- if len(name) == 0 {
- err = errors.New("Invalid name format")
- return
- }
- if name[:1] == "/" {
- name = name[1:]
- i := strings.Index(name, "/")
- if i == -1 {
- qName.namespace = name
- return
- }
- if i == 0 {
- err = errors.New("Invalid name format")
- return
- }
+/*
+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.
- qName.namespace = name[:i]
- name = name[i+1:]
- }
+Return a qualifiedName struct
- i := strings.Index(name, "/")
+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 i > 0 {
- qName.packageName = name[:i]
- name = name[i+1:]
- }
+ // 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]
- qName.entityName = name
+ 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
+ }
- return
+ 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 parseKeyValueArray(args []string) ([]whisk.KeyValue, error) {
- parsed := []whisk.KeyValue{}
- if len(args)%2 != 0 {
- err := errors.New("key|value arguments must be submitted in comma-separated pairs")
- return parsed, err
- }
+func getNamespace() (string) {
+ namespace := "_"
- for i := 0; i < len(args); i += 2 {
- keyValue := whisk.KeyValue{
- Key: args[i],
- Value: args[i+1],
- }
- parsed = append(parsed, keyValue)
+ if Properties.Namespace != "" {
+ namespace = Properties.Namespace
+ }
- }
- return parsed, nil
+ return namespace
}
-func parseParameters(args []string) (whisk.Parameters, error) {
- parameters := whisk.Parameters{}
- parsedArgs, err := parseKeyValueArray(args)
- if err != nil {
- return parameters, err
- }
- parameters = whisk.Parameters(parsedArgs)
- return parameters, nil
+/*
+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 parseAnnotations(args []string) (whisk.Annotations, error) {
- annotations := whisk.Annotations{}
- parsedArgs, err := parseKeyValueArray(args)
- if err != nil {
- return annotations, err
- }
- annotations = whisk.Annotations(parsedArgs)
- return annotations, nil
+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()
-var boldPrintf = color.New(color.Bold).PrintfFunc()
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)
- }
+ 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) {
- boldPrintf("actions\n")
- for _, action := range actions {
- publishState := "private"
- if action.Publish {
- publishState = "shared"
- }
- fmt.Printf("%-70s%s\n", fmt.Sprintf("/%s/%s", action.Namespace, action.Name), publishState)
- }
+ 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) {
- boldPrintf("triggers\n")
- for _, trigger := range triggers {
- publishState := "private"
- if trigger.Publish {
- publishState = "shared"
- }
- fmt.Printf("%-70s%s\n", fmt.Sprintf("/%s/%s", trigger.Namespace, trigger.Name), publishState)
- }
+ 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) {
- boldPrintf("packages\n")
- for _, xPackage := range packages {
- publishState := "private"
- if xPackage.Publish {
- publishState = "shared"
- }
- fmt.Printf("%-70s%s\n", fmt.Sprintf("/%s/%s", xPackage.Namespace, xPackage.Name), publishState)
- }
+ 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) {
- boldPrintf("rules\n")
- for _, rule := range rules {
- publishState := "private"
- if rule.Publish {
- publishState = "shared"
- }
- fmt.Printf("%-70s%s\n", fmt.Sprintf("/%s/%s", rule.Namespace, rule.Name), publishState)
- }
+ 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) {
- boldPrintf("namespaces\n")
- for _, namespace := range namespaces {
- fmt.Printf("%s\n", namespace.Name)
- }
+ fmt.Fprintf(color.Output, "%s\n", boldString("namespaces"))
+ for _, namespace := range namespaces {
+ fmt.Printf("%s\n", namespace.Name)
+ }
}
func printActivationList(activations []whisk.Activation) {
- boldPrintf("activations\n")
- for _, activation := range activations {
- fmt.Printf("%s%20s\n", activation.ActivationID, activation.Name)
- }
+ fmt.Fprintf(color.Output, "%s\n", boldString("activations"))
+ for _, activation := range activations {
+ fmt.Printf("%s %-20s\n", activation.ActivationID, activation.Name)
+ }
}
-//
-//
-//
-// func parseParameters(jsonStr string) (whisk.Parameters, error) {
-// parameters := whisk.Parameters{}
-// if len(jsonStr) == 0 {
-// return parameters, nil
-// }
-// reader := strings.NewReader(jsonStr)
-// err := json.NewDecoder(reader).Decode(¶meters)
-// if err != nil {
-// return nil, err
-// }
-// return parameters, nil
-// }
-//
-// func parseAnnotations(jsonStr string) (whisk.Annotations, error) {
-// annotations := whisk.Annotations{}
-// if len(jsonStr) == 0 {
-// return annotations, nil
-// }
-// reader := strings.NewReader(jsonStr)
-// err := json.NewDecoder(reader).Decode(&annotations)
-// if err != nil {
-// return nil, err
-// }
-// return annotations, nil
-// }
+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 |_|
+`
- logo := `
-
-__ ___ _ _
-\ \ / / | (_) | |
- \ \ /\ / /| |__ _ ___| | __
- \ \/ \/ / | '_ \| / __| |/ /
- \ /\ / | | | | \__ \ <
- \/ \/ |_| |_|_|___/_|\_\
-
- `
-
- return logo
+ return logo
}
-func printJSON(v interface{}) {
- output, _ := prettyjson.Marshal(v)
- fmt.Println(string(output))
+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/commands/wsk.go b/commands/wsk.go
index c4b2712..beca322 100644
--- a/commands/wsk.go
+++ b/commands/wsk.go
@@ -1,31 +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"
+import (
+ "github.com/spf13/cobra"
+ "github.com/openwhisk/openwhisk-cli/wski18n"
+)
// WskCmd defines the entry point for the cli.
var WskCmd = &cobra.Command{
- Use: "wsk",
- Short: "Whisk cloud computing command line interface.",
- Long: logoText(),
- PersistentPreRun: parseConfigFlags,
+ 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,
- )
+ WskCmd.AddCommand(
+ actionCmd,
+ activationCmd,
+ packageCmd,
+ ruleCmd,
+ triggerCmd,
+ sdkCmd,
+ propertyCmd,
+ namespaceCmd,
+ listCmd,
+ apiExperimentalCmd,
+ apiCmd,
+ )
- WskCmd.PersistentFlags().BoolVarP(&flags.global.verbose, "verbose", "v", false, "verbose output")
- WskCmd.PersistentFlags().StringVarP(&flags.global.auth, "auth", "u", "", "authorization key")
- WskCmd.PersistentFlags().StringVar(&flags.global.apihost, "apihost", "", "whisk API host")
- WskCmd.PersistentFlags().StringVar(&flags.global.apiversion, "apiversion", "", "whisk API version")
+ 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/gradle/docker.gradle b/gradle/docker.gradle
new file mode 100644
index 0000000..f716c7b
--- /dev/null
+++ b/gradle/docker.gradle
@@ -0,0 +1,99 @@
+import groovy.time.*
+
+/**
+ * Utility to build docker images based in gradle projects
+ *
+ * This extends gradle's 'application' plugin logic with a 'distDocker' task which builds
+ * a docker image from the Dockerfile of the project that applies this file. The image
+ * is automatically tagged and pushed if a tag and/or a registry is given.
+ *
+ * Parameters that can be set on project level:
+ * - dockerImageName (required): The name of the image to build (e.g. controller)
+ * - dockerRegistry (optional): The registry to push to
+ * - dockerImageTag (optional, default 'latest'): The tag for the image
+ * - dockerImagePrefix (optional, default 'whisk'): The prefix for the image,
+ * 'controller' becomes 'whisk/controller' per default
+ * - dockerTimeout (optional, default 840): Timeout for docker operations in seconds
+ * - dockerRetries (optional, default 3): How many times to retry docker operations
+ * - dockerBinary (optional, default 'docker'): The binary to execute docker commands
+ * - dockerBuildArgs (options, default ''): Project specific custom docker build arguments
+ * - dockerHost (optional): The docker host to run commands on, default behaviour is
+ * docker's own DOCKER_HOST environment variable
+ */
+
+ext {
+ dockerRegistry = project.hasProperty('dockerRegistry') ? dockerRegistry + '/' : ''
+ dockerImageTag = project.hasProperty('dockerImageTag') ? dockerImageTag : 'latest'
+ dockerImagePrefix = project.hasProperty('dockerImagePrefix') ? dockerImagePrefix : 'whisk'
+ dockerTimeout = project.hasProperty('dockerTimeout') ? dockerTimeout.toInteger() : 840
+ dockerRetries = project.hasProperty('dockerRetries') ? dockerRetries.toInteger() : 3
+ dockerBinary = project.hasProperty('dockerBinary') ? [dockerBinary] : ['docker']
+ dockerBuildArg = ['build']
+}
+ext.dockerTaggedImageName = dockerRegistry + dockerImagePrefix + '/' + dockerImageName + ':' + dockerImageTag
+
+if(project.hasProperty('dockerHost')) {
+ dockerBinary += ['--host', project.dockerHost]
+}
+
+if(project.hasProperty('dockerBuildArgs')) {
+ dockerBuildArgs.each { arg ->
+ dockerBuildArg += ['--build-arg', arg]
+ }
+}
+
+task distDocker {
+ doLast {
+ def start = new Date()
+ def cmd = dockerBinary + dockerBuildArg + ['-t', dockerImageName, project.buildscript.sourceFile.getParentFile().getAbsolutePath()]
+ retry(cmd, dockerRetries, dockerTimeout)
+ println("Building '${dockerImageName}' took ${TimeCategory.minus(new Date(), start)}")
+ }
+}
+task tagImage {
+ doLast {
+ def versionString = (dockerBinary + ['-v']).execute().text
+ def matched = (versionString =~ /(\d+)\.(\d+)\.(\d+)/)
+
+ def major = matched[0][1] as int
+ def minor = matched[0][2] as int
+
+ def dockerCmd = ['tag']
+ if(major == 1 && minor < 12) {
+ dockerCmd += ['-f']
+ }
+ retry(dockerBinary + dockerCmd + [dockerImageName, dockerTaggedImageName], dockerRetries, dockerTimeout)
+ }
+}
+
+task pushImage {
+ doLast {
+ def cmd = dockerBinary + ['push', dockerTaggedImageName]
+ retry(cmd, dockerRetries, dockerTimeout)
+ }
+}
+pushImage.dependsOn tagImage
+pushImage.onlyIf { dockerRegistry != '' }
+distDocker.finalizedBy pushImage
+
+def retry(cmd, retries, timeout) {
+ println("${new Date()}: Executing '${cmd.join(" ")}'")
+ def proc = cmd.execute()
+ proc.consumeProcessOutput(System.out, System.err)
+ proc.waitForOrKill(timeout * 1000)
+ if(proc.exitValue() != 0) {
+ def message = "${new Date()}: Command '${cmd.join(" ")}' failed with exitCode ${proc.exitValue()}"
+ if(proc.exitValue() == 143) { // 143 means the process was killed (SIGTERM signal)
+ message = "${new Date()}: Command '${cmd.join(" ")}' was killed after ${timeout} seconds"
+ }
+
+ if(retries > 1) {
+ println("${message}, ${retries-1} retries left, retrying...")
+ retry(cmd, retries-1, timeout)
+ }
+ else {
+ println("${message}, no more retries left, aborting...")
+ throw new GradleException(message)
+ }
+ }
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..ca78035
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..cd3a0f7
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 05 17:13:49 EDT 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-bin.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..27309d9
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100755
index 0000000..f6d5974
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/main.go b/main.go
index 208db38..9ee44cb 100644
--- a/main.go
+++ b/main.go
@@ -1,21 +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"
+ "fmt"
+ "os"
+ "reflect"
+ goi18n "github.com/nicksnyder/go-i18n/i18n"
+ "github.com/fatih/color"
- "github.ibm.com/BlueMix-Fabric/go-whisk-cli/commands"
+ "github.com/openwhisk/openwhisk-client-go/whisk"
+ "github.com/openwhisk/openwhisk-cli/commands"
+ "github.com/openwhisk/openwhisk-cli/wski18n"
+ "github.com/mattn/go-colorable"
)
-func main() {
- defer func() {
- if r := recover(); r != nil {
- fmt.Println(r)
- fmt.Println("Application exited unexpectedly")
- }
- }()
+// 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"
- if err := commands.Execute(); err != nil {
- fmt.Println(err)
- return
- }
+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/tests/src/dat/empty.js b/tests/src/dat/empty.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/src/dat/empty.js
diff --git a/tests/src/dat/hello.js b/tests/src/dat/hello.js
new file mode 100644
index 0000000..c40254d
--- /dev/null
+++ b/tests/src/dat/hello.js
@@ -0,0 +1,8 @@
+/**
+ * Hello, world.
+ */
+function main(params) {
+ greeting = 'hello, ' + params.payload + '!'
+ console.log(greeting);
+ return {payload: greeting}
+}
\ No newline at end of file
diff --git a/tests/src/dat/invalidInput1.json b/tests/src/dat/invalidInput1.json
new file mode 100644
index 0000000..da57a79
--- /dev/null
+++ b/tests/src/dat/invalidInput1.json
@@ -0,0 +1,3 @@
+{
+ "invalidJSON":
+}
diff --git a/tests/src/dat/invalidInput2.json b/tests/src/dat/invalidInput2.json
new file mode 100644
index 0000000..275fa9f
--- /dev/null
+++ b/tests/src/dat/invalidInput2.json
@@ -0,0 +1,4 @@
+{
+ "invalid": "JS
+ ON"
+}
diff --git a/tests/src/dat/invalidInput3.json b/tests/src/dat/invalidInput3.json
new file mode 100644
index 0000000..3272df5
--- /dev/null
+++ b/tests/src/dat/invalidInput3.json
@@ -0,0 +1,2 @@
+{
+ "invalid": "JSON"
diff --git a/tests/src/dat/invalidInput4.json b/tests/src/dat/invalidInput4.json
new file mode 100644
index 0000000..63c9ade
--- /dev/null
+++ b/tests/src/dat/invalidInput4.json
@@ -0,0 +1,3 @@
+{
+ "invalid": "JS"ON"
+}
\ No newline at end of file
diff --git a/tests/src/dat/malformed.js b/tests/src/dat/malformed.js
new file mode 100644
index 0000000..587be6b
--- /dev/null
+++ b/tests/src/dat/malformed.js
@@ -0,0 +1 @@
+x
diff --git a/tests/src/integration/command_test.go b/tests/src/integration/command_test.go
new file mode 100644
index 0000000..599cb7f
--- /dev/null
+++ b/tests/src/integration/command_test.go
@@ -0,0 +1,549 @@
+// +build native
+
+package tests
+
+import (
+ "testing"
+ "os"
+ "github.com/stretchr/testify/assert"
+ "github.com/openwhisk/openwhisk-cli/tests/src/integration/common"
+)
+
+var wsk *common.Wsk = common.NewWsk()
+var tmpProp = os.Getenv("GOPATH") + "/src/github.com/openwhisk/openwhisk-cli/wskprops.tmp"
+var invalidArgs []common.InvalidArg
+var invalidParamMsg = "Arguments for '-p' must be a key/value pair"
+var invalidAnnotMsg = "Arguments for '-a' must be a key/value pair"
+var invalidParamFileMsg = "An argument must be provided for '-P'"
+var invalidAnnotFileMsg = "An argument must be provided for '-A'"
+
+var emptyFile = common.GetTestActionFilename("emtpy.js")
+var helloFile = common.GetTestActionFilename("hello.js")
+var missingFile = "notafile"
+var emptyFileMsg = "File '" + emptyFile + "' is not a valid file or it does not exist"
+var missingFileMsg = "File '" + missingFile + "' is not a valid file or it does not exist"
+
+// Test case to check if the binary exits.
+func TestWskExist(t *testing.T) {
+ assert.True(t, wsk.Exists(), "The binary should exist.")
+}
+
+func TestHelpUsageInfoCommand(t *testing.T) {
+ stdout, err := wsk.RunCommand("-h")
+ assert.Equal(t, nil, err, "The command -h failed to run.")
+ assert.Contains(t, string(stdout), "Usage:", "The output of the command -h does not contain \"Usage\".")
+ assert.Contains(t, string(stdout), "Flags:", "The output of the command -h does not contain \"Flags\".")
+ assert.Contains(t, string(stdout), "Available Commands:",
+ "The output of the command -h does not contain \"Available Commands\".")
+ assert.Contains(t, string(stdout), "--help", "The output of the command -h does not contain \"--help\".")
+}
+
+func TestHelpUsageInfoCommandLanguage(t *testing.T) {
+ os.Setenv("LANG", "de_DE")
+ assert.Equal(t, "de_DE", os.Getenv("LANG"), "The environment variable LANG has not been set to de_DE.")
+ TestHelpUsageInfoCommand(t)
+}
+
+func TestShowCLIBuildVersion(t *testing.T) {
+ stdout, err := wsk.RunCommand("property", "get", "--cliversion")
+ assert.Equal(t, nil, err, "The command property get --cliversion failed to run.")
+ assert.Contains(t, string(stdout), "whisk CLI version",
+ "The output of the command property get --cliversion does not contain \"whisk CLI version\".")
+}
+
+func TestShowAPIVersion(t *testing.T) {
+ stdout, err := wsk.RunCommand("property", "get", "--apiversion")
+ assert.Equal(t, nil, err, "The command property get --apiversion failed to run.")
+ assert.Contains(t, string(stdout), "whisk API version",
+ "The output of the command property get --apiversion does not contain \"whisk API version\".")
+}
+
+// Test case to verify the default namespace _.
+func TestDefaultNamespace(t *testing.T) {
+ common.CreateFile(tmpProp)
+ common.WriteFile(tmpProp, []string{"NAMESPACE="})
+
+ os.Setenv("WSK_CONFIG_FILE", tmpProp)
+ assert.Equal(t, os.Getenv("WSK_CONFIG_FILE"), tmpProp, "The environment variable WSK_CONFIG_FILE has not been set.")
+
+ stdout, err := wsk.RunCommand("property", "get", "-i", "--namespace")
+ assert.Equal(t, nil, err, "The command property get -i --namespace failed to run.")
+ assert.Contains(t, common.RemoveRedundentSpaces(string(stdout)), "whisk namespace _",
+ "The output of the command does not contain \"whisk namespace _\".")
+ common.DeleteFile(tmpProp)
+}
+
+// Test case to validate default property values.
+func TestValidateDefaultProperties(t *testing.T) {
+ common.CreateFile(tmpProp)
+
+ os.Setenv("WSK_CONFIG_FILE", tmpProp)
+ assert.Equal(t, os.Getenv("WSK_CONFIG_FILE"), tmpProp, "The environment variable WSK_CONFIG_FILE has not been set.")
+
+ stdout, err := wsk.RunCommand("property", "unset", "--auth", "--apihost", "--apiversion", "--namespace")
+ assert.Equal(t, nil, err, "The command property unset failed to run.")
+ outputString := string(stdout)
+ assert.Contains(t, outputString, "ok: whisk auth unset",
+ "The output of the command does not contain \"ok: whisk auth unset\".")
+ assert.Contains(t, outputString, "ok: whisk API host unset",
+ "The output of the command does not contain \"ok: whisk API host unset\".")
+ assert.Contains(t, outputString, "ok: whisk API version unset",
+ "The output of the command does not contain \"ok: whisk API version unset\".")
+ assert.Contains(t, outputString, "ok: whisk namespace unset",
+ "The output of the command does not contain \"ok: whisk namespace unset\".")
+
+ stdout, err = wsk.RunCommand("property", "get", "--auth")
+ assert.Equal(t, nil, err, "The command property get --auth failed to run.")
+ assert.Equal(t, common.RemoveRedundentSpaces(string(stdout)), "whisk auth",
+ "The output of the command does not equal to \"whisk auth\".")
+
+ stdout, err = wsk.RunCommand("property", "get", "--apihost")
+ assert.Equal(t, nil, err, "The command property get --apihost failed to run.")
+ assert.Equal(t, common.RemoveRedundentSpaces(string(stdout)), "whisk API host",
+ "The output of the command does not equal to \"whisk API host\".")
+
+ stdout, err = wsk.RunCommand("property", "get", "--namespace")
+ assert.Equal(t, nil, err, "The command property get --namespace failed to run.")
+ assert.Equal(t, common.RemoveRedundentSpaces(string(stdout)), "whisk namespace _",
+ "The output of the command does not equal to \"whisk namespace _\".")
+
+ common.DeleteFile(tmpProp)
+}
+
+// Test case to set auth in property file.
+func TestSetAuth(t *testing.T) {
+ common.CreateFile(tmpProp)
+
+ os.Setenv("WSK_CONFIG_FILE", tmpProp)
+ assert.Equal(t, os.Getenv("WSK_CONFIG_FILE"), tmpProp, "The environment variable WSK_CONFIG_FILE has not been set.")
+
+ _, err := wsk.RunCommand("property", "set", "--auth", "testKey")
+ assert.Equal(t, nil, err, "The command property set --auth testKey failed to run.")
+ output := common.ReadFile(tmpProp)
+ assert.Contains(t, output, "AUTH=testKey",
+ "The wsk property file does not contain \"AUTH=testKey\".")
+ common.DeleteFile(tmpProp)
+}
+
+// Test case to set multiple property values with single command.
+func TestSetMultipleValues(t *testing.T) {
+ common.CreateFile(tmpProp)
+
+ os.Setenv("WSK_CONFIG_FILE", tmpProp)
+ assert.Equal(t, os.Getenv("WSK_CONFIG_FILE"), tmpProp, "The environment variable WSK_CONFIG_FILE has not been set.")
+
+ _, err := wsk.RunCommand("property", "set", "--auth", "testKey", "--apihost", "openwhisk.ng.bluemix.net",
+ "--apiversion", "v1")
+ assert.Equal(t, nil, err, "The command property set --auth --apihost --apiversion failed to run.")
+ output := common.ReadFile(tmpProp)
+ assert.Contains(t, output, "AUTH=testKey", "The wsk property file does not contain \"AUTH=testKey\".")
+ assert.Contains(t, output, "APIHOST=openwhisk.ng.bluemix.net",
+ "The wsk property file does not contain \"APIHOST=openwhisk.ng.bluemix.net\".")
+ assert.Contains(t, output, "APIVERSION=v1", "The wsk property file does not contain \"APIVERSION=v1\".")
+ common.DeleteFile(tmpProp)
+}
+
+// Test case to reject bad command.
+func TestRejectBadComm(t *testing.T) {
+ common.CreateFile(tmpProp)
+
+ os.Setenv("WSK_CONFIG_FILE", tmpProp)
+ assert.Equal(t, os.Getenv("WSK_CONFIG_FILE"), tmpProp, "The environment variable WSK_CONFIG_FILE has not been set.")
+
+ stdout, err := wsk.RunCommand("bogus")
+ assert.NotEqual(t, nil, err, "The command bogus should fail to run.")
+ assert.Contains(t, string(stdout), "Run 'wsk --help' for usage",
+ "The output of the command does not contain \"Run 'wsk --help' for usage\".")
+ common.DeleteFile(tmpProp)
+}
+
+// Test case to reject a command when the API host is not set.
+func TestRejectCommAPIHostNotSet(t *testing.T) {
+ common.CreateFile(tmpProp)
+
+ os.Setenv("WSK_CONFIG_FILE", tmpProp)
+ assert.Equal(t, os.Getenv("WSK_CONFIG_FILE"), tmpProp, "The environment variable WSK_CONFIG_FILE has not been set.")
+
+ stdout, err := wsk.RunCommand("property", "get")
+ assert.NotEqual(t, nil, err, "The command property get --apihost --apiversion should fail to run.")
+ assert.Contains(t, common.RemoveRedundentSpaces(string(stdout)),
+ "The API host is not valid: An API host must be provided",
+ "The output of the command does not contain \"The API host is not valid: An API host must be provided\".")
+ common.DeleteFile(tmpProp)
+}
+
+func initInvalidArgsNotEnoughParamsArgs() {
+ invalidArgs = []common.InvalidArg{
+ common.InvalidArg{
+ Cmd: []string{"action", "create", "actionName", "-p"},
+ Err: invalidParamMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "create", "actionName", "-p", "key"},
+ Err: invalidParamMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "create", "actionName", "-P"},
+ Err: invalidParamFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "update", "actionName", "-p"},
+ Err: invalidParamMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "update", "actionName", "-p", "key"},
+ Err: invalidParamMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "update", "actionName", "-P"},
+ Err: invalidParamFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "invoke", "actionName", "-p"},
+ Err: invalidParamMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "invoke", "actionName", "-p", "key"},
+ Err: invalidParamMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "invoke", "actionName", "-P"},
+ Err: invalidParamFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "create", "actionName", "-a"},
+ Err: invalidAnnotMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "create", "actionName", "-a", "key"},
+ Err: invalidAnnotMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "create", "actionName", "-A"},
+ Err: invalidAnnotFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "update", "actionName", "-a"},
+ Err: invalidAnnotMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "update", "actionName", "-a", "key"},
+ Err: invalidAnnotMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "update", "actionName", "-A"},
+ Err: invalidAnnotFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "invoke", "actionName", "-a"},
+ Err: invalidAnnotMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "invoke", "actionName", "-a", "key"},
+ Err: invalidAnnotMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "invoke", "actionName", "-A"},
+ Err: invalidAnnotFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "create", "packageName", "-p"},
+ Err: invalidParamMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "create", "packageName", "-p", "key"},
+ Err: invalidParamMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "create", "packageName", "-P"},
+ Err: invalidParamFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "update", "packageName", "-p"},
+ Err: invalidParamMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "update", "packageName", "-p", "key"},
+ Err: invalidParamMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "update", "packageName", "-P"},
+ Err: invalidParamFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "bind", "packageName", "boundPackageName", "-p"},
+ Err: invalidParamMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "bind", "packageName", "boundPackageName", "-p", "key"},
+ Err: invalidParamMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "bind", "packageName", "boundPackageName", "-P"},
+ Err: invalidParamFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "create", "packageName", "-a"},
+ Err: invalidAnnotMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "create", "packageName", "-a", "key"},
+ Err: invalidAnnotMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "create", "packageName", "-A"},
+ Err: invalidAnnotFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "update", "packageName", "-a"},
+ Err: invalidAnnotMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "update", "packageName", "-a", "key"},
+ Err: invalidAnnotMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "update", "packageName", "-A"},
+ Err: invalidAnnotFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "bind", "packageName", "boundPackageName", "-a"},
+ Err: invalidAnnotMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "bind", "packageName", "boundPackageName", "-a", "key"},
+ Err: invalidAnnotMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "bind", "packageName", "boundPackageName", "-A"},
+ Err: invalidAnnotFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "create", "triggerName", "-p"},
+ Err: invalidParamMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "create", "triggerName", "-p", "key"},
+ Err: invalidParamMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "create", "triggerName", "-P"},
+ Err: invalidParamFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "update", "triggerName", "-p"},
+ Err: invalidParamMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "update", "triggerName", "-p", "key"},
+ Err: invalidParamMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "update", "triggerName", "-P"},
+ Err: invalidParamFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "fire", "triggerName", "-p"},
+ Err: invalidParamMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "fire", "triggerName", "-p", "key"},
+ Err: invalidParamMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "fire", "triggerName", "-P"},
+ Err: invalidParamFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "create", "triggerName", "-a"},
+ Err: invalidAnnotMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "create", "triggerName", "-a", "key"},
+ Err: invalidAnnotMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "create", "triggerName", "-A"},
+ Err: invalidAnnotFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "update", "triggerName", "-a"},
+ Err: invalidAnnotMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "update", "triggerName", "-a", "key"},
+ Err: invalidAnnotMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "update", "triggerName", "-A"},
+ Err: invalidAnnotFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "fire", "triggerName", "-a"},
+ Err: invalidAnnotMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "fire", "triggerName", "-a", "key"},
+ Err: invalidAnnotMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "fire", "triggerName", "-A"},
+ Err: invalidAnnotFileMsg,
+ },
+ }
+}
+
+func initInvalidArgsMissingInvalidParamsAnno(){
+ invalidArgs = []common.InvalidArg{
+ common.InvalidArg{
+ Cmd: []string{"action", "create", "actionName", helloFile, "-P", emptyFile},
+ Err: emptyFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "update", "actionName", helloFile, "-P", emptyFile},
+ Err: emptyFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "invoke", "actionName", "-P", emptyFile},
+ Err: emptyFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "create", "actionName", "-P", emptyFile},
+ Err: emptyFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "update", "actionName", "-P", emptyFile},
+ Err: emptyFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "invoke", "actionName", "-P", emptyFile},
+ Err: emptyFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "create", "packageName", "-P", emptyFile},
+ Err: emptyFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "update", "packageName", "-P", emptyFile},
+ Err: emptyFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "bind", "packageName", "boundPackageName", "-P", emptyFile},
+ Err: emptyFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "create", "packageName", "-P", emptyFile},
+ Err: emptyFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "update", "packageName", "-P", emptyFile},
+ Err: emptyFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "bind", "packageName", "boundPackageName", "-P", emptyFile},
+ Err: emptyFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "create", "triggerName", "-P", emptyFile},
+ Err: emptyFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "update", "triggerName", "-P", emptyFile},
+ Err: emptyFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "fire", "triggerName", "-P", emptyFile},
+ Err: emptyFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "create", "triggerName", "-P", emptyFile},
+ Err: emptyFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "update", "triggerName", "-P", emptyFile},
+ Err: emptyFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "fire", "triggerName", "-P", emptyFile},
+ Err: emptyFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "create", "actionName", helloFile, "-A", missingFile},
+ Err: missingFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "update", "actionName", helloFile, "-A", missingFile},
+ Err: missingFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "invoke", "actionName", "-A", missingFile},
+ Err: missingFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "create", "actionName", "-A", missingFile},
+ Err: missingFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "update", "actionName", "-A", missingFile},
+ Err: missingFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "invoke", "actionName", "-A", missingFile},
+ Err: missingFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "create", "packageName", "-A", missingFile},
+ Err: missingFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "update", "packageName", "-A", missingFile},
+ Err: missingFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "bind", "packageName", "boundPackageName", "-A", missingFile},
+ Err: missingFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "create", "triggerName", "-A", missingFile},
+ Err: missingFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "update", "triggerName", "-A", missingFile},
+ Err: missingFileMsg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "fire", "triggerName", "-A", missingFile},
+ Err: missingFileMsg,
+ },
+ }
+}
+
+// Test case to reject commands that are executed with not enough param or annotation arguments.
+func TestRejectCommandsNotEnoughParamsArgs(t *testing.T) {
+ initInvalidArgsNotEnoughParamsArgs()
+ for _, invalidArg := range invalidArgs {
+ stdout, err := wsk.RunCommand(invalidArg.Cmd...)
+ outputString := string(stdout)
+ assert.NotEqual(t, nil, err, "The command should fail to run.")
+ assert.Equal(t, "exit status 1", err.Error(), "The error should be exit status 1.")
+ assert.Contains(t, outputString, invalidArg.Err,
+ "The output of the command does not contain " + invalidArg.Err)
+ assert.Contains(t, outputString, "Run 'wsk --help' for usage",
+ "The output of the command does not contain \"Run 'wsk --help' for usage\".")
+ }
+}
+
+// Test case to reject commands that are executed with a missing or invalid parameter or annotation file.
+func TestRejectCommandsMissingIvalidParamsAnno(t *testing.T) {
+ initInvalidArgsMissingInvalidParamsAnno()
+ for _, invalidArg := range invalidArgs {
+ stdout, err := wsk.RunCommand(invalidArg.Cmd...)
+ outputString := string(stdout)
+ assert.NotEqual(t, nil, err, "The command should fail to run.")
+ assert.Equal(t, "exit status 2", err.Error(), "The error should be exit status 1.")
+ assert.Contains(t, outputString, invalidArg.Err,
+ "The output of the command does not contain " + invalidArg.Err)
+ assert.Contains(t, outputString, "Run 'wsk --help' for usage",
+ "The output of the command does not contain \"Run 'wsk --help' for usage\".")
+ }
+}
diff --git a/tests/src/integration/common/utils.go b/tests/src/integration/common/utils.go
new file mode 100644
index 0000000..1830490
--- /dev/null
+++ b/tests/src/integration/common/utils.go
@@ -0,0 +1,79 @@
+package common
+
+import (
+ "fmt"
+ "os"
+ "regexp"
+ "io"
+)
+
+func checkError(err error) {
+ if err != nil {
+ fmt.Println(err.Error())
+ os.Exit(0)
+ }
+}
+
+func CreateFile(filePath string) {
+ var _, err = os.Stat(filePath)
+
+ if os.IsNotExist(err) {
+ var file, err = os.Create(filePath)
+ checkError(err)
+ defer file.Close()
+ }
+ return
+}
+
+func ReadFile(filePath string) string {
+ var file, err = os.OpenFile(filePath, os.O_RDWR, 0644)
+ checkError(err)
+ defer file.Close()
+
+ var text = make([]byte, 1024)
+ for {
+ n, err := file.Read(text)
+ if err != io.EOF {
+ checkError(err)
+ }
+ if n == 0 {
+ break
+ }
+ }
+ return string(text)
+}
+
+func WriteFile(filePath string, lines []string) {
+ var file, err = os.OpenFile(filePath, os.O_RDWR, 0644)
+ checkError(err)
+ defer file.Close()
+
+ for _, each := range lines {
+ _, err = file.WriteString(each + "\n")
+ checkError(err)
+ }
+
+ err = file.Sync()
+ checkError(err)
+}
+
+func DeleteFile(filePath string) {
+ var err = os.Remove(filePath)
+ checkError(err)
+}
+
+func RemoveRedundentSpaces(str string) string {
+ re_leadclose_whtsp := regexp.MustCompile(`^[\s\p{Zs}]+|[\s\p{Zs}]+$`)
+ re_inside_whtsp := regexp.MustCompile(`[\s\p{Zs}]{2,}`)
+ final := re_leadclose_whtsp.ReplaceAllString(str, "")
+ return re_inside_whtsp.ReplaceAllString(final, " ")
+}
+
+func GetTestActionFilename(fileName string) string {
+ return os.Getenv("GOPATH") + "/src/github.com/openwhisk/openwhisk-cli/tests/src/dat/" + fileName
+}
+
+type InvalidArg struct {
+ Cmd []string
+ Err string
+}
\ No newline at end of file
diff --git a/tests/src/integration/common/wsk.go b/tests/src/integration/common/wsk.go
new file mode 100644
index 0000000..78716c9
--- /dev/null
+++ b/tests/src/integration/common/wsk.go
@@ -0,0 +1,51 @@
+package common
+
+import (
+ "os"
+ "os/exec"
+)
+
+const cmd = "wsk"
+const arg = "-i"
+
+type Wsk struct {
+ Path string
+ Arg []string
+ Dir string
+ Wskprops *Wskprops
+}
+
+func NewWsk() *Wsk {
+ return NewWskWithPath(os.Getenv("GOPATH") + "/src/github.com/openwhisk/openwhisk-cli/")
+}
+
+func NewWskWithPath(path string) *Wsk {
+ var dep Wsk
+ dep.Path = cmd
+ dep.Arg = []string{arg}
+ dep.Dir = path
+ dep.Wskprops = GetWskprops()
+ return &dep
+}
+
+func (wsk *Wsk)Exists() bool {
+ _, err := os.Stat(wsk.Dir + wsk.Path);
+ if err == nil {
+ return true
+ } else {
+ return false
+ }
+}
+
+func (wsk *Wsk)RunCommand(s ...string) ([]byte, error) {
+ cs := wsk.Arg
+ cs = append(cs, s...)
+ command := exec.Command(wsk.Path, cs...)
+ command.Dir = wsk.Dir
+ return command.CombinedOutput()
+}
+
+func (wsk *Wsk)ListNamespaces() ([]byte, error) {
+ return wsk.RunCommand("namespace", "list", "--apihost", wsk.Wskprops.APIHost,
+ "--auth", wsk.Wskprops.AuthKey)
+}
\ No newline at end of file
diff --git a/tests/src/integration/common/wskprops.go b/tests/src/integration/common/wskprops.go
new file mode 100644
index 0000000..ae493c1
--- /dev/null
+++ b/tests/src/integration/common/wskprops.go
@@ -0,0 +1,39 @@
+package common
+
+import (
+ "github.com/spf13/viper"
+ "io/ioutil"
+ "os"
+)
+
+type Wskprops struct {
+ APIHost string
+ APIVersion string
+ AuthKey string
+ ControllerHost string
+ ControllerPort string
+}
+
+func GetWskprops() *Wskprops {
+ var dep Wskprops
+ dep.APIHost = ""
+ dep.AuthKey = ""
+ dep.APIVersion = "v1"
+
+ viper.SetConfigName("whisk")
+ viper.AddConfigPath(os.Getenv("OPENWHISK_HOME"))
+
+ err := viper.ReadInConfig()
+ if err == nil {
+ authPath := viper.GetString("testing.auth")
+
+ b, err := ioutil.ReadFile(authPath)
+ if err == nil {
+ dep.AuthKey = string(b)
+ }
+ dep.APIHost = viper.GetString("router.host")
+ dep.ControllerHost = viper.GetString("router.host")
+ dep.ControllerPort = viper.GetString("controller.host.port")
+ }
+ return &dep
+}
diff --git a/tests/src/integration/integration_test.go b/tests/src/integration/integration_test.go
new file mode 100644
index 0000000..d46e108
--- /dev/null
+++ b/tests/src/integration/integration_test.go
@@ -0,0 +1,597 @@
+// +build integration
+
+package tests
+
+import (
+ "testing"
+ "github.com/stretchr/testify/assert"
+ "github.com/openwhisk/openwhisk-cli/tests/src/integration/common"
+ "os"
+ "strings"
+)
+
+var invalidArgs []common.InvalidArg
+var invalidArgsMsg = "error: Invalid argument(s)"
+var tooFewArgsMsg = invalidArgsMsg + "."
+var tooManyArgsMsg = invalidArgsMsg + ": "
+var actionNameActionReqMsg = "An action name and action are required."
+var actionNameReqMsg = "An action name is required."
+var actionOptMsg = "An action is optional."
+var packageNameReqMsg = "A package name is required."
+var packageNameBindingReqMsg = "A package name and binding name are required."
+var ruleNameReqMsg = "A rule name is required."
+var ruleTriggerActionReqMsg = "A rule, trigger and action name are required."
+var activationIdReq = "An activation ID is required."
+var triggerNameReqMsg = "A trigger name is required."
+var optNamespaceMsg = "An optional namespace is the only valid argument."
+var optPayloadMsg = "A payload is optional."
+var noArgsReqMsg = "No arguments are required."
+var invalidArg = "invalidArg"
+var apiCreateReqMsg = "Specify a swagger file or specify an API base path with an API path, an API verb, and an action name."
+var apiGetReqMsg = "An API base path or API name is required."
+var apiDeleteReqMsg = "An API base path or API name is required. An optional API relative path and operation may also be provided."
+var apiListReqMsg = "Optional parameters are: API base path (or API name), API relative path and operation."
+var invalidShared = "Cannot use value '" + invalidArg + "' for shared"
+
+func initInvalidArgs() {
+ invalidArgs = []common.InvalidArg{
+ common.InvalidArg {
+ Cmd: []string{"api-experimental", "create"},
+ Err: tooFewArgsMsg + " " + apiCreateReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"api-experimental", "create", "/basepath", "/path", "GET", "action", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ". " + apiCreateReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"api-experimental", "get"},
+ Err: tooFewArgsMsg + " " + apiGetReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"api-experimental", "get", "/basepath", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ". " + apiGetReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"api-experimental", "delete"},
+ Err: tooFewArgsMsg + " " + apiDeleteReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"api-experimental", "delete", "/basepath", "/path", "GET", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ". " + apiDeleteReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"api-experimental", "list", "/basepath", "/path", "GET", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ". " + apiListReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"action", "create"},
+ Err: tooFewArgsMsg + " " + actionNameActionReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"action", "create", "someAction"},
+ Err: tooFewArgsMsg + " " + actionNameActionReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"action", "create", "actionName", "artifactName", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+ common.InvalidArg {
+ Cmd: []string{"action", "update"},
+ Err: tooFewArgsMsg + " " + actionNameReqMsg + " " + actionOptMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"action", "update", "actionName", "artifactName", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ". " + actionNameReqMsg + " " + actionOptMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"action", "delete"},
+ Err: tooFewArgsMsg + " " + actionNameReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"action", "delete", "actionName", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+ common.InvalidArg {
+ Cmd: []string{"action", "get"},
+ Err: tooFewArgsMsg + " " + actionNameReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"action", "get", "actionName", "namespace", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+ common.InvalidArg {
+ Cmd: []string{"action", "list", "namespace", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ". " + optNamespaceMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"action", "invoke"},
+ Err: tooFewArgsMsg + " " + actionNameReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"action", "invoke", "actionName", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+ common.InvalidArg {
+ Cmd: []string{"activation", "list", "namespace", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ". " + optNamespaceMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"activation", "get"},
+ Err: tooFewArgsMsg + " " + activationIdReq,
+ },
+ common.InvalidArg {
+ Cmd: []string{"activation", "get", "activationID", "namespace", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+ common.InvalidArg {
+ Cmd: []string{"activation", "logs"},
+ Err: tooFewArgsMsg + " " + activationIdReq,
+ },
+ common.InvalidArg {
+ Cmd: []string{"activation", "logs", "activationID", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+
+ common.InvalidArg {
+ Cmd: []string{"activation", "result"},
+ Err: tooFewArgsMsg + " " + activationIdReq,
+ },
+ common.InvalidArg {
+ Cmd: []string{"activation", "result", "activationID", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+ common.InvalidArg {
+ Cmd: []string{"activation", "poll", "activationID", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ". " + optNamespaceMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"namespace", "list", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ". " + noArgsReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"namespace", "get", "namespace", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ". " + optNamespaceMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"package", "create"},
+ Err: tooFewArgsMsg + " " + packageNameReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"package", "create", "packageName", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+ common.InvalidArg {
+ Cmd: []string{"package", "create", "packageName", "--shared", invalidArg},
+ Err: invalidShared,
+ },
+ common.InvalidArg {
+ Cmd: []string{"package", "update"},
+ Err: tooFewArgsMsg + " " + packageNameReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"package", "update", "packageName", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+ common.InvalidArg {
+ Cmd: []string{"package", "update", "packageName", "--shared", invalidArg},
+ Err: invalidShared,
+ },
+ common.InvalidArg {
+ Cmd: []string{"package", "get"},
+ Err: tooFewArgsMsg + " " +packageNameReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"package", "get", "packageName", "namespace", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+ common.InvalidArg {
+ Cmd: []string{"package", "bind"},
+ Err: tooFewArgsMsg + " " + packageNameBindingReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"package", "bind", "packageName"},
+ Err: tooFewArgsMsg + " " +packageNameBindingReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"package", "bind", "packageName", "bindingName", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+ common.InvalidArg {
+ Cmd: []string{"package", "list", "namespace", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ". " + optNamespaceMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"package", "delete"},
+ Err: tooFewArgsMsg + " " + packageNameReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"package", "delete", "namespace", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+ common.InvalidArg {
+ Cmd: []string{"package", "refresh", "namespace", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ". " + optNamespaceMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"rule", "enable"},
+ Err: tooFewArgsMsg + " " + ruleNameReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"rule", "enable", "ruleName", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+ common.InvalidArg {
+ Cmd: []string{"rule", "disable"},
+ Err: tooFewArgsMsg + " " + ruleNameReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"rule", "disable", "ruleName", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+ common.InvalidArg {
+ Cmd: []string{"rule", "status"},
+ Err: tooFewArgsMsg + " " + ruleNameReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"rule", "status", "ruleName", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+ common.InvalidArg {
+ Cmd: []string{"rule", "create"},
+ Err: tooFewArgsMsg + " " + ruleTriggerActionReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"rule", "create", "ruleName"},
+ Err: tooFewArgsMsg + " " + ruleTriggerActionReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"rule", "create", "ruleName", "triggerName"},
+ Err: tooFewArgsMsg + " " + ruleTriggerActionReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"rule", "create", "ruleName", "triggerName", "actionName", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+
+ common.InvalidArg {
+ Cmd: []string{"rule", "update"},
+ Err: tooFewArgsMsg + " " + ruleTriggerActionReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"rule", "update", "ruleName"},
+ Err: tooFewArgsMsg + " " + ruleTriggerActionReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"rule", "update", "ruleName", "triggerName"},
+ Err: tooFewArgsMsg + " " + ruleTriggerActionReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"rule", "update", "ruleName", "triggerName", "actionName", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+ common.InvalidArg {
+ Cmd: []string{"rule", "get"},
+ Err: tooFewArgsMsg + " " + ruleNameReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"rule", "get", "ruleName", "namespace", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+ common.InvalidArg {
+ Cmd: []string{"rule", "delete"},
+ Err: tooFewArgsMsg + " " + ruleNameReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"rule", "delete", "ruleName", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+ common.InvalidArg {
+ Cmd: []string{"rule", "list", "namespace", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ". " + optNamespaceMsg,
+ },
+
+ common.InvalidArg {
+ Cmd: []string{"trigger", "fire"},
+ Err: tooFewArgsMsg + " " + triggerNameReqMsg + " " + optPayloadMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"trigger", "fire", "triggerName", "triggerPayload", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ". " + triggerNameReqMsg + " " +optPayloadMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"trigger", "create"},
+ Err: tooFewArgsMsg + " " + triggerNameReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"trigger", "create", "triggerName", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+ common.InvalidArg {
+ Cmd: []string{"trigger", "update"},
+ Err: tooFewArgsMsg + " " + triggerNameReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"trigger", "update", "triggerName", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+
+ common.InvalidArg {
+ Cmd: []string{"trigger", "get"},
+ Err: tooFewArgsMsg + " " + triggerNameReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"trigger", "get", "triggerName", "namespace", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+ common.InvalidArg {
+ Cmd: []string{"trigger", "delete"},
+ Err: tooFewArgsMsg + " " + triggerNameReqMsg,
+ },
+ common.InvalidArg {
+ Cmd: []string{"trigger", "delete", "triggerName", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ".",
+ },
+ common.InvalidArg {
+ Cmd: []string{"trigger", "list", "namespace", invalidArg},
+ Err: tooManyArgsMsg + invalidArg + ". " + optNamespaceMsg,
+ },
+ }
+}
+
+var wsk *common.Wsk = common.NewWsk()
+var tmpProp = os.Getenv("GOPATH") + "/src/github.com/openwhisk/openwhisk-cli/wskprops.tmp"
+
+// Test case to set apihost, auth, and namespace.
+func TestSetAPIHostAuthNamespace(t *testing.T) {
+ common.CreateFile(tmpProp)
+ common.WriteFile(tmpProp, []string{})
+
+ os.Setenv("WSK_CONFIG_FILE", tmpProp)
+ assert.Equal(t, os.Getenv("WSK_CONFIG_FILE"), tmpProp, "The environment variable WSK_CONFIG_FILE has not been set.")
+
+ namespace, _ := wsk.ListNamespaces()
+ namespaces := strings.Split(strings.TrimSpace(string(namespace)), "\n")
+ expectedNamespace := string(namespaces[len(namespaces) - 1])
+ if (wsk.Wskprops.APIHost != "" && wsk.Wskprops.APIHost != "") {
+ stdout, err := wsk.RunCommand("property", "set", "--apihost", wsk.Wskprops.APIHost,
+ "--auth", wsk.Wskprops.AuthKey, "--namespace", expectedNamespace)
+ ouputString := string(stdout)
+ assert.Equal(t, nil, err, "The command property set --apihost --auth --namespace failed to run.")
+ assert.Contains(t, ouputString, "ok: whisk auth set to " + wsk.Wskprops.AuthKey,
+ "The output of the command property set --apihost --auth --namespace does not contain \"whisk auth key setting\".")
+ assert.Contains(t, ouputString, "ok: whisk API host set to " + wsk.Wskprops.APIHost,
+ "The output of the command property set --apihost --auth --namespace does not contain \"whisk API host setting\".")
+ assert.Contains(t, ouputString, "ok: whisk namespace set to " + expectedNamespace,
+ "The output of the command property set --apihost --auth --namespace does not contain \"whisk namespace setting\".")
+ }
+ common.DeleteFile(tmpProp)
+}
+
+// Test case to show api build version using property file.
+func TestShowAPIBuildVersion(t *testing.T) {
+ common.CreateFile(tmpProp)
+
+ os.Setenv("WSK_CONFIG_FILE", tmpProp)
+ assert.Equal(t, os.Getenv("WSK_CONFIG_FILE"), tmpProp, "The environment variable WSK_CONFIG_FILE has not been set.")
+
+ stdout, err := wsk.RunCommand("property", "set", "--apihost", wsk.Wskprops.APIHost,
+ "--apiversion", wsk.Wskprops.APIVersion)
+ assert.Equal(t, nil, err, "The command property set --apihost --apiversion failed to run.")
+ stdout, err = wsk.RunCommand("property", "get", "-i", "--apibuild")
+ assert.Equal(t, nil, err, "The command property get -i --apibuild failed to run.")
+ assert.NotContains(t, common.RemoveRedundentSpaces(string(stdout)), "whisk API build Unknown",
+ "The output of the command property get --apibuild does not contain \"whisk API build Unknown\".")
+ assert.NotContains(t, common.RemoveRedundentSpaces(string(stdout)), "Unable to obtain API build information",
+ "The output of the command property get --apibuild does not contain \"Unable to obtain API build information\".")
+ assert.Contains(t, common.RemoveRedundentSpaces(string(stdout)), "whisk API build 20",
+ "The output of the command property get --apibuild does not contain \"whisk API build 20\".")
+ common.DeleteFile(tmpProp)
+}
+
+// Test case to fail to show api build when setting apihost to bogus value.
+func TestFailShowAPIBuildVersion(t *testing.T) {
+ common.CreateFile(tmpProp)
+
+ os.Setenv("WSK_CONFIG_FILE", tmpProp)
+ assert.Equal(t, os.Getenv("WSK_CONFIG_FILE"), tmpProp, "The environment variable WSK_CONFIG_FILE has not been set.")
+
+ _, err := wsk.RunCommand("property", "set", "--apihost", "xxxx.yyyy")
+ assert.Equal(t, nil, err, "The command property set --apihost failed to run.")
+ stdout, err := wsk.RunCommand("property", "get", "-i", "--apibuild")
+ assert.NotEqual(t, nil, err, "The command property get -i --apibuild does not raise any error.")
+ assert.Contains(t, common.RemoveRedundentSpaces(string(stdout)), "whisk API build Unknown",
+ "The output of the command property get --apibuild does not contain \"whisk API build Unknown\".")
+ assert.Contains(t, common.RemoveRedundentSpaces(string(stdout)), "Unable to obtain API build information",
+ "The output of the command property get --apibuild does not contain \"Unable to obtain API build information\".")
+}
+
+// Test case to show api build using http apihost.
+func TestShowAPIBuildVersionHTTP(t *testing.T) {
+ common.CreateFile(tmpProp)
+
+ os.Setenv("WSK_CONFIG_FILE", tmpProp)
+ assert.Equal(t, os.Getenv("WSK_CONFIG_FILE"), tmpProp, "The environment variable WSK_CONFIG_FILE has not been set.")
+
+ apihost := "http://" + wsk.Wskprops.ControllerHost + ":" + wsk.Wskprops.ControllerPort
+ stdout, err := wsk.RunCommand("property", "set", "--apihost", apihost)
+ assert.Equal(t, nil, err, "The command property set --apihost failed to run.")
+ stdout, err = wsk.RunCommand("property", "get", "-i", "--apibuild")
+ assert.Equal(t, nil, err, "The command property get -i --apibuild failed to run.")
+ assert.NotContains(t, common.RemoveRedundentSpaces(string(stdout)), "whisk API build Unknown",
+ "The output of the command property get --apibuild does not contain \"whisk API build Unknown\".")
+ assert.NotContains(t, common.RemoveRedundentSpaces(string(stdout)), "Unable to obtain API build information",
+ "The output of the command property get --apibuild does not contain \"Unable to obtain API build information\".")
+ assert.Contains(t, common.RemoveRedundentSpaces(string(stdout)), "whisk API build 20",
+ "The output of the command property get --apibuild does not contain \"whisk API build 20\".")
+ common.DeleteFile(tmpProp)
+}
+
+// Test case to reject bad command.
+func TestRejectAuthCommNoKey(t *testing.T) {
+ common.CreateFile(tmpProp)
+
+ os.Setenv("WSK_CONFIG_FILE", tmpProp)
+ assert.Equal(t, os.Getenv("WSK_CONFIG_FILE"), tmpProp, "The environment variable WSK_CONFIG_FILE has not been set.")
+
+ stdout, err := wsk.RunCommand("list", "--apihost", wsk.Wskprops.APIHost,
+ "--apiversion", wsk.Wskprops.APIVersion)
+ assert.NotEqual(t, nil, err, "The command list should fail to run.")
+ assert.Contains(t, common.RemoveRedundentSpaces(string(stdout)), "usage.",
+ "The output of the command does not contain \"usage.\".")
+ assert.Contains(t, common.RemoveRedundentSpaces(string(stdout)), "--auth is required",
+ "The output of the command does not contain \"--auth is required\".")
+ common.DeleteFile(tmpProp)
+}
+
+// Test case to reject commands that are executed with invalid arguments.
+func TestRejectCommInvalidArgs(t *testing.T) {
+ initInvalidArgs()
+ for _, invalidArg := range invalidArgs {
+ cs := invalidArg.Cmd
+ cs = append(cs, "--apihost", wsk.Wskprops.APIHost)
+ stdout, err := wsk.RunCommand(cs...)
+ outputString := string(stdout)
+ assert.NotEqual(t, nil, err, "The command should fail to run.")
+ assert.Equal(t, "exit status 1", err.Error(), "The error should be exit status 1.")
+ assert.Contains(t, outputString, invalidArg.Err,
+ "The output of the command does not contain " + invalidArg.Err)
+ assert.Contains(t, outputString, "Run 'wsk --help' for usage",
+ "The output of the command does not contain \"Run 'wsk --help' for usage\".")
+ }
+}
+
+// Test case to reject commands that are executed with invalid JSON for annotations and parameters.
+func TestRejectCommInvalidJSON(t *testing.T) {
+ helloFile := common.GetTestActionFilename("hello.js")
+ var invalidJSONInputs = []string{
+ "{\"invalid1\": }",
+ "{\"invalid2\": bogus}",
+ "{\"invalid1\": \"aKey\"",
+ "invalid \"string\"",
+ "{\"invalid1\": [1, 2, \"invalid\"\"arr\"]}",
+ }
+ var invalidJSONFiles = []string{
+ common.GetTestActionFilename("malformed.js"),
+ common.GetTestActionFilename("invalidInput1.json"),
+ common.GetTestActionFilename("invalidInput2.json"),
+ common.GetTestActionFilename("invalidInput3.json"),
+ common.GetTestActionFilename("invalidInput4.json"),
+ }
+ var invalidParamArg = "Invalid parameter argument"
+ var invalidAnnoArg = "Invalid annotation argument"
+ var paramCmds = []common.InvalidArg{
+ common.InvalidArg{
+ Cmd: []string{"action", "create", "actionName", helloFile},
+ Err: invalidParamArg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "update", "actionName", helloFile},
+ Err: invalidParamArg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "invoke", "actionName"},
+ Err: invalidParamArg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "create", "packageName"},
+ Err: invalidParamArg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "update", "packageName"},
+ Err: invalidParamArg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "bind", "packageName", "boundPackageName"},
+ Err: invalidParamArg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "create", "triggerName"},
+ Err: invalidParamArg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "update", "triggerName"},
+ Err: invalidParamArg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "fire", "triggerName"},
+ Err: invalidParamArg,
+ },
+ }
+
+ var annotCmds = []common.InvalidArg{
+ common.InvalidArg{
+ Cmd: []string{"action", "create", "actionName", helloFile},
+ Err: invalidAnnoArg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"action", "update", "actionName", helloFile},
+ Err: invalidAnnoArg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "create", "packageName"},
+ Err: invalidAnnoArg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "update", "packageName"},
+ Err: invalidAnnoArg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"package", "bind", "packageName", "boundPackageName"},
+ Err: invalidAnnoArg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "create", "triggerName"},
+ Err: invalidAnnoArg,
+ },
+ common.InvalidArg{
+ Cmd: []string{"trigger", "update", "triggerName"},
+ Err: invalidAnnoArg,
+ },
+ }
+
+ for _, cmd := range paramCmds {
+ for _, invalid := range invalidJSONInputs {
+ cs := cmd.Cmd
+ cs = append(cs, "-p", "key", invalid, "--apihost", wsk.Wskprops.APIHost)
+ stdout, err := wsk.RunCommand(cs...)
+ outputString := string(stdout)
+ assert.NotEqual(t, nil, err, "The command should fail to run.")
+ assert.Equal(t, "exit status 1", err.Error(), "The error should be exit status 1.")
+ assert.Contains(t, outputString, cmd.Err,
+ "The output of the command does not contain " + cmd.Err + " .")
+ }
+ for _, invalid := range invalidJSONFiles {
+ cs := cmd.Cmd
+ cs = append(cs, "-P", invalid, "--apihost", wsk.Wskprops.APIHost)
+ stdout, err := wsk.RunCommand(cs...)
+ outputString := string(stdout)
+ assert.NotEqual(t, nil, err, "The command should fail to run.")
+ assert.Equal(t, "exit status 1", err.Error(), "The error should be exit status 1.")
+ assert.Contains(t, outputString, cmd.Err,
+ "The output of the command does not contain " + cmd.Err + " .")
+ }
+ }
+
+ for _, cmd := range annotCmds {
+ for _, invalid := range invalidJSONInputs {
+ cs := cmd.Cmd
+ cs = append(cs, "-a", "key", invalid, "--apihost", wsk.Wskprops.APIHost)
+ stdout, err := wsk.RunCommand(cs...)
+ outputString := string(stdout)
+ assert.NotEqual(t, nil, err, "The command should fail to run.")
+ assert.Equal(t, "exit status 1", err.Error(), "The error should be exit status 1.")
+ assert.Contains(t, outputString, cmd.Err,
+ "The output of the command does not contain " + cmd.Err + " .")
+ }
+ for _, invalid := range invalidJSONFiles {
+ cs := cmd.Cmd
+ cs = append(cs, "-A", invalid, "--apihost", wsk.Wskprops.APIHost)
+ stdout, err := wsk.RunCommand(cs...)
+ outputString := string(stdout)
+ assert.NotEqual(t, nil, err, "The command should fail to run.")
+ assert.Equal(t, "exit status 1", err.Error(), "The error should be exit status 1.")
+ assert.Contains(t, outputString, cmd.Err,
+ "The output of the command does not contain " + cmd.Err + " .")
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/travis/build_tag_releases.sh b/tools/travis/build_tag_releases.sh
new file mode 100755
index 0000000..71298f0
--- /dev/null
+++ b/tools/travis/build_tag_releases.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+
+declare -a os_list=()
+declare -a arc_list=("amd64" "386")
+build_file_name=${1:-"wsk"}
+zip_file_name=${2:-"OpenWhisk_CLI"}
+os=$TRAVIS_OS_NAME
+
+if [[ $TRAVIS_OS_NAME == 'linux' ]]; then
+ # Currently we have not set up the CI designated to build windows binaries, so we tentatively
+ # add the windows build into the linux CI environment.
+ os_list=("linux" "windows")
+elif [[ $TRAVIS_OS_NAME == 'osx' ]]; then
+ os_list=("darwin")
+fi
+
+for os in "${os_list[@]}"
+do
+ for arc in "${arc_list[@]}"
+ do
+ wsk=$build_file_name
+ os_name=$os
+ if [ "$os" == "windows" ]; then
+ wsk="$wsk.exe"
+ fi
+ if [ "$os" == "darwin" ]; then
+ os_name="mac"
+ fi
+ cd $TRAVIS_BUILD_DIR
+ GOOS=$os GOARCH=$arc go build -o build/$os/$arc/$wsk
+ cd build/$os/$arc
+ zip -r "$TRAVIS_BUILD_DIR/$zip_file_name-$TRAVIS_TAG-$os_name-$arc.zip" $wsk
+ done
+done
diff --git a/tools/travis/install_openwhisk.sh b/tools/travis/install_openwhisk.sh
new file mode 100755
index 0000000..ae0d5ed
--- /dev/null
+++ b/tools/travis/install_openwhisk.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+
+HOMEDIR="$(dirname "$TRAVIS_BUILD_DIR")"
+cd $HOMEDIR
+
+sudo gpasswd -a travis docker
+sudo -E bash -c 'echo '\''DOCKER_OPTS="-H tcp://0.0.0.0:4243 -H unix:///var/run/docker.sock --api-enable-cors --storage-driver=aufs"'\'' > /etc/default/docker'
+
+# Docker
+sudo apt-get -y update -qq
+sudo apt-get -o Dpkg::Options::="--force-confold" --force-yes -y install docker-engine=1.12.0-0~trusty
+sudo service docker restart
+echo "Docker Version:"
+docker version
+echo "Docker Info:"
+docker info
+
+# Ansible
+pip install --user ansible==2.3.0.0
+
+# Clone the OpenWhisk code
+git clone --depth 3 https://github.com/openwhisk/openwhisk.git
+
+# Build script for Travis-CI.
+WHISKDIR="$HOMEDIR/openwhisk"
+
+ANSIBLE_CMD="ansible-playbook -i environments/local"
+
+cd $WHISKDIR/ansible
+$ANSIBLE_CMD setup.yml
+$ANSIBLE_CMD prereq.yml
+$ANSIBLE_CMD couchdb.yml
+$ANSIBLE_CMD initdb.yml
+
+cd $WHISKDIR
+./gradlew distDocker
+
+cd $WHISKDIR/ansible
+$ANSIBLE_CMD wipe.yml
+$ANSIBLE_CMD openwhisk.yml
diff --git a/wsk b/wsk
deleted file mode 100755
index 47cde91..0000000
--- a/wsk
+++ /dev/null
Binary files differ
diff --git a/wski18n/detection.go b/wski18n/detection.go
new file mode 100644
index 0000000..03d9be8
--- /dev/null
+++ b/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/wski18n/i18n.go b/wski18n/i18n.go
new file mode 100644
index 0000000..3c68b5f
--- /dev/null
+++ b/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/wski18n/i18n_resources.go b/wski18n/i18n_resources.go
new file mode 100644
index 0000000..723b874
--- /dev/null
+++ b/wski18n/i18n_resources.go
@@ -0,0 +1,469 @@
+// Code generated by go-bindata.
+// sources:
+// wski18n/resources/.DS_Store
+// wski18n/resources/de_DE.all.json
+// wski18n/resources/en_US.all.json
+// wski18n/resources/es_ES.all.json
+// wski18n/resources/fr_FR.all.json
+// wski18n/resources/it_IT.all.json
+// wski18n/resources/ja_JA.all.json
+// wski18n/resources/ko_KR.all.json
+// wski18n/resources/pt_BR.all.json
+// wski18n/resources/zh_Hans.all.json
+// wski18n/resources/zh_Hant.all.json
+// DO NOT EDIT!
+
+package wski18n
+
+import (
+ "bytes"
+ "compress/gzip"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+)
+
+func bindataRead(data []byte, name string) ([]byte, error) {
+ gz, err := gzip.NewReader(bytes.NewBuffer(data))
+ if err != nil {
+ return nil, fmt.Errorf("Read %q: %v", name, err)
+ }
+
+ var buf bytes.Buffer
+ _, err = io.Copy(&buf, gz)
+ clErr := gz.Close()
+
+ if err != nil {
+ return nil, fmt.Errorf("Read %q: %v", name, err)
+ }
+ if clErr != nil {
+ return nil, err
+ }
+
+ return buf.Bytes(), nil
+}
+
+type asset struct {
+ bytes []byte
+ info os.FileInfo
+}
+
+type bindataFileInfo struct {
+ name string
+ size int64
+ mode os.FileMode
+ modTime time.Time
+}
+
+func (fi bindataFileInfo) Name() string {
+ return fi.name
+}
+func (fi bindataFileInfo) Size() int64 {
+ return fi.size
+}
+func (fi bindataFileInfo) Mode() os.FileMode {
+ return fi.mode
+}
+func (fi bindataFileInfo) ModTime() time.Time {
+ return fi.modTime
+}
+func (fi bindataFileInfo) IsDir() bool {
+ return false
+}
+func (fi bindataFileInfo) Sys() interface{} {
+ return nil
+}
+
+var _wski18nResourcesDs_store = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x97\x41\x4e\xea\x40\x18\x80\xbf\x29\x5d\x34\xef\x25\xa4\xcb\xb7\xec\xf2\xad\x48\xbc\x41\x25\x40\x44\x77\xa0\x5b\x47\x10\x8c\x62\x43\x8d\xa0\x0b\x57\x1c\xc3\x43\x79\x00\x0f\xe0\x01\xbc\x81\xa6\xed\x6f\x40\x0b\x0e\x1b\x63\x35\xff\x97\x90\x6f\xc1\x7c\x65\x42\xda\xe9\x0c\x60\x9a\x37\xa3\x1d\x08\x81\x80\xc2\xfe\x1f\xd6\x12\xc8\xa7\x84\x27\xce\x3a\x93\x5f\xa3\x4d\x83\x01\x09\x09\x8d\xf5\xd7\x52\x14\xa5\x42\x64\xcf\x6e\x9d\x11\x63\x2c\xad\x77\xcf\xef\x84\x19\x29\xd3\x6e\x92\x9e\x0e\x93\x74\x28\x8b\x44\x07\xf8\xff\x92\x93\xf7\x75\xc6\x4c\xb1\x1c\xd1\x77\xb6\x0f\x6b\xda\x19\x96\xb6\xbb\x35\xbd\x52\x7b\xc6\x35\x96\x0e\x3d\x67\xfb\x58\x6a\x2f\x98\x63\xe9\x72\xe8\x6a\xbd\xe3\x52\x3b\x61\x80\x65\x9f\x5d\x67\xfb\x54\x6a\x2f\x49\xb1\x1c\xb8\xe7\x5c\x9b\x94\xda\xab\x7c\xce\xcd\x2d\xda\xe7\x0f\x6d\xc8\x1d\xe7\x58\xf6\x18\x30\x65\xe6\xea\xfd\xdb\x4f\xfb\xf9\x56\xf7\xc8\xfd\x4a\xaf\x54\x13\x53\x28\xf8\xfb\xdd\x13\x51\x14\xa5\x72\x64\xeb\x43\x24\x8e\xc5\x8b\xc2\x46\xbe\xf7\xc4\xfe\x4a\x13\x8a\x23\x71\x2c\x5e\x14\x36\x32\xce\x13\xfb\xe2\x40\x1c\x8a\x23\x71\x2c\x5e\x14\x96\x45\xcb\xc8\xe1\xc3\xc8\x2f\x1b\x39\xa1\x98\x50\x1c\x89\xe3\xaf\xf9\x6f\x14\xe5\xa7\x53\x2b\x14\x66\xef\xff\xf6\xe6\xf3\xbf\xa2\x28\xbf\x18\xe3\xb7\xfa\xad\xe6\xf2\x40\x50\xc2\x93\x8d\xc0\xc9\x5b\xb0\x61\x23\x20\x63\xb3\x57\xf1\x3f\x96\x63\x75\x23\xa0\x28\x15\xe3\x35\x00\x00\xff\xff\x7e\xe4\x42\x91\x04\x18\x00\x00")
+
+func wski18nResourcesDs_storeBytes() ([]byte, error) {
+ return bindataRead(
+ _wski18nResourcesDs_store,
+ "wski18n/resources/.DS_Store",
+ )
+}
+
+func wski18nResourcesDs_store() (*asset, error) {
+ bytes, err := wski18nResourcesDs_storeBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "wski18n/resources/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1491519881, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _wski18nResourcesDe_deAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00")
+
+func wski18nResourcesDe_deAllJsonBytes() ([]byte, error) {
+ return bindataRead(
+ _wski18nResourcesDe_deAllJson,
+ "wski18n/resources/de_DE.all.json",
+ )
+}
+
+func wski18nResourcesDe_deAllJson() (*asset, error) {
+ bytes, err := wski18nResourcesDe_deAllJsonBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "wski18n/resources/de_DE.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1491513831, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _wski18nResourcesEn_usAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd4\x5d\x5f\x73\xdb\x38\x92\x7f\x9f\x4f\xd1\x95\x17\x3b\x55\xb6\xb3\xfb\x74\x75\x99\x9a\x07\x4d\xec\xd9\x78\x93\xd8\x2e\xcb\x99\xdd\xa9\x9b\xab\x11\x44\x42\x12\xd6\x14\xc0\x01\x40\x3b\x8a\xd7\xdf\xfd\x0a\x00\x49\x91\x12\xfe\x92\x72\xb2\xf7\x14\x47\xec\xfe\x75\xb3\xf1\xaf\xd1\xe8\x06\xff\xe7\x07\x80\xa7\x1f\x00\x00\x5e\x91\xfc\xd5\x5b\x78\x35\x29\xcb\x82\x64\x48\x12\x46\x01\x7f\x21\x12\xe7\x50\x51\xfc\xa5\xc4\x99\xc4\x79\xb1\x79\x75\x62\x88\x25\x47\x54\x14\x9a\x2c\x86\xeb\x07\x80\xe7\x93\x5d\x51\xb7\x15\x85\xa3\xa7\xa7\xb3\x2b\xb4\xc6\xcf\xcf\x70\x7a\xba\xc2\x45\x79\x04\x0b\xc6\xa1\x12\x68\x89\xcf\x7e\xa7\x0e\x71\x31\x9c\x56\x91\x98\x73\xc6\xdf\x82\x03\xb6\x79\x6a\x65\xa5\x4c\x82\xc0\xd2\xc1\xda\x3c\xb5\xb2\x5e\x97\x98\xfe\x63\x45\xc4\x3d\x64\x05\xab\x72\xc8\xd8\xba\xac\x24\xa1\x4b\xf5\xd7\x1a\xd1\x1c\x0a\x42\x31\x10\x2a\x31\x5f\xa0\x0c\x9f\x39\x84\xa4\xe3\x58\xd5\x79\xc0\x7c\xce\x04\x06\x56\xc9\xb2\x72\xbd\xd0\x0e\x91\x15\x28\xc7\xf3\x6a\x09\x05\x7e\xc0\x85\x1f\xcc\x42\x68\x05\x44\x95\x5c\x31\x4e\xbe\x9a\x8e\x34\xfb\x70\xf1\xdb\xcc\x81\x68\xa3\xb4\x42\x3e\x6a\x7b\x4d\x6e\x2e\x61\xf6\xfe\x7a\x7a\xe7\xc2\xdb\x23\x0b\x81\xfd\x7a\x71\x3b\xbd\xbc\xbe\x8a\xc0\x6b\x29\xad\x90\xf3\x4d\x89\x84\x80\x0c\x73\x49\x16\x6a\x08\x61\xc8\x56\x38\xbb\x27\x74\xe9\x80\xf6\x71\x58\x45\x7c\xa6\x68\x5e\x60\x90\x0c\x08\x25\x92\xa0\x82\x7c\xc5\x20\x30\x7f\xc0\x1c\x32\x46\x29\xce\x14\xf4\x5b\x78\x7a\x3a\xc3\x9c\x3f\x3f\x3b\xe4\x26\xc3\x58\x95\xb9\x41\x1c\xad\xb1\xc4\x1c\x10\x5f\x56\x6b\x4c\xa5\x80\x75\x25\x24\xcc\x31\x20\xb8\xc7\x1b\x78\x40\x45\x85\xa1\x44\x84\x6b\x2c\xc4\x97\xc2\xa9\xd3\x50\x34\xab\x6a\x13\x4a\x99\x34\x1d\xea\x10\xba\x0d\x86\xb3\x2a\xf7\x0b\x22\x05\xce\x95\xf5\x4b\xc4\x05\xde\x42\x06\xdb\x2d\x86\xd3\xde\xdb\x19\xbf\x87\x47\x22\x57\x40\xd1\x1a\x8b\x12\x65\x58\xb8\xba\xbb\x8d\xd4\x0a\x5a\x10\x21\x01\x53\x49\x24\xc1\x02\x08\x05\xb9\xc2\x90\x55\x9c\x63\x2a\xb7\xcc\x0e\x31\x91\xcc\x81\x51\xc0\xe6\x12\xd5\xbc\x1a\x90\x2d\x00\x3d\x20\x52\xe8\xe7\x5b\xfd\x13\x06\x44\x3a\xa2\x55\xc5\x25\x96\x20\x39\x59\x2e\x31\x17\x27\x80\xf4\x78\x52\x7f\xd0\x1c\x78\x55\x6c\xdf\x98\xe3\x25\x11\x92\x6f\xf4\x82\x87\x82\x56\x1b\x0d\x6b\x55\x56\xad\xbf\x54\xaf\xbf\x47\x40\x04\xa8\x05\x10\xa9\x0e\x4d\x72\xf8\xb3\x42\x05\x59\x10\x9c\x6b\x8c\xa0\x1d\x87\x20\xa5\x37\x71\xdb\x6d\xd4\xdb\xb5\xef\x06\x8d\x70\xfd\xbf\xe7\xe7\xa3\x71\xad\x9e\x2e\xc4\xfa\x22\x17\x9d\x2e\xde\xf2\x69\xa6\x0e\x8a\xd3\x3b\x8a\xe5\x76\x8f\x4f\x5b\xe7\xf5\x0d\x49\x2b\x7d\x60\x4e\x29\x51\x76\x8f\x96\x11\x33\x4a\x4b\x68\x5f\x3f\x09\xcd\xd5\xc4\x66\x96\x01\xa1\xda\x05\x35\x2c\xae\x05\xd4\xc7\x62\x15\x72\x49\x4d\x77\x2c\xf7\x96\x1b\xdd\xb4\xfa\xe7\x98\xbe\x93\x8e\xe3\x55\x07\xed\xaf\x30\x1a\x67\xfb\x7b\x8a\x52\xa9\x68\x56\xd5\x7e\x26\x34\xd7\x0e\x29\xc7\x06\x6a\xa1\x57\x9f\xa0\x12\x61\x3e\xab\xb8\xa7\xa7\x33\x76\xff\xfc\x6c\xd8\x70\x0e\xf3\x1a\xa6\x9d\x51\x9c\x83\x24\x86\xd3\x2a\xd2\x30\xa8\xf9\x11\x3f\x06\x3a\x9a\x95\xd4\xe1\x15\xe9\x87\xc9\x66\x0b\xf3\x45\x99\xad\xd6\x6d\x80\xd9\x6c\x9c\x56\x91\x55\x99\x6b\x5b\xe8\xfd\xa1\xd0\x9b\x96\x9a\xf7\x04\x18\x87\xd6\x54\x0d\x20\x59\x00\x91\x90\x33\x6c\x16\x04\xcd\xe4\xd0\xe9\x20\xd0\xde\x66\xa9\x25\x24\x36\x8a\x8b\xcb\xdb\x24\x86\x69\x48\x93\xf8\x38\x9d\xee\x86\xbf\x03\x77\x29\xbc\x06\x52\x84\x89\xd6\xb1\xb2\x78\x4d\xb3\x64\x72\x80\x59\x5c\x5c\x8e\x1d\x6d\x81\x25\x0e\x58\x65\x87\xc8\x6b\x98\x9a\x36\xd1\x36\x2e\x2e\xaf\x79\x0c\xd3\x90\x9e\xe3\xe3\xf4\x38\x09\x45\x11\x5a\xc4\xf7\xe9\xac\x70\x57\xac\x76\xf7\xb6\x1e\x53\x8e\xa5\x8e\x1d\x9d\x81\x8e\xf5\x3c\x8a\x7b\x28\x39\x2b\x31\x97\x1b\x10\x58\xc2\xe9\x69\x4b\x7b\xa4\x06\x39\xa6\xa2\xe2\x58\xfb\x62\xea\xc1\x76\x05\x23\x02\x4a\x8e\x33\x9c\xab\x39\x7e\x03\x08\x7e\x7f\xf5\xe6\xf7\x57\x0e\x7d\xbf\x83\x22\xe9\x8e\x6c\x63\x4b\x87\x8f\x39\xda\x87\x4d\xc2\xb7\xaa\xcf\xf1\x82\x63\xd1\x7a\x6e\xcd\xda\xea\xea\x25\x4e\x72\xef\xc8\x6a\xb8\x9c\x5a\xa6\x0e\xba\x01\x80\xae\xf1\x68\x18\x1a\x44\x9c\x83\xa8\xb2\x0c\x0b\xb1\xa8\x8a\x62\xe3\x1b\x8e\x21\x46\x8f\x47\xd2\xba\x30\xe2\xad\xd7\x1d\xe9\xd2\x79\x56\xea\x30\xdc\x3e\x9d\x67\x4a\x0d\xc3\xed\xd3\x59\xe1\xee\x56\xed\xcc\xbb\x6d\x31\x8c\xa4\x1a\x74\xf5\xe6\x91\xac\xcb\x02\xab\x31\x87\xf3\x66\x6b\x2b\x11\x57\xeb\x4d\x8e\xcb\x82\x6d\xd4\x23\x87\x12\x87\x42\x3f\x48\xcf\x85\xbc\xd2\x63\x74\x1b\x49\x87\xf7\x77\x77\x37\x20\x24\x92\x95\x80\x8c\xe5\x66\x57\xa7\xfe\x38\x58\xef\x4e\x14\x6a\x0f\xe3\x6e\x77\x12\x3a\xc2\xa5\x77\xa2\xb3\x0f\x17\xbf\xc1\xaf\x93\x8f\x9f\x2f\x66\x4a\x89\x35\x72\xb5\x41\x2c\xb7\x55\xf4\xec\x97\xcb\x8f\x17\x33\xc8\x18\x55\xf3\x9a\x72\x05\xad\x70\x7f\x9f\x5e\x5f\xf9\xb5\x18\x00\xb4\xa3\x10\x65\x12\x9f\x4a\x76\xda\x00\x33\x2e\x14\xf0\xf9\x35\x5c\x5d\xdf\xc1\xdd\xed\xe4\x6a\xfa\x71\x72\x77\x01\x77\xef\x2f\xe0\x68\x83\xc5\x11\x4c\xae\xce\xe1\x88\xb2\xa3\x33\x80\xbb\xf7\xd7\xd3\x0b\x98\xdc\x5e\xc0\x2f\x97\xff\xbc\x38\x87\x77\x1f\x2f\x61\x72\xfb\xb7\xcf\x9f\x2e\xae\xee\x8c\x1d\xa6\x8d\xe2\xe6\xc5\x9b\x5e\xfb\x40\x04\x99\x93\x82\xc8\x0d\xcc\xa6\xef\xae\x6f\x2e\x66\x3f\xc2\x06\x0b\xf8\x09\xc4\x0a\x71\x9c\x9f\x00\x65\xf0\x13\x94\x9c\x3c\x20\xe9\xf2\x70\x06\x82\x59\x5b\x44\x54\xeb\x35\xe2\xe4\xeb\x76\x60\xe5\x58\x22\x52\xb8\x56\x03\x37\xbd\x15\x9e\xd0\xac\xa8\x72\x0c\x65\x35\x2f\x48\x56\x6c\x6a\xcd\xf6\xa2\x84\x1c\x8b\xaa\x70\x35\x76\x22\x88\xfd\x68\xe9\x8b\xc1\x50\x74\x0b\xc2\x85\x84\xd9\xf4\xc3\xe5\xcd\x0c\x68\xb5\x9e\x63\xde\x5f\x59\x39\x5b\x87\xb5\x1a\x83\x68\x55\x91\xd1\x62\x03\x1c\xcb\x8a\x53\x98\x7d\xbc\xfc\x74\x79\xe7\xc7\xca\x58\x51\x98\xa8\xbe\x43\xc3\x11\x80\x56\x05\x1b\xcf\xca\xd5\x2d\x9b\xc7\x81\xd0\x92\x39\x7c\xa9\xa9\x49\x44\x88\x69\x8f\xc1\xde\x93\xd5\x6e\xc8\xaf\x61\x8f\x24\xe0\xda\x29\x5a\x65\x94\xd6\x9d\xd4\xb3\x4a\x82\xef\x16\x04\xf0\x6e\x17\xcc\x3b\xa3\x4a\xae\x0c\x10\xd3\x27\x10\x95\x5c\x45\x6c\x18\xfc\xbc\x11\xef\x3d\xb9\xb9\x84\x15\x13\xd2\xa8\xfc\xa3\x7e\x8d\xfe\x6f\x26\xf4\x54\x12\xf5\x4b\x1d\x18\x26\x26\x50\x95\x68\xa1\x03\x89\x8a\xb0\x65\x8b\xba\xb5\x89\x81\x8c\xb4\xa7\x8f\x3f\x52\xfc\x03\xe6\x42\x2d\x50\x5b\x84\xfa\x97\x24\x25\xfc\x28\xf6\xb3\xb3\x4a\xae\xd4\x6c\x99\x69\x97\xb0\x12\x98\x6f\x03\x2c\x2b\xf4\x80\xed\x3e\xc7\x8f\x5a\x44\x73\x78\x1d\xe9\xaf\xbf\x88\x28\xfb\xbe\xd4\xea\x27\x35\x7e\xa0\x25\xf2\x5f\xe0\x3c\x1c\x31\x1f\x8b\x1a\xd1\x15\xb6\x16\xd8\x36\x61\x64\x2c\x20\x02\x60\xd0\xbc\x76\x2c\x5e\x8f\x9e\xda\xfa\x18\xf6\xcd\x0b\x8d\x98\xa5\x77\x88\x02\xef\x63\xa8\x47\xcd\xd4\x11\x10\xb1\x73\xb5\x86\x8a\x9f\x9e\x0d\x79\x52\x97\x89\x97\xb0\xcb\x93\x32\x47\xc6\x4b\xd9\x61\x49\x9c\x09\xd3\xe4\xf4\xb9\xac\xa2\xcc\x0a\x92\xe3\x05\xaa\x8a\x66\x01\x61\x0b\xd5\x90\xf5\x6f\x0a\x90\x14\x05\xcc\xb1\x9a\x9c\x72\x77\x16\xd3\x10\x24\xb7\x4a\xcd\x0e\x75\x07\x50\xae\x90\x84\x0c\xd1\x48\x75\x12\x50\xdc\x31\x65\xff\xe8\x5b\x06\xc7\xde\xb6\xff\x7a\xb3\x6b\x34\x41\x20\x4f\x47\x75\x9c\x60\x8a\x8e\x26\x0a\x00\xd5\x3d\x23\x88\xd5\xd0\x79\xe0\x42\xe7\xf5\xbb\x54\x1e\x28\xb5\x2d\x8c\xd1\xac\x4b\xe7\x98\xee\xee\x29\x7b\x74\x81\x34\x4f\x03\x36\x9a\x57\xa4\xc8\x83\x16\x32\x54\x31\x50\xf5\x5e\x22\x0e\xb1\x21\x8e\x0b\xa8\x6e\xd9\x08\x35\xdb\xf7\xb4\x14\xa8\x48\x98\x88\x2c\xb7\x7b\xec\x1a\x28\xfb\x74\x51\x46\x8b\xed\xa9\x7d\x6a\xbb\xa6\x45\x11\xde\x46\xed\x10\x79\x74\x9c\x5d\x4d\x3e\x5d\x4c\x6f\x26\xef\x2e\xfc\x69\x73\x5d\xba\x40\x73\x16\x4c\x27\xc0\x6d\xe5\xc3\x82\x14\xc6\xb3\x52\x7f\xa4\x07\xc4\x93\x01\x03\x0a\x72\x8c\xf2\xee\xca\x7f\x00\x15\x07\x40\x7a\xd3\x08\xf4\xe2\x8a\xf2\x9c\x63\x21\x34\x46\xbd\x11\x8a\xce\x1c\x88\x00\xb0\x2a\xf0\x8f\x9d\x6d\xb7\x79\x8b\x47\x4e\xea\xb3\xaf\x8a\x87\x9d\xac\x34\x8c\x40\xe0\x40\xa7\x43\x05\xa3\x05\x86\xca\x1e\x04\x32\x8d\xa4\x28\x5c\x41\x9d\x0e\x45\xa0\xeb\x74\x48\x07\x9e\xee\x84\x11\xbc\xde\x94\x61\x37\x59\x62\x09\x5b\x08\x27\x9b\xfd\x7c\x80\x88\x90\xcd\x7a\x24\x01\xa3\x75\x69\x07\x5a\x2d\x02\xc2\x7f\x10\x6b\xf8\x93\xed\xe6\xe6\x73\xba\x5a\x9a\xd2\x84\xe6\x3d\xde\x56\x97\x2a\x38\x5d\x49\x4e\xf0\xc3\x28\xfb\xc5\x60\x78\x0d\xd8\x7f\x7f\xe5\x90\x3e\x3d\x9d\x19\xfd\x23\xcc\x18\xe2\xf6\x65\xf5\x50\xfc\xe8\xeb\x88\xbb\x54\x01\x63\xd6\xe4\x23\x4c\x19\x46\x88\xca\xef\x49\xec\x88\x4e\xb6\xd8\xcc\x1e\xc5\xd8\xcf\xbd\xd1\x50\xe3\x72\x7a\x52\x40\x43\x5b\x7a\x83\x3e\xa2\x61\xc2\x08\x51\x59\x3e\x89\x0d\xe3\x64\xf3\x4e\x10\x81\x99\xe1\xff\xc5\x94\xb0\x64\x32\xd5\x58\x56\x16\x5f\xd6\x8f\x6f\x05\xea\x50\x84\x16\xa0\x2d\xe9\xd0\xf5\x27\x88\x10\x95\x07\x94\xba\xfa\xb8\xd8\xfc\x19\x40\x3e\x97\x69\x87\x28\x2d\xc6\xba\x17\x82\x77\xbe\x41\x12\x44\x7a\xbe\x8d\xc9\x91\x7f\xa1\x64\x9b\x78\xf0\xef\x7a\xba\xac\x3b\xc5\x41\x8e\x96\x87\x20\xb9\x76\xd1\x4c\xed\xb7\x33\x54\x14\x9b\x9e\xa7\x8b\x16\x12\xd7\xab\x84\x5a\x37\x88\x33\xd1\x20\x01\x21\x42\x85\x9e\xdb\x38\xc7\x0b\xc6\xeb\x44\xbe\x04\x25\x42\x18\x81\x13\x76\xcd\x16\x7b\xbc\xde\x23\x0e\x6c\x8c\x54\x97\x15\xf9\x7d\x70\x6b\xd4\xd0\x39\x8e\xea\x85\x54\xb3\xc1\xf4\xfc\x03\x20\x2e\xc9\x02\x65\xd2\xa5\xa6\x9d\x36\x1e\xf6\x04\x1e\x75\x30\xd3\x6c\x50\xdf\x5d\x7f\xba\xb9\xbe\x52\x9d\xbb\xce\xdc\x40\xca\xae\x2c\xbb\xc7\xfc\x04\x08\xab\x8b\x61\xe6\x48\xac\x54\x73\xa4\xa8\x94\x22\xe7\x7a\xba\x23\xc7\x99\xe0\xa4\x44\x64\x6c\x5d\x32\x8a\xa9\xec\xa5\x10\xae\x89\x10\x84\x2e\xcf\xe0\x9a\xe2\x0e\xc9\x71\xef\x65\x18\x6f\x65\xbc\x6e\x2b\xce\x44\x89\x33\x5d\x4a\xe3\x49\x7d\x7a\x59\xb9\x81\xa9\x77\x89\x29\xe6\xca\xa9\x1a\x36\xb9\x06\xd8\xed\xc5\x23\x48\xac\xfe\x50\x6f\xa3\x46\x18\xa3\x7f\xac\x85\xab\xe8\x52\x59\x47\x51\x83\x7a\xb9\xd3\x2d\x0b\x88\x8c\x93\x52\xc2\x71\x2b\xf4\xb5\x59\x79\x74\x5f\xd9\xa6\x88\x35\x45\x6a\x39\xe1\x38\x93\x8c\x6f\xce\x7e\xa7\x77\xed\x06\xbd\x57\xbe\xdb\x01\x67\x0b\x78\x14\xf7\xcd\x63\x71\x02\x82\x55\x3c\x33\x19\x21\x4a\x11\xd8\x57\x84\x50\xc9\x60\xc3\x2a\xd3\x14\x80\xe9\x03\xe1\x8c\xaa\x66\x74\x2d\x7e\x9e\x86\x3f\xd2\x89\x5e\xf5\xcf\xfd\x45\xf5\x0c\x7e\xd5\x5d\xbe\x7d\xbc\x37\xa8\x62\xc6\xd4\xb7\x91\xed\x7c\x6d\x1d\x2b\xda\x6e\x15\x51\xc1\x31\xca\x37\x66\x0f\x21\xce\x00\xce\x8d\x27\x46\xa4\x29\x96\xc3\x92\x6f\x5c\xb5\xd9\x83\xe1\x9c\xca\xf5\xdf\x5f\x9b\xa9\xee\x56\x49\x25\x22\x83\xa0\x9c\x4a\x19\x1b\x83\xb8\x57\xaf\xc2\xa8\x39\x2c\x7a\xec\xf4\x77\x24\x1d\xfd\xdd\xa3\xde\x08\x50\xab\xa2\xe7\xec\x91\x16\x0c\xe5\x38\x87\x6d\xc9\x3c\xb9\x9e\x82\x90\x88\xeb\xaa\xab\xb2\x3c\x83\xcf\xf4\x2b\x29\xbb\xcd\x45\x73\x60\x25\xa6\x4d\x68\xf5\x5f\x38\xd3\xe7\xf1\xff\xcc\x58\xee\xb9\x8b\xe0\x85\x84\xc5\x6e\xca\xd4\x30\xa9\x78\x51\x22\xb9\x52\x83\x64\x7a\xfe\x61\xc8\xb6\xcc\x8b\x62\x55\x65\x6a\x0a\xbf\x17\x6d\x71\xb1\xc0\xd4\x44\xa5\xf7\x06\x6e\x8c\x4e\x83\xe1\xec\xf5\x93\x9c\xb3\x8e\xff\xa6\xfa\x7b\x7f\x6c\x06\xf5\x49\x41\xf0\xa9\xc0\xca\x8d\xe2\xaf\xcb\xe4\x39\x16\x25\xa3\x02\x9b\x59\x5a\x01\xc6\x2a\x92\x80\xe3\x1e\xbb\xcd\xb0\x39\xe0\x94\x37\x1c\xd3\x63\xb5\x8a\xfe\xed\x2b\x29\x4b\xf5\xc2\x83\x9a\x2d\x86\xdf\x2b\x5e\x22\xce\x47\x48\x0f\xb2\x87\xbc\xed\xba\x58\x3b\xec\x6e\x37\x84\x56\xc0\x05\xe1\xb8\x21\x01\xfc\xe0\xce\x7a\xb7\x10\x06\xa6\x9f\x1e\xc7\x30\x7f\x2d\x02\xc2\x1b\xe6\xa8\x59\x71\x0e\x6f\xfa\x95\xcd\x6f\xb6\x3d\x51\x1b\x89\xe4\x0a\x91\xe4\x11\x31\x90\x61\x98\xa1\x80\x72\x8d\x1a\x8e\x29\x37\x84\xdf\x75\xab\xdf\x1c\xea\x9d\x9e\xd6\x49\xd0\xad\x47\xd6\x49\x96\xe4\xcb\x07\x54\xe8\xac\x3a\x43\xdc\xd9\xee\x18\x0d\x18\xd7\x0a\x04\x0e\x0e\x0f\x23\x23\x2e\x0a\x3f\xae\xb7\x46\x81\x44\xc5\xe2\x1b\x88\xf4\x70\xbc\x8d\x33\x36\x22\x5f\xf3\xf6\xe3\xe7\x0d\xe0\xb8\xb8\x3c\xa2\x03\xd0\xe3\x02\xf4\xe3\xda\x2c\x0a\x24\x2a\x4c\x9f\xde\x66\x3e\x4e\x67\xb0\xde\x3f\x51\x74\x29\x82\x5b\x69\x39\xd2\x76\x61\x84\x60\xb8\x3e\xdd\x68\x2e\x2e\x5f\xd0\xde\x6f\xb3\x1d\xa2\xb8\xd0\xfd\x38\xcb\x45\x81\x44\x05\xf0\xd3\x0d\xe8\xe3\xf4\x87\xf1\x03\x2e\xc7\x3e\x5d\x7a\x1c\xbd\x61\x7d\xa9\x50\x7a\x12\x7e\x40\x7d\x42\x1f\xd8\x7d\xbf\x11\xd5\xdf\x6d\xa9\x1c\x56\x6b\x96\x2e\x64\x31\x49\x37\x18\xe7\x6d\x9e\xb9\x7e\x58\xa7\x73\x67\x8c\x2e\xc8\xb2\xe2\x11\xfb\xf4\x6f\x24\xfc\xbb\xfa\x16\xcd\x2b\x1d\xe4\x24\x61\x20\x98\xb5\xe5\x1b\x2c\x6d\xbe\xd9\xe4\xdd\xdd\xe5\xf5\xd5\x1f\x57\x93\x4f\xce\x44\x35\x0f\x43\x20\x46\xdf\x70\xc6\x86\xe9\x77\xe9\xed\x85\x53\xed\x85\x32\x03\x0a\x2e\x23\x99\x23\xeb\x2d\x6d\x68\x43\xca\x2d\x43\x38\xf6\x32\x94\xbd\x8b\xd6\x74\xec\x14\x04\x56\x70\x52\x75\x06\xb5\x05\x6d\x1e\xfe\x59\x31\x5d\xc6\xbb\x50\x13\xc9\xa6\x91\x0e\xa6\xd0\xc2\xb5\xed\x3d\xac\x0c\x6f\x66\x5e\xfc\x25\x6f\x3e\x0e\x67\x40\xa0\xf5\xba\x67\xc6\xdf\x7e\x7e\x9e\xa5\xd4\x59\x25\x41\x0c\x54\x42\x37\xf9\x01\x34\xd9\xc5\x71\x94\x1f\x7a\x8b\x62\xbd\x55\xae\x7a\xaa\x71\x0d\x66\xf3\xd0\x1f\x86\xee\x9e\x5d\xf7\x9d\xe5\x60\xc8\xd9\xc7\x1a\x13\x1f\x33\xf1\x92\x3d\xac\xd4\x20\x59\x18\xc6\xa3\x0c\x2b\xb1\x1e\xf6\x63\x34\x89\xc5\x08\x16\xfc\x0c\xd2\x21\x01\x20\xe4\x3c\x95\x98\x8a\x7e\x6d\xbe\x8e\x2b\xd5\x41\xad\x14\x37\x29\x1a\x29\x6e\x13\xbd\x0d\xf7\x29\xcc\x9c\x70\x05\xf9\xb8\x52\x6f\xda\x82\x8e\xdb\x61\x8f\x90\x10\x61\x55\x50\x08\x38\xef\xa7\x4b\x1f\xee\x1d\x0e\x20\x22\xae\x1d\x5e\x48\xff\x71\xe8\x11\xa3\x5b\x22\x3e\x76\x70\x07\x20\x3c\x4a\x70\x8c\xf2\x91\x4a\x44\x42\x1c\x60\x30\x35\x31\xe4\x97\x1b\x4c\x7e\x09\x23\xfb\xe1\x41\xd4\x1f\x88\x1e\x9c\xde\x07\x75\x80\x04\x80\x28\x05\x7a\x8b\xf6\xde\x9d\x2b\xad\x04\xb9\x29\xb1\x73\xdf\x3f\x0e\x33\x70\x0a\x51\x5f\x14\x1b\x3c\x84\x68\xe8\x7c\x61\x70\x73\x05\x22\xf2\xdd\x25\x61\xa3\x0c\x18\xb2\xbe\xcf\xd8\x6c\x85\x9b\x1c\x8a\x78\x9f\x79\x00\x50\xdc\xa8\x40\xa9\x17\x6a\xbb\xf8\xa2\x02\xca\xb5\xda\xe9\xf1\x64\x0b\x63\x6c\x38\xd9\xb0\xf6\xe2\xbd\xb4\xc1\x1b\x19\x4d\x4e\x45\x8e\x8b\x24\x27\x37\x89\x8b\x2f\x2a\x5e\x9c\xdc\x24\x1e\x46\x47\xce\x9b\x0e\x11\x79\x07\x54\x9f\x26\x2e\xe0\xd5\x09\x2b\xa5\xcf\x8b\x31\x18\x5e\xf3\x19\x80\x43\x1e\xe8\x0d\x41\x74\x86\xe7\xbd\xd6\xee\x10\x44\x04\xe7\x93\xbb\xa3\x95\xc9\x9e\x94\xea\xd3\xd2\xa7\x61\x37\x0a\x9f\xdc\x81\x1d\x4c\xbe\xc8\xbd\x57\xd1\x3e\x4d\x5c\xdc\x3e\xd9\xa8\x2e\xbe\xa8\xe8\x7c\xb2\x89\x3c\x8c\xfe\xd8\xbc\x7f\x21\xde\x23\x4b\x8f\xcc\xd7\x9c\x2f\x15\x98\x4f\x81\xb7\x2a\xff\x8e\x55\x45\xae\xa7\xfc\x05\xa1\x39\x1c\xad\x11\xa1\x47\xb0\xc6\x72\xc5\x74\x82\x65\x07\xca\xa1\x5f\x0a\x42\x9c\xfd\xf6\x67\x39\xbd\x90\xb3\x72\x93\x6e\xac\x28\x2c\xab\x5a\xbf\xec\x86\x7f\x76\xee\xcf\xd7\xee\x1e\xe3\xfb\xcb\x66\x50\xc9\x43\x20\xc7\x94\x46\x0f\xad\x27\x72\xb2\x5a\x85\x5a\x5f\x44\x54\x65\xc9\x78\x67\x48\xf2\x8a\x4a\xb2\x76\xc5\xff\xd2\x30\xdc\xde\x70\x7d\x66\x5e\xd3\xeb\xbb\xd1\x10\x9c\x7d\x25\x65\x9b\x55\x0e\x1c\xff\x59\x11\x8e\x45\x9d\x3c\xad\x53\xbf\x74\xce\xaf\xe1\xb9\x57\x7d\x18\x7f\x29\x0b\x92\x11\xe9\xfc\x26\xd3\x0b\x09\xb3\xbe\xd8\xdf\xd1\x03\x6a\xc7\x79\x0d\x08\xa7\xa7\x6b\x3d\x15\xb0\x06\xd9\xdc\x64\x57\x15\xc5\xe6\xb4\xff\x61\x07\x7d\x7c\xb7\xc2\xa0\xe9\xb3\x02\x09\xd7\x64\x77\x78\x39\x8e\xe3\x20\x8c\x24\x98\x53\x1d\x40\xa2\x49\x50\x25\x6b\xb4\xc4\x50\x22\xb9\x02\x46\xeb\x1f\x57\xd5\xdc\x79\x44\x94\x04\x12\xa5\x48\x7b\xad\xb0\x9a\x56\xf7\xbc\xe6\x48\x45\x02\x20\x51\x8a\xec\x1c\x79\x80\xc0\x7f\x56\x98\x66\xb8\x3b\xdf\xb7\x2e\x61\xa4\x5e\x69\x98\x76\x35\x57\x18\x66\x1f\x2e\xaf\xce\x67\x4d\x53\xf7\x87\x25\x1c\xe3\x2f\x68\x5d\x16\xf8\x2d\x88\x47\xb2\x90\x6f\xeb\x6b\x6e\x4e\x80\xb2\x1c\xff\x4b\x34\xff\x7f\xed\x52\xf9\x60\xf8\x4e\xf5\xbb\xfd\xb4\x06\xc7\x54\xf2\x0d\x94\x8c\x50\x09\xc7\x8b\x8a\x9a\x5f\x19\xdf\xeb\xe3\xf5\x82\xa6\x21\x1e\x57\x98\x02\x32\xdf\x65\x9b\x17\xd8\xf7\x46\x2f\x26\xd2\xe3\xa0\x1e\xe6\x0c\x78\x18\x96\xd3\xf6\xaa\x09\x59\x25\xdb\xbb\x2c\x09\x85\x35\x29\x0a\x22\x70\xc6\x68\x2e\xea\xba\xb0\xc7\x15\xc9\x56\x5d\x63\x11\x01\x12\xf3\x35\xa1\xaa\xdb\x7a\xec\x7c\x10\x78\xa7\xf2\x6b\xf4\x85\xac\xab\x35\xac\xf1\x9a\xf1\x4d\x57\xc8\xa7\x9f\xb5\xef\xb5\x85\xf4\xe8\x98\x82\x12\x54\xa5\x60\x4b\x10\xe4\x2b\x1e\xab\x4c\x1c\x8e\xbd\xc4\xa7\x60\xfa\xd3\x68\xfe\xa9\x68\x97\xca\x71\xc6\xc7\x1e\x41\x5f\x79\xaa\x04\x3e\x98\xd2\x09\x73\xdf\x2a\x90\x05\x20\x68\x51\x3a\xcf\x8f\x2b\x5a\x60\x21\xb6\x77\x6b\xa1\xe6\x5a\x12\xd7\x88\x3c\xb8\x18\xeb\xcb\x44\x5c\x2e\xdb\xfa\xed\x87\xba\xad\xd6\x05\x68\x55\xd0\x7f\xb7\xec\x1e\xd4\xc8\xbb\x6a\x7d\x78\x81\x84\x8e\x7a\x94\xc6\xe6\x73\xec\x90\x47\x04\x61\x4d\x13\xc7\x05\x62\x1b\x5a\xcf\x8e\x32\x88\xb8\x47\x36\xa8\x70\x7b\x60\xb1\xf6\xc0\xed\x6b\xad\xea\x4b\x6e\x61\x53\x44\x78\x63\x48\x06\x28\x10\x47\xaa\x89\xe2\x37\xa3\xed\x44\x91\x31\x9e\x6b\x25\x8f\xea\x98\xd6\x80\x77\x8f\x07\x74\x2e\xf1\xfd\x89\x4b\xb1\xbb\x83\x70\xc7\x9d\x1b\x5a\x00\x49\x85\xae\xd6\xca\xe7\xe7\xd7\xce\xa8\xca\x41\x45\x44\xc5\xc3\x6a\x69\xb1\xa1\x47\x27\x9b\x3b\xfd\x57\x75\x38\xb6\x14\xb5\x5f\x1e\xd5\x55\xdc\x3c\x71\x3d\x47\xf3\xea\xef\xfd\x6d\x55\x1d\xd1\x6f\x62\xe1\xe2\x94\x6b\x16\xbd\xed\xd5\x7a\x07\x54\x75\x18\xb8\x3d\x6f\x88\x15\x85\x4e\xec\x22\xb4\x62\x95\x28\xcc\x17\x14\x95\xd3\xb2\xc6\x42\x6c\xaf\x40\xaf\xeb\x17\xd5\x3a\x54\x51\xba\xdd\x72\xb9\x26\xe2\xf1\xb8\x56\x75\x6f\x14\x6c\xd0\x61\xdd\xa5\xb2\x1f\xac\x53\xe5\xb1\xbe\x93\xbc\x38\xcd\xf4\x85\x63\x5f\x88\x33\x5d\xc9\x4e\xeb\xd4\x50\x17\x50\xf5\x1b\x44\xf5\x2e\xe7\xb8\xf3\xf3\x58\xc5\xfc\x4e\x27\x2d\xe1\x5b\xe8\xcf\x15\xaa\xe1\xdd\xb3\x4f\x0c\xe7\x18\xb7\xab\x5d\x6b\x0e\xe9\x7a\xb9\x40\x07\xba\x5f\x7b\x70\x07\x70\xc1\x7c\x98\x8e\x83\x30\xf3\xea\x6a\x5b\xda\x6d\xf6\x1c\x9b\x7a\x78\xdf\xf1\x58\x98\xd3\x2a\xb2\x7e\x81\xae\xba\xa6\x18\x8f\xac\xb1\x90\x68\x5d\x0a\xc0\x88\x17\x04\xab\xcd\x09\xa2\x30\xfb\x7c\x73\x77\x3d\xfb\x11\xd6\x18\x89\x8a\x9b\x2b\x01\x7a\xdb\x3e\x41\x68\x86\xe1\x6e\x75\x02\x7f\xf9\xeb\x09\xfc\x1d\x51\xf8\xeb\x7f\xff\xd7\x5f\x1c\x6a\x7f\x2b\xe9\x43\x5f\xbd\x40\xb2\x15\x3d\xbd\xbc\x7a\x77\xf1\x2d\xdf\xfc\x10\xc2\x23\xbc\xfd\xb6\xa7\xc4\x7b\xfc\x3b\x2c\x76\x21\x92\x95\x50\xd6\xd3\x98\x89\x05\xcc\xa6\x17\xef\xae\xaf\xce\xa7\x33\xa8\xb5\x76\x09\x8b\x61\x75\x08\x45\x5c\xb6\xac\xfd\xc9\x53\xec\x83\x00\x5a\xba\xee\x72\x18\x82\x34\x44\xa5\x4f\x97\x57\x9f\xef\x2e\xa6\x33\x58\x13\x5a\x49\x3c\x42\x25\x2b\xd2\x10\x95\xde\x5f\x7f\xbe\x9d\xce\x60\xc5\x2a\x3e\x42\x9d\x3d\x94\x21\xaa\x9c\x4f\x7e\x9b\xce\x20\x47\x9b\x11\x8a\xec\x60\x04\x32\xed\x9b\x9d\x81\xce\xb9\x3e\xea\x7f\xe1\xfc\xcd\xf6\x0b\xe7\xc1\x6c\xfa\x58\x1c\x67\x1a\xf5\xfe\x57\x75\x4d\xb5\x68\x4a\x3e\x7b\x3c\x86\x3b\xa1\xdd\xf6\xdd\xf7\x54\x3d\x12\x40\xec\xcd\xd3\x9e\xf9\xe8\xc8\xac\xce\x67\xca\x6a\x38\xdc\x9c\x9c\xe4\xce\x12\x87\x48\xee\x18\xd1\x44\xa4\x8a\xeb\x71\xa4\x8a\x80\xed\x33\x22\x80\xe9\x75\x1c\x15\x03\x24\xbb\x80\x7c\x0a\xd5\xb3\xfc\xe5\x79\xc2\x5b\x3b\x78\xec\x62\xda\x6f\x62\xb5\xed\xd2\x7c\xb7\xd8\xfc\x10\xd3\xb6\x49\x18\x31\x6a\xc4\xbc\xab\x87\xc3\x21\x42\x5f\x4f\x16\x8f\xef\x20\xf7\x80\x9f\xb4\xa5\x55\x9d\xfe\x9d\x60\xc6\x14\x0c\x87\x1a\x0d\x73\xfc\x6b\xba\x39\x52\x45\x80\x6a\x93\x4d\xc1\x50\x1e\x35\x50\xd2\x81\x5c\x03\xa5\xa1\xe8\x04\xd1\x88\x39\x94\xd4\xfb\x83\x7e\x1d\x93\x67\xf0\x24\xe2\x44\x15\x5a\x1d\x8b\xd7\x67\x6a\x66\x6d\x5e\x2e\xba\xde\xca\xc2\x18\x2b\x50\x4f\xe5\x88\x2f\xc5\xf3\xf3\x60\xd9\x1e\x0c\xc7\x36\x14\x65\xee\xcc\x81\xe6\xa9\x3d\xb6\x27\xa1\xc0\xc8\x99\xd2\xd9\x3e\xb6\x32\x53\x06\x6b\xa6\x3f\x8f\x8b\x5c\xfb\xb2\x1e\x89\x3d\xf2\xcc\x76\xd6\xc4\xd0\xb0\xf1\x30\xb8\x5c\x2c\xf7\x85\xda\xbe\x7b\xb4\x5b\xef\xc1\x19\xd1\xd9\x12\x38\x52\xe3\xf4\xf1\xb0\x33\x29\xce\x3c\x75\x2e\x41\x4d\x09\x5c\xe3\x3b\x95\x9c\x3d\x90\x1c\xe7\x3b\xbe\x95\x67\x41\x8a\x45\x70\x7a\x3f\xed\xd7\x7a\xea\x14\x99\x78\x97\x27\xc4\xe9\x7a\xeb\x96\x6f\x57\x67\xcf\xdc\xe1\xe1\xf1\x0e\xdb\x05\xc1\x85\xce\x7d\x92\xb8\x6b\x0e\x97\xa4\x20\xdb\x80\xc0\xf3\x09\xe4\x44\x94\x05\xda\x98\x1b\x86\x14\xb0\xae\x90\xc0\xc5\xf0\xa8\x74\x08\x33\x39\x5f\xf4\x30\x4a\xa6\x20\x06\x55\xdc\xfb\xb8\xf9\x78\x1d\x93\x21\x83\x4a\xf6\x2f\x5f\x1e\xaf\x61\x1a\x5e\x50\xbd\xbd\x7b\x25\xc6\x6b\x98\x0c\xe9\x9b\x06\x4a\x24\x57\x27\x80\x68\xf3\x25\xa6\xb9\x29\xc5\x46\x34\xdd\xbd\x1b\x0e\x18\x52\xb0\x41\x68\x20\x53\xf5\x09\xf0\x5b\xc4\xdb\x53\x17\x6b\x27\x62\xfb\x1e\xc1\x59\x3a\x19\x66\xb8\x2e\x2e\x3b\xc4\x72\xc7\x15\xea\x4c\x6e\x2e\xd3\xab\x74\xfa\x4c\x51\x25\x3a\xaa\xa9\x9e\x9e\xce\xcc\xf5\x8a\x60\xbe\xa7\x39\x7f\x7e\x6e\x23\x2f\xfd\x74\x70\xd5\xdd\xab\xa2\xbe\x8c\x31\xba\x9a\x67\x9c\x8c\xb8\x4a\xab\x92\x1c\xa2\xcc\x2a\x80\x12\xae\x23\x9b\xdc\x5c\xc6\x14\x91\x29\xb2\xd8\x82\x26\x37\xa4\x83\x38\xae\xec\x28\xad\x83\x59\x99\xa2\x0a\x8e\x5e\xb2\x83\x1d\x46\x86\xf3\xd4\xdb\x6d\xf9\xe6\x69\x44\x3d\x4d\x9a\x9d\xf7\x39\xbc\x75\x2a\xd4\xa3\x63\x9f\xe6\x3f\xb1\x4e\xa5\x6e\xb6\x39\x12\x38\xb2\xc1\x7d\x9c\x51\x22\xb7\xbd\x44\x9f\xe3\x0d\x93\x1e\x00\x49\x54\xa4\xd3\x5d\x0f\xa1\x52\x00\xce\x9d\x7d\x35\xb9\xb9\xf4\xa6\x5d\xe9\xe7\xf1\x29\x50\xaa\x89\x8a\x98\x72\x8e\x58\x6e\x67\xf5\x84\x79\xdd\xbd\x45\xb7\x71\x42\xce\xa0\xbe\x93\x7b\x7b\x4b\xe3\xdb\xc6\x4a\xc2\xeb\x50\x8c\x04\xb6\x2a\x5c\x5f\xc8\x34\xeb\x54\x25\xea\xdc\x64\x05\x4a\x04\x64\xfa\x56\x69\x87\x4e\x71\xbc\x8e\xe3\x51\x05\xf3\x80\x61\x36\xb9\xb9\xfc\xe3\x66\x72\xf7\x5e\xa7\x89\xbb\x67\x0f\x0f\x83\xdd\x8b\xbc\xb9\x34\xa4\xbf\x5e\xdc\xfe\xec\xba\x9a\x6a\x87\xc8\x09\xb4\x3d\x5a\x37\x1f\x5c\x9c\xc1\x71\xf3\xd1\xd7\xf6\xfb\x8b\xae\x1c\xd5\x78\x7e\x6f\xa0\x60\xf6\xf3\x64\x7a\x51\xbf\xb7\x64\x9d\xcc\xe7\xc6\x1c\x26\xd0\x68\x8c\x14\x08\x1d\xa4\x61\x79\xa7\x0f\x35\x08\x83\x93\x42\x4d\xe4\x02\xaa\x78\x61\xe6\x09\x56\x62\xae\x99\xcd\x7f\xcd\x24\xee\x9f\x74\x22\x78\x7d\x9b\x0c\x35\x15\x99\x9d\x42\x2f\x4c\x0b\xdd\x98\xa9\xa2\x6b\x7b\x5f\xbb\xab\x68\xe5\xc1\x1a\x6d\x00\x15\x82\x25\xc4\x53\x5e\x4e\xac\x73\x52\x6a\xee\x2b\xaf\xa3\x38\x4b\x42\x4d\x4a\xc0\xd1\x1b\x67\x48\x26\xc8\x16\xe5\x09\x8b\x47\x64\xae\x7f\x8b\xb9\xc2\x3b\x92\xd9\x9e\x60\xd5\xbb\x76\xa4\xcb\x39\xe2\xf6\x92\x08\x18\xc7\x69\x42\x73\xa5\x60\x9d\x2f\xa7\x3f\xb5\x89\xcc\xc4\xdd\x7e\xe7\xc3\x7d\x84\x10\xc7\xed\xb1\x83\xb2\xdf\x01\xec\x90\x00\x63\x55\x66\xda\xe5\xeb\xdc\x31\x76\x5c\x7f\x25\x45\x0f\x85\x1b\x1d\x3a\x20\x74\xc1\x4e\x74\x4f\x37\xdf\x47\xd9\x8a\xc4\x45\x2e\x5c\xd3\xeb\x01\x05\x84\x5f\xa0\xc1\x32\xc3\xc1\xa4\x06\x98\x7c\x72\x28\x9a\x0e\x53\x20\xb1\x82\xe3\x37\x51\xfa\x26\xe1\x39\x57\xa7\x7e\x51\xa9\xde\x64\x34\x2e\x56\xdf\xdd\xf2\xac\x50\xf1\x18\x29\x6a\xa8\x9f\x75\xd8\x47\xcd\xc8\x25\x31\x7d\x26\x4d\x0b\x3b\x84\x6f\x4e\xd7\xd4\x19\xa2\xe6\x6c\xab\xfb\x59\x1d\x60\xd4\xf9\x25\xf1\x48\x66\x7b\x1f\xa9\x6b\x29\x51\x7f\x98\xa8\x2e\xd6\x3c\xd9\x9d\xf7\x4d\x2b\x27\x44\xce\x5c\x53\xc5\xb7\x91\x1d\x7f\x22\xf8\x3d\x6d\xf1\x1d\x15\xb2\x17\xe3\xeb\xd4\x14\xa8\x04\xee\x5f\xd6\xde\x5c\x2e\x67\x2a\xee\x5c\x6f\x13\xcb\x6d\x1f\x0b\xbe\xe4\xd2\x89\x27\x4b\xf4\x57\xcc\x5d\x55\xba\xfa\x91\x73\xf8\x5f\x21\x67\x39\x78\xfb\xd8\xee\x30\xdc\x7e\x74\x79\x03\xb7\x1f\xed\x2c\x3f\x37\x4d\xe5\x60\xdc\x3e\xb7\xe7\x4c\xbb\x39\x6f\x9c\x4c\x75\xa8\xdd\x24\xc1\x76\x32\x5f\xd5\x2e\x04\xa3\x6c\xe5\x8b\x7c\xc4\xb0\x7e\xcf\x33\xbb\x5b\xfc\x67\x85\x75\x25\x53\x86\x4b\x7d\x2d\xeb\xbc\x92\x8a\x29\xc3\x66\x11\x35\x77\x24\xeb\x0f\x68\xe1\x1c\x36\xd8\x99\x6c\x30\x04\xc9\xbb\xc1\x08\x5e\x4b\x63\x24\x98\x1c\x6d\x23\x7b\x55\x3b\x4a\x1b\xac\xef\xb0\x20\x62\xa5\xf4\x38\xf8\x7d\x38\xc3\x05\x47\x95\x8d\x23\x78\xc4\xf3\x7a\x92\x39\x01\x04\x1c\x3d\xc2\xfb\xbb\xbb\x9b\xde\xcf\x8c\x1b\x52\x21\x11\xcd\x11\x6f\x22\xfb\xa6\xa4\xf7\xdf\x20\x79\x85\xe1\xa7\x1e\x87\x82\xf9\xc9\x0e\x46\x19\xfc\x1b\x16\xa8\x10\x8a\x67\x07\x31\xb2\x2e\xfd\x3f\x4c\xe9\xa8\x65\x6b\x77\x5e\x3d\x3d\x55\xf8\x8b\x02\x2d\x9b\x8f\xa7\x11\x5a\x56\xfa\x8e\x70\x51\xd7\xb8\xe9\xeb\xbd\x4f\xe0\x48\xa9\xaa\xfe\xe5\xe8\x51\xfd\xa3\xd5\x38\x3a\x69\xbe\xb6\x11\xbb\x4c\xbd\xa0\x02\xca\x00\x3f\xfc\xef\x0f\xff\x17\x00\x00\xff\xff\x19\x9a\x50\xcb\xbb\xac\x00\x00")
+
+func wski18nResourcesEn_usAllJsonBytes() ([]byte, error) {
+ return bindataRead(
+ _wski18nResourcesEn_usAllJson,
+ "wski18n/resources/en_US.all.json",
+ )
+}
+
+func wski18nResourcesEn_usAllJson() (*asset, error) {
+ bytes, err := wski18nResourcesEn_usAllJsonBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "wski18n/resources/en_US.all.json", size: 44219, mode: os.FileMode(420), modTime: time.Unix(1491513831, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _wski18nResourcesEs_esAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00")
+
+func wski18nResourcesEs_esAllJsonBytes() ([]byte, error) {
+ return bindataRead(
+ _wski18nResourcesEs_esAllJson,
+ "wski18n/resources/es_ES.all.json",
+ )
+}
+
+func wski18nResourcesEs_esAllJson() (*asset, error) {
+ bytes, err := wski18nResourcesEs_esAllJsonBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "wski18n/resources/es_ES.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1491513831, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _wski18nResourcesFr_frAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8a\xe6\x52\x50\xa8\xe6\x52\x50\x50\x50\x50\xca\x4c\x51\xb2\x52\x50\x4a\xaa\x2c\x48\x2c\x2e\x56\x48\x4e\x2d\x2a\xc9\x4c\xcb\x4c\x4e\x2c\x49\x55\x48\xce\x48\x4d\xce\xce\xcc\x4b\x57\xd2\x81\x28\x2c\x29\x4a\xcc\x2b\xce\x49\x2c\xc9\xcc\xcf\x03\xe9\x08\xce\xcf\x4d\x55\x40\x12\x53\xc8\xcc\x53\x70\x2b\x4a\xcd\x4b\xce\x50\xe2\x52\x50\xa8\xe5\x8a\xe5\x02\x04\x00\x00\xff\xff\x45\xa4\xe9\x62\x65\x00\x00\x00")
+
+func wski18nResourcesFr_frAllJsonBytes() ([]byte, error) {
+ return bindataRead(
+ _wski18nResourcesFr_frAllJson,
+ "wski18n/resources/fr_FR.all.json",
+ )
+}
+
+func wski18nResourcesFr_frAllJson() (*asset, error) {
+ bytes, err := wski18nResourcesFr_frAllJsonBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "wski18n/resources/fr_FR.all.json", size: 101, mode: os.FileMode(420), modTime: time.Unix(1491513831, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _wski18nResourcesIt_itAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00")
+
+func wski18nResourcesIt_itAllJsonBytes() ([]byte, error) {
+ return bindataRead(
+ _wski18nResourcesIt_itAllJson,
+ "wski18n/resources/it_IT.all.json",
+ )
+}
+
+func wski18nResourcesIt_itAllJson() (*asset, error) {
+ bytes, err := wski18nResourcesIt_itAllJsonBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "wski18n/resources/it_IT.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1491513831, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _wski18nResourcesJa_jaAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00")
+
+func wski18nResourcesJa_jaAllJsonBytes() ([]byte, error) {
+ return bindataRead(
+ _wski18nResourcesJa_jaAllJson,
+ "wski18n/resources/ja_JA.all.json",
+ )
+}
+
+func wski18nResourcesJa_jaAllJson() (*asset, error) {
+ bytes, err := wski18nResourcesJa_jaAllJsonBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "wski18n/resources/ja_JA.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1491513831, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _wski18nResourcesKo_krAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00")
+
+func wski18nResourcesKo_krAllJsonBytes() ([]byte, error) {
+ return bindataRead(
+ _wski18nResourcesKo_krAllJson,
+ "wski18n/resources/ko_KR.all.json",
+ )
+}
+
+func wski18nResourcesKo_krAllJson() (*asset, error) {
+ bytes, err := wski18nResourcesKo_krAllJsonBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "wski18n/resources/ko_KR.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1491513831, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _wski18nResourcesPt_brAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00")
+
+func wski18nResourcesPt_brAllJsonBytes() ([]byte, error) {
+ return bindataRead(
+ _wski18nResourcesPt_brAllJson,
+ "wski18n/resources/pt_BR.all.json",
+ )
+}
+
+func wski18nResourcesPt_brAllJson() (*asset, error) {
+ bytes, err := wski18nResourcesPt_brAllJsonBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "wski18n/resources/pt_BR.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1491513831, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _wski18nResourcesZh_hansAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00")
+
+func wski18nResourcesZh_hansAllJsonBytes() ([]byte, error) {
+ return bindataRead(
+ _wski18nResourcesZh_hansAllJson,
+ "wski18n/resources/zh_Hans.all.json",
+ )
+}
+
+func wski18nResourcesZh_hansAllJson() (*asset, error) {
+ bytes, err := wski18nResourcesZh_hansAllJsonBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "wski18n/resources/zh_Hans.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1491513831, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _wski18nResourcesZh_hantAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00")
+
+func wski18nResourcesZh_hantAllJsonBytes() ([]byte, error) {
+ return bindataRead(
+ _wski18nResourcesZh_hantAllJson,
+ "wski18n/resources/zh_Hant.all.json",
+ )
+}
+
+func wski18nResourcesZh_hantAllJson() (*asset, error) {
+ bytes, err := wski18nResourcesZh_hantAllJsonBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "wski18n/resources/zh_Hant.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1491513831, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+// Asset loads and returns the asset for the given name.
+// It returns an error if the asset could not be found or
+// could not be loaded.
+func Asset(name string) ([]byte, error) {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ if f, ok := _bindata[cannonicalName]; ok {
+ a, err := f()
+ if err != nil {
+ return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
+ }
+ return a.bytes, nil
+ }
+ return nil, fmt.Errorf("Asset %s not found", name)
+}
+
+// MustAsset is like Asset but panics when Asset would return an error.
+// It simplifies safe initialization of global variables.
+func MustAsset(name string) []byte {
+ a, err := Asset(name)
+ if err != nil {
+ panic("asset: Asset(" + name + "): " + err.Error())
+ }
+
+ return a
+}
+
+// AssetInfo loads and returns the asset info for the given name.
+// It returns an error if the asset could not be found or
+// could not be loaded.
+func AssetInfo(name string) (os.FileInfo, error) {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ if f, ok := _bindata[cannonicalName]; ok {
+ a, err := f()
+ if err != nil {
+ return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
+ }
+ return a.info, nil
+ }
+ return nil, fmt.Errorf("AssetInfo %s not found", name)
+}
+
+// AssetNames returns the names of the assets.
+func AssetNames() []string {
+ names := make([]string, 0, len(_bindata))
+ for name := range _bindata {
+ names = append(names, name)
+ }
+ return names
+}
+
+// _bindata is a table, holding each asset generator, mapped to its name.
+var _bindata = map[string]func() (*asset, error){
+ "wski18n/resources/.DS_Store": wski18nResourcesDs_store,
+ "wski18n/resources/de_DE.all.json": wski18nResourcesDe_deAllJson,
+ "wski18n/resources/en_US.all.json": wski18nResourcesEn_usAllJson,
+ "wski18n/resources/es_ES.all.json": wski18nResourcesEs_esAllJson,
+ "wski18n/resources/fr_FR.all.json": wski18nResourcesFr_frAllJson,
+ "wski18n/resources/it_IT.all.json": wski18nResourcesIt_itAllJson,
+ "wski18n/resources/ja_JA.all.json": wski18nResourcesJa_jaAllJson,
+ "wski18n/resources/ko_KR.all.json": wski18nResourcesKo_krAllJson,
+ "wski18n/resources/pt_BR.all.json": wski18nResourcesPt_brAllJson,
+ "wski18n/resources/zh_Hans.all.json": wski18nResourcesZh_hansAllJson,
+ "wski18n/resources/zh_Hant.all.json": wski18nResourcesZh_hantAllJson,
+}
+
+// AssetDir returns the file names below a certain
+// directory embedded in the file by go-bindata.
+// For example if you run go-bindata on data/... and data contains the
+// following hierarchy:
+// data/
+// foo.txt
+// img/
+// a.png
+// b.png
+// then AssetDir("data") would return []string{"foo.txt", "img"}
+// AssetDir("data/img") would return []string{"a.png", "b.png"}
+// AssetDir("foo.txt") and AssetDir("notexist") would return an error
+// AssetDir("") will return []string{"data"}.
+func AssetDir(name string) ([]string, error) {
+ node := _bintree
+ if len(name) != 0 {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ pathList := strings.Split(cannonicalName, "/")
+ for _, p := range pathList {
+ node = node.Children[p]
+ if node == nil {
+ return nil, fmt.Errorf("Asset %s not found", name)
+ }
+ }
+ }
+ if node.Func != nil {
+ return nil, fmt.Errorf("Asset %s not found", name)
+ }
+ rv := make([]string, 0, len(node.Children))
+ for childName := range node.Children {
+ rv = append(rv, childName)
+ }
+ return rv, nil
+}
+
+type bintree struct {
+ Func func() (*asset, error)
+ Children map[string]*bintree
+}
+var _bintree = &bintree{nil, map[string]*bintree{
+ "wski18n": &bintree{nil, map[string]*bintree{
+ "resources": &bintree{nil, map[string]*bintree{
+ ".DS_Store": &bintree{wski18nResourcesDs_store, map[string]*bintree{}},
+ "de_DE.all.json": &bintree{wski18nResourcesDe_deAllJson, map[string]*bintree{}},
+ "en_US.all.json": &bintree{wski18nResourcesEn_usAllJson, map[string]*bintree{}},
+ "es_ES.all.json": &bintree{wski18nResourcesEs_esAllJson, map[string]*bintree{}},
+ "fr_FR.all.json": &bintree{wski18nResourcesFr_frAllJson, map[string]*bintree{}},
+ "it_IT.all.json": &bintree{wski18nResourcesIt_itAllJson, map[string]*bintree{}},
+ "ja_JA.all.json": &bintree{wski18nResourcesJa_jaAllJson, map[string]*bintree{}},
+ "ko_KR.all.json": &bintree{wski18nResourcesKo_krAllJson, map[string]*bintree{}},
+ "pt_BR.all.json": &bintree{wski18nResourcesPt_brAllJson, map[string]*bintree{}},
+ "zh_Hans.all.json": &bintree{wski18nResourcesZh_hansAllJson, map[string]*bintree{}},
+ "zh_Hant.all.json": &bintree{wski18nResourcesZh_hantAllJson, map[string]*bintree{}},
+ }},
+ }},
+}}
+
+// RestoreAsset restores an asset under the given directory
+func RestoreAsset(dir, name string) error {
+ data, err := Asset(name)
+ if err != nil {
+ return err
+ }
+ info, err := AssetInfo(name)
+ if err != nil {
+ return err
+ }
+ err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
+ if err != nil {
+ return err
+ }
+ err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
+ if err != nil {
+ return err
+ }
+ err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// RestoreAssets restores an asset under the given directory recursively
+func RestoreAssets(dir, name string) error {
+ children, err := AssetDir(name)
+ // File
+ if err != nil {
+ return RestoreAsset(dir, name)
+ }
+ // Dir
+ for _, child := range children {
+ err = RestoreAssets(dir, filepath.Join(name, child))
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func _filePath(dir, name string) string {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
+}
+
diff --git a/wski18n/resources/de_DE.all.json b/wski18n/resources/de_DE.all.json
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/wski18n/resources/de_DE.all.json
diff --git a/wski18n/resources/en_US.all.json b/wski18n/resources/en_US.all.json
new file mode 100644
index 0000000..2a0deb4
--- /dev/null
+++ b/wski18n/resources/en_US.all.json
@@ -0,0 +1,1446 @@
+[
+ {
+ "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 to {{.auth}}\n",
+ "translation": "{{.ok}} whisk auth set to {{.auth}}\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",
+ "translation": "{{.ok}} whisk auth unset"
+ },
+ {
+ "id": "{{.ok}} whisk namespace unset",
+ "translation": "{{.ok}} whisk namespace unset"
+ },
+ {
+ "id": "{{.ok}} whisk API host unset",
+ "translation": "{{.ok}} whisk API host unset"
+ },
+ {
+ "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 does not exist",
+ "translation": "API action 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"
+ }
+]
diff --git a/wski18n/resources/es_ES.all.json b/wski18n/resources/es_ES.all.json
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/wski18n/resources/es_ES.all.json
diff --git a/wski18n/resources/fr_FR.all.json b/wski18n/resources/fr_FR.all.json
new file mode 100644
index 0000000..55e81ef
--- /dev/null
+++ b/wski18n/resources/fr_FR.all.json
@@ -0,0 +1,6 @@
+[
+ {
+ "id": "bypass certificate checking",
+ "translation": "Some translation in French"
+ }
+]
diff --git a/wski18n/resources/it_IT.all.json b/wski18n/resources/it_IT.all.json
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/wski18n/resources/it_IT.all.json
diff --git a/wski18n/resources/ja_JA.all.json b/wski18n/resources/ja_JA.all.json
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/wski18n/resources/ja_JA.all.json
diff --git a/wski18n/resources/ko_KR.all.json b/wski18n/resources/ko_KR.all.json
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/wski18n/resources/ko_KR.all.json
diff --git a/wski18n/resources/pt_BR.all.json b/wski18n/resources/pt_BR.all.json
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/wski18n/resources/pt_BR.all.json
diff --git a/wski18n/resources/zh_Hans.all.json b/wski18n/resources/zh_Hans.all.json
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/wski18n/resources/zh_Hans.all.json
diff --git a/wski18n/resources/zh_Hant.all.json b/wski18n/resources/zh_Hant.all.json
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/wski18n/resources/zh_Hant.all.json