├── .asf.yaml
├── .gitignore
├── .scalafmt.conf
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.txt
├── NOTICE.txt
├── README.md
├── build.gradle
├── commands
├── action.go
├── action_test.go
├── activation.go
├── api.go
├── commands.go
├── commands_suite_test.go
├── flags.go
├── messages.go
├── namespace.go
├── package.go
├── project.go
├── property.go
├── qualified_name.go
├── rule.go
├── sdk.go
├── shared.go
├── trigger.go
├── trigger_test.go
├── util.go
├── util_test.go
└── wsk.go
├── go.mod
├── go.sum
├── gradle.properties
├── gradle
├── docker.gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── main.go
├── settings.gradle
├── tests
├── build.gradle
└── src
│ ├── dat
│ ├── empty.js
│ ├── hello.js
│ ├── invalidInput1.json
│ ├── invalidInput2.json
│ ├── invalidInput3.json
│ ├── invalidInput4.json
│ └── malformed.js
│ ├── integration
│ ├── command_test.go
│ ├── common
│ │ ├── utils.go
│ │ └── wsk.go
│ ├── dummy.go
│ └── integration_test.go
│ └── test
│ ├── resources
│ └── application.conf
│ └── scala
│ ├── apigw
│ └── healthtests
│ │ └── ApiGwCliEndToEndTests.scala
│ ├── org
│ └── apache
│ │ └── openwhisk
│ │ └── core
│ │ ├── apigw
│ │ └── actions
│ │ │ └── test
│ │ │ └── ApiGwCliRoutemgmtActionTests.scala
│ │ └── cli
│ │ └── test
│ │ ├── ApiGwCliBasicTests.scala
│ │ ├── ApiGwCliTests.scala
│ │ ├── WskApiGwTests.scala
│ │ ├── WskCliActionSequenceTests.scala
│ │ ├── WskCliBasicUsageTests.scala
│ │ ├── WskCliEntitlementTests.scala
│ │ ├── WskCliWebActionsTests.scala
│ │ └── WskConfigTests.scala
│ └── system
│ └── basic
│ ├── HttpProxy.scala
│ ├── WskCliActionTests.scala
│ ├── WskCliActivationTests.scala
│ ├── WskCliBasicTests.scala
│ ├── WskCliConsoleTests.scala
│ ├── WskCliPackageTests.scala
│ ├── WskCliRuleTests.scala
│ ├── WskCliSequenceTests.scala
│ └── WskSdkTests.scala
├── tools
├── git
│ └── pre-commit-gofmt.sh
└── travis
│ └── test_openwhisk.sh
└── wski18n
├── detection.go
├── i18n.go
└── resources
├── de_DE.all.json
├── en_US.all.json
├── es_ES.all.json
├── fr_FR.all.json
├── it_IT.all.json
├── ja_JA.all.json
├── ko_KR.all.json
├── pt_BR.all.json
├── zh_Hans.all.json
└── zh_Hant.all.json
/.asf.yaml:
--------------------------------------------------------------------------------
1 | #
2 | # Licensed to the Apache Software Foundation (ASF) under one or more
3 | # contributor license agreements. See the NOTICE file distributed with
4 | # this work for additional information regarding copyright ownership.
5 | # The ASF licenses this file to You under the Apache License, Version 2.0
6 | # (the "License"); you may not use this file except in compliance with
7 | # the License. You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 |
18 | github:
19 | description: "Apache OpenWhisk Command Line Interface (CLI)"
20 | homepage: https://openwhisk.apache.org/
21 | labels:
22 | - apache
23 | - deployment
24 | - faas
25 | - functions
26 | - functions-as-a-service
27 | - openwhisk
28 | - serverless
29 | - serverless-functions
30 | - tooling
31 | protected_branches:
32 | master:
33 | required_status_checks:
34 | strict: false
35 | required_pull_request_reviews:
36 | required_approving_review_count: 1
37 | required_signatures: false
38 | enabled_merge_buttons:
39 | merge: false
40 | squash: true
41 | rebase: true
42 | features:
43 | issues: true
44 |
45 | notifications:
46 | commits: commits@openwhisk.apache.org
47 | issues_status: issues@openwhisk.apache.org
48 | issues_comment: issues@openwhisk.apache.org
49 | pullrequests_status: issues@openwhisk.apache.org
50 | pullrequests_comment: issues@openwhisk.apache.org
51 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Built or generated files
3 | openwhisk-cli
4 | wsk
5 | wsk.exe
6 | scripts
7 | Godeps/
8 | *~
9 |
10 | # IDE-related exclusions
11 | /.vscode/
12 | .idea/
13 | *.iml
14 |
15 | # Gradle build working directories
16 | /.gradle/
17 | /.gogradle/
18 | build
19 | /release/
20 | /vendor/*/
21 | openwhisk-cli.iml
22 | wski18n/i18n_resources.go
23 | bin/
24 | tests/build/
25 | tests/out/
26 |
27 | # Scala
28 | /.metals/
29 |
30 | # Misc
31 | javascript/
32 |
--------------------------------------------------------------------------------
/.scalafmt.conf:
--------------------------------------------------------------------------------
1 | #
2 | # Licensed to the Apache Software Foundation (ASF) under one or more
3 | # contributor license agreements. See the NOTICE file distributed with
4 | # this work for additional information regarding copyright ownership.
5 | # The ASF licenses this file to You under the Apache License, Version 2.0
6 | # (the "License"); you may not use this file except in compliance with
7 | # the License. You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 |
18 | style = intellij
19 | danglingParentheses = false
20 | maxColumn = 120
21 | docstrings = JavaDoc
22 | rewrite.rules = [SortImports]
23 | project.git = true
24 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | #
2 | # Licensed to the Apache Software Foundation (ASF) under one or more
3 | # contributor license agreements. See the NOTICE file distributed with
4 | # this work for additional information regarding copyright ownership.
5 | # The ASF licenses this file to You under the Apache License, Version 2.0
6 | # (the "License"); you may not use this file except in compliance with
7 | # the License. You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 |
18 | language: go
19 |
20 | matrix:
21 | include:
22 | - os: linux
23 | sudo: required
24 | go: "1.18"
25 | services: docker
26 | dist: xenial
27 |
28 | env:
29 | global:
30 | - secure: "Br0fD9CAKm8gqyEuwmltNJd4dGJCxPpj6feugHlO+CFFwfE/+kJKkpTlsDuRfrUzUDlWiETNPf0XGSjvPFqZExnLCE9XQh2+XF6u+S3YBWfM+rbbyRVAK6BlTwmt0u3jRJ2JP7spedTGZA+qfIWI+UkwoOexo7NcqtMPLahiZzheaaad8y3J+crHQCrB/kPrhLqKVMEOkIbveFdfV2QLfCOWgqP8e1LGZhPZ2N4QcNo0iB5uI4ZyYszTZDniXFKxz7kBs4tl4ZQDqRHqL02qKPsjbvjrZp83ql+PbC2dpgXi9YpaDuBEqKKX1rTQP5ppcwbobot5U3ItHWzpXbLCdsWxvbde/0enjMmOF1wwl71hPYIf7PkQmNAWXRtL2Z1TguO/dKCeXBLDER4YDQ79GYpikAMqnrRLou4rsyZrNUzg8aHbELzAHppDSpqEJN8ymGgWgmWBT8yPaWVwN5CjPFOxLPrVEObcwcNSGOcOvtmUGCnXKSZminNZUjz5QfWqpA+LYCUIbeOI2X2D+iQWklWONaU2A+PcNLaaegwwj4K+9ZlU08Ed8Ud5ZGhjN9s72OtoFPdSHAyBgf/qxIdnTzmmE+SQ90mDtG3VqjHy28Ix7cIGqaIEC8shFb0kKHqQ7AVkLPm0bsh2fbbYu/5YVpXHfxlKWfOLBV14bHUq3v0="
31 | - build_file_name=wsk
32 | # WARNING: if zip_file_name includes spaces or leading hyphen(s), it will create cosmetic breakage in 'before_deploy'
33 | - zip_file_name=OpenWhisk_CLI
34 |
35 | git:
36 | depth: 3
37 |
38 | notifications:
39 | email: false
40 | webhooks:
41 | urls:
42 | # travis2slack webhook to enable DMs on openwhisk-team.slack.com to PR authors with TravisCI results
43 | secure: "kDRRf+tVeBtPaPai8/ZCV/HdIJ0Oz0+xNCrBINnapKMJDcoNNIfNDPQc4sKJ3fPE9SVYjdiIyBqukggN9JeyhJAUQHcgUm5MAeM6Vut4M3EiAZHrs03do4Syax0MeHUJykaTkk/vQCQbMaHBpzz1DlNdwigTE4JNJNYU8ZDLSP2p2+qTUUouMdA2Wa3RDghTlGtGQOOL13YKvYuQSd1jVDrHpsnRSBfbdOd4IdUr5Og9TrzJQ6J9nl+zBO8r9cYmGdcWchQI2IxzdrOaTODqVRaVo8GF3e8Qzbqu0ZVqqYQYeFWGkfHKfRZLKpxTDuvQUhNrLaa0J1EwW14Z+2wM7PRg1S8VyGTo/jAba0niQMyW00/a2FLcGkacBUozvHfDN1xpEy2RtDjmPeyk/ZsrWkyyXF/lk2kJawPUy+kG7k2WeM+EN6ZFl9/OpjLZ75hgBsnJlzsDlbXkzQfmqshyEQB8l6c+OF9Mhme5KDNFGlbYyUruwKTghIvKLqU8wlVv8wC9D5ArdzeR7fqYAl4ikqpv6aZOwUwgHJXTEDSJ5A2QdIYIdjBwWJG6bJpMK36XY4ZpijKtiNs+4MhX/XNMx3FswkblhXKFdXaze5A6nwj5lkSmJ1I67F18FNN1dsPsEGpQT0xHP/Kdbc9CgPqeEq26OSml4Fl9IaBQquGeZnw="
44 |
45 | before_install:
46 | - export DEPLOY_BUILD_READY=false
47 |
48 | install:
49 | - cd $TRAVIS_BUILD_DIR/..
50 | - pip install --user --upgrade pip setuptools
51 |
52 | before_script:
53 | - cd $TRAVIS_BUILD_DIR
54 | # - ./gradlew --console=plain checkScalafmtAll
55 | # - GO_FILES=$(find . -iname '*.go' -type f)
56 | # - test -z "$(gofmt -s -l $(echo $GO_FILES))"
57 | # - cd $TRAVIS_BUILD_DIR/..
58 | # - git clone https://github.com/apache/openwhisk-utilities.git
59 | # - git clone https://github.com/apache/openwhisk.git
60 | # - cd openwhisk
61 | # - ./tools/travis/setup.sh
62 |
63 | script:
64 | # - cd $TRAVIS_BUILD_DIR/../openwhisk
65 | # - ./gradlew install tests:buildArtifacts
66 | # - cd $TRAVIS_BUILD_DIR
67 | # - export BUILD_VERSION="latest"
68 | # - if [ ! -z "$TRAVIS_TAG" ] ; then
69 | # export BUILD_VERSION=$TRAVIS_TAG;
70 | # fi
71 | # - ./gradlew --console=plain releaseBinaries -PpackageVersion=$BUILD_VERSION
72 | # - ./tools/travis/test_openwhisk.sh $BUILD_VERSION
73 | - echo "tests disabled"
74 |
75 | after_success:
76 | # - export DEPLOY_BUILD_READY=true
77 | # - if [ "$TRAVIS_EVENT_TYPE" == "cron" ] ; then
78 | # export DEPLOY_BUILD_READY=false;
79 | # fi
80 |
81 | before_deploy:
82 | - export RELEASE_PKG_FILE="$(cd "$TRAVIS_BUILD_DIR/release" && ls ${zip_file_name}-*.tgz ${zip_file_name}-*.zip)"
83 | - echo "Deploying $RELEASE_PKG_FILE to GitHub releases."
84 | - export GIT_TAG="latest"
85 | - export TAG=false;
86 | - if [ ! -z "$TRAVIS_TAG" ] ; then
87 | export GIT_TAG=$TRAVIS_TAG;
88 | export TAG=true;
89 | fi
90 | # This tag is automatically generated for the latest merged commit in master branch.
91 | - if [ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_EVENT_TYPE" == "push" ] && [ "$TRAVIS_OS_NAME" == "linux" ] ; then
92 | git config --global user.email "builds@travis-ci.com";
93 | git config --global user.name "Travis CI";
94 | export GIT_TAG="latest";
95 | git tag -d $GIT_TAG;
96 | git push -q https://$API_KEY@github.com/apache/openwhisk-cli :refs/tags/$GIT_TAG;
97 | GIT_COMMITTER_DATE="$(git show --format=%aD | head -1)" git tag $GIT_TAG -a -m "Generated tag from Travis CI build $TRAVIS_BUILD_NUMBER";
98 | git push -f -q https://$API_KEY@github.com/apache/openwhisk-cli $GIT_TAG;
99 | fi
100 | - echo "The GIT_TAG of this Travis build is $GIT_TAG."
101 |
102 | deploy:
103 | provider: releases
104 | api_key:
105 | 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=
106 | file_glob: true
107 | file:
108 | - release/${zip_file_name}-*.tgz
109 | - release/${zip_file_name}-*.zip
110 | overwrite: true
111 | skip_cleanup: true
112 | target_commitish: $TRAVIS_COMMIT
113 | tag_name: $GIT_TAG
114 | on:
115 | repo: apache/openwhisk-cli
116 | tags: $TAG
117 | condition: "$DEPLOY_BUILD_READY = true"
118 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
19 |
20 | # Changelog
21 |
22 | ## v1.2.0
23 |
24 | - Update Whisk Deploy (openwhisk-wskdeploy) dependency to v1.2.0 (#510)
25 | - Prep. for potential 1.2.0 release (#498)
26 | - Update for travis migration (#492)
27 | - Bump openwhisk-client-go dependency (#493)
28 | - Remove trailing slash on apihost #481 (#485)
29 | - Recognize .rs extension as a Rust action kind (#495)
30 | - Remove last Godeps, update Gogradle for gomod and Ansible setup (#496)
31 | - Update Gradle/Wrapper to latest version (#497)
32 |
33 | ## v1.1.0
34 |
35 | - Upgrade all Go dependencies to latest (#490)
36 | - Migrated to using go mod to manage dependencies (#489)
37 | - Upgrade travis to go 1.15
38 | - Support passing del annotation (#488)
39 | - Add an overwrite flag to "package bind" (#474)
40 | - Trigger parameter issue (#479)
41 | - remove test for download of iOS SDK (#478)
42 | - build binary test artifacts (#477)
43 | - Update test file (#463)
44 | - Fix regex for log stripping. (#462)
45 | - Ensure that the pollSince is greater than Activation start time (#461)
46 |
47 | ## v1.0.0
48 |
49 | - Allow log stripping to tolerate a missing stream identifier. (#444)
50 | - Add --logs options on activation get to return stripped logs as a convenience. (#445)
51 | - RestAssured fixes (#441)
52 | - Remove namespace property from wskprops (#434)
53 | - "wsk property get" can now return raw output for specific properties (#430)
54 | - Add dynamic column sizing to wsk activation list command (#427)
55 |
56 | ## v0.10.0
57 |
58 | - Integrate wskdeploy via `project` subcommand
59 | - Enhanced columnar output in `activation list`
60 | - CLI support for OpenWhisk enhancements including:
61 | - Support for specifying intra-container concurrency
62 | - New supported action languages: Ballerina, .Net, and Go
63 |
64 | ## v0.9.0
65 |
66 | - Initial release as an Apache Incubator project.
67 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 |
19 |
20 | # Contributing to OpenWhisk CLI
21 |
22 | ## Set up the development environment
23 |
24 | In order to develop OpenWhisk CLI on your local machine. First, install the prerequisites to
25 | download and build OpenWhisk CLI: [installing Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).
26 |
27 | Then, save the project in the location compliant with the Go standard naming convention, which means you need to
28 | created a directory named $GOPATH/src/github.com/apache/ and download the source code via the following commands:
29 |
30 | ```
31 | $ cd $GOPATH/src/github.com/apache/
32 | $ git clone https://github.com/apache/openwhisk-cli.git
33 | ```
34 |
35 | After cloning the source code, you need to install all the dependencies by running the command under openwhisk cli folder:
36 |
37 | ```
38 | $ go get -d -t ./...
39 | ```
40 |
41 | or
42 |
43 | ```
44 | $ make deps
45 | ```
46 |
47 | You should be able to build the binaries with either the go command, or the Gradle command, which is available in [README](https://github.com/apache/openwhisk-cli/blob/master/README.md).
48 |
49 |
50 | ## Proposing new features
51 |
52 | If you would like to implement a new feature, please [raise an issue](https://github.com/apache/openwhisk-cli/issues) before sending a pull request, so the feature can be discussed.
53 | This is to avoid you spending your valuable time working on a feature that the project developers are not willing to accept into the code base.
54 |
55 | ## Fixing bugs
56 |
57 | If you would like to fix a bug, please [raise an issue](https://github.com/apache/openwhisk-cli/issues) before sending a pull request, so it can be discussed.
58 | If the fix is trivial or non controversial then this is not usually necessary.
59 |
60 | ## Merge approval
61 |
62 | The project maintainers use LGTM (Looks Good To Me) in comments on the code review to
63 | indicate acceptance. A change requires LGTMs from two of the maintainers of each
64 | component affected.
65 |
66 | ## Communication
67 |
68 | Please use [Slack channel #whisk-users](https://cloudplatform.slack.com/messages/whisk_cli).
69 |
70 | ## Setup
71 |
72 | Project was written with `Go v1.9`. It has a dependency on [openwhisk-client-go](https://github.com/apache/openwhisk-client-go).
73 |
74 | ## Testing
75 |
76 | This repository needs unit tests.
77 |
78 | Please provide information that helps the developer test any changes they make before submitting.
79 |
80 | ## Coding style guidelines
81 |
82 | Use idomatic go. Document exported functions.
83 |
84 | # Publishing Tagged Release to Homebrew
85 |
86 | [Homebrew](https://brew.sh) is used to install `wsk` locally. Once we release a new version of `wsk` we should update its version in Homebrew.
87 |
88 | Get the new release SHA256 checksum by downloading the Source Code (tar.gz) from the [releases page](https://github.com/apache/openwhisk-cli/releases) and running `shasum -a 256 X.Y.Z.tar.gz` on the tarball.
89 |
90 | Update brew formula with the automation command `brew bump-formula-pr`:
91 | ```bash
92 | $ brew bump-formula-pr \
93 | --url='https://github.com/apache/openwhisk-cli/archive/X.Y.Z.tar.gz' \
94 | --sha256='PASTE THE SHA256 CHECKSUM HERE' \
95 | wsk
96 | ```
97 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
204 | ========================================================================
205 | Apache License 2.0
206 | ========================================================================
207 |
208 | This product bundles the files gradlew and gradlew.bat from Gradle v5.5
209 | which are distributed under the Apache License, Version 2.0.
210 | For details see ./gradlew and ./gradlew.bat.
211 |
--------------------------------------------------------------------------------
/NOTICE.txt:
--------------------------------------------------------------------------------
1 | Apache OpenWhisk Command-line Interface (CLI)
2 | Copyright 2016-2021 The Apache Software Foundation
3 |
4 | This product includes software developed at
5 | The Apache Software Foundation (http://www.apache.org/).
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
19 |
20 | # OpenWhisk Command-line Interface `wsk`
21 |
22 | [](https://travis-ci.com/apache/openwhisk-cli)
23 | [](http://www.apache.org/licenses/LICENSE-2.0)
24 | [](http://slack.openwhisk.org/)
25 | [](https://twitter.com/intent/follow?screen_name=openwhisk)
26 |
27 | OpenWhisk Command-line Interface (CLI) is a unified tool that provides a consistent interface to interact with OpenWhisk services.
28 |
29 | ## Getting started
30 |
31 | Here are some quick links to help you get started:
32 |
33 | - [Downloading released binaries](#downloading-released-binaries) for Linux, macOS and Windows
34 | - [Running the `wsk` CLI](#running-the-wsk-cli) executable
35 | - [Building the project](#building-the-project) - download and build the GoLang source code
36 | - [Contributing to the project](#contributing-to-the-project) - join us!
37 |
38 | ---
39 |
40 | ## Downloading released binaries
41 |
42 | Executable binaries of the OpenWhisk CLI are available for download on the project's GitHub [releases page](https://github.com/apache/openwhisk-cli/releases).
43 |
44 | We currently provide binaries for the following Operating Systems (OS) and architecture combinations:
45 |
46 | Operating System | Architectures
47 | --- | ---
48 | Linux | i386, AMD64, ARM, ARM64, PPC64 (Power), S/390 and IBM Z
49 | macOS (Darwin) | 386[1](#1), AMD64
50 | Windows | 386, AMD64
51 |
52 | 1. macOS, 32-bit (386) released versions are not available for builds using Go lang version 1.15 and greater.
53 |
54 | We also provide instructions on how to build your own binaries from source code. See [Building the project](#building-the-project).
55 |
56 | ---
57 |
58 | ## Running the `wsk` CLI
59 |
60 | You can copy the `wsk` binary to any folder, and add the folder to your system `PATH` in order to run the OpenWhisk CLI command from anywhere on your system. To get the CLI command help, execute the following:
61 |
62 | ```sh
63 | $ wsk --help
64 | ```
65 |
66 | To get CLI command debug information, include the `-d`, or `--debug` flag when executing this command.
67 |
68 | ---
69 |
70 | ## Building the project
71 |
72 | ### GoLang setup
73 |
74 | The Openwhisk CLI is a GoLang program, so you will first need to [Download and install GoLang](https://golang.org/dl/) onto your local machine.
75 |
76 | > **Note** Go version 1.15 or higher is recommended
77 |
78 | Make sure your `$GOPATH` is defined correctly in your environment. For detailed setup of your GoLang development environment, please read [How to Write Go Code](https://golang.org/doc/code.html).
79 |
80 | ### Download the source code from GitHub
81 |
82 | As the code is managed using GitHub, it is easiest to retrieve the code using the `git clone` command.
83 |
84 | if you just want to build the code and do not intend to be a Contributor, you can clone the latest code from the Apache repository:
85 |
86 | ```sh
87 | git clone git@github.com:apache/openwhisk-cli
88 | ```
89 |
90 | or you can specify a release (tag) if you do not want the latest code by using the `--branch ` flag. For example, you can clone the source code for the tagged 1.1.0 [release](https://github.com/apache/openwhisk-cli/releases/tag/1.1.0)
91 |
92 | ```sh
93 | git clone --branch 1.1.0 git@github.com:apache/openwhisk-cli
94 | ```
95 |
96 | You can also pull the code from a fork of the repository. If you intend to become a Contributor to the project, read the section [Contributing to the project](#contributing-to-the-project) below on how to setup a fork.
97 |
98 | ### Build using `go build`
99 |
100 | Use the Go utility to build the ```wsk`` binary.
101 |
102 | Change into the cloned project directory and use `go build` with the target output name for the binary:
103 |
104 | ```sh
105 | $ go build -o wsk
106 | ```
107 |
108 | an executable named `wsk` will be created in the project directory compatible with your current operating system and architecture.
109 |
110 | #### Building for other Operating Systems (GOOS) and Architectures (GOARCH)
111 |
112 | If you would like to build the binary for a specific operating system and processor architecture, you may add the arguments `GOOS` and `GOARCH` into the Go build command (as inline environment variables).
113 |
114 | For example, run the following command to build the binary for 64-bit Linux:
115 |
116 | ```sh
117 | $ GOOS=linux GOARCH=amd64 go build -o wsk
118 | ```
119 |
120 | If successful, an executable named `wsk` will be created in the project directory compatible with your current operating system and architecture.
121 |
122 | Supported value combinations include:
123 |
124 | `GOOS` | `GOARCH`
125 | --- | ---
126 | linux | 386 (32-bit), amd64 (64-bit), s390x (S/390, Z), ppc64le (Power), arm (32-bit), arm64 (64-bit)
127 | darwin (macOS) | amd64
128 | windows | 386 (32-bit), amd64 (64-bit)
129 |
130 | ### Build using Gradle
131 |
132 | The project includes its own packaged version of Gradle called Gradle Wrapper which is invoked using the `./gradlew` command on Linux/Unix/Mac or `gradlew.bat` on Windows.
133 |
134 | 1. Gradle requires you to [install Java JDK version 8](https://gradle.org/install/) or higher
135 |
136 | 1. Clone the `openwhisk-cli` repo:
137 |
138 | ```sh
139 | git clone https://github.com/apache/openwhisk-cli
140 | ```
141 |
142 | and change into the project directory.
143 |
144 | 1. Cross-compile binaries for all supported Operating Systems and Architectures:
145 |
146 | ```sh
147 | ./gradlew goBuild
148 | ```
149 |
150 | Upon a successful build, the `wsk` binaries can be found under the corresponding `build/-/` folder of your project:
151 |
152 | ```sh
153 | $ ls build
154 | darwin-amd64 linux-amd64 linux-arm64 linux-s390x windows-amd64
155 | linux-386 linux-arm linux-ppc64le windows-386
156 | ```
157 |
158 | #### Compiling for a single OS/ARCH
159 |
160 | 1. View gradle build tasks for supported Operating Systems and Architectures:
161 |
162 | ```sh
163 | ./gradlew tasks
164 | ```
165 |
166 | you will see build tasks for supported OS/ARCH combinations:
167 |
168 | ```sh
169 | Gogradle tasks
170 | --------------
171 | buildDarwinAmd64 - Custom go task.
172 | buildLinux386 - Custom go task.
173 | buildLinuxAmd64 - Custom go task.
174 | buildLinuxArm - Custom go task.
175 | buildLinuxArm64 - Custom go task.
176 | buildLinuxPpc64le - Custom go task.
177 | buildLinuxS390x - Custom go task.
178 | buildWindows386 - Custom go task.
179 | buildWindowsAmd64 - Custom go task.
180 | ```
181 |
182 | > **Note**: The `buildWindows386` option is only supported on Golang versions less than 1.15.
183 |
184 | 1. Build using one of these tasks, for example:
185 |
186 | ```sh
187 | $ ./gradlew buildDarwinAmd64
188 | ```
189 |
190 | > **Note** You may use the `compile` Gradle task to build a subset of the supported platforms using the `buildPlatforms` parameter and supplying a comma-separated list, for example:
191 | `-PbuildPlatforms=linux-amd64,mac-amd64,windows-amd64`
192 |
193 | #### Using your own local Gradle to build
194 |
195 | Alternatively, you can choose to [Install Gradle](https://gradle.org/install/) and use it instead of the project's Gradle Wrapper. If so, you would use the `gradle` command instead of `gradlew`. If you do elect to use your own Gradle, verify its version is `6.8.1` or higher:
196 |
197 | ```sh
198 | gradle -version
199 | ```
200 |
201 | > **Note** If using your own local Gradle installation, use the `gradle` command instead of the `./gradlew` command in the build instructions below.
202 |
203 | ### Building for internationalization (i18n)
204 |
205 | The CLI internationalization is generated dynamically using the `bindata` tool as part of the gradle build. If you need to install it manually, you may use:
206 |
207 | ```sh
208 | $ go get -u github.com/jteeuwen/go-bindata/...
209 | $ go-bindata -pkg wski18n -o wski18n/i18n_resources.go wski18n/resources
210 | ```
211 |
212 | > **Note**: the `go-bindata` package will automatically be installed if the `go build` command is used in the project as it is listed in the `go.mod` dependency file.
213 |
214 | ### Running unit tests
215 |
216 | ##### Using Go
217 |
218 | ```sh
219 | $ cd commands
220 | $ go test -tags=unit -v
221 | ```
222 |
223 | > **Note** A large number of CLI tests today are not yet available as Go tests.
224 |
225 | ##### Using gradle
226 |
227 | All tests can be run using the Gradle script:
228 |
229 | ```sh
230 | $ ./gradlew goTest -PgoTags=unit
231 | $ ./gradlew goTest -PgoTags=native
232 | ```
233 |
234 | ### Running integration tests
235 |
236 | Integration tests are best left to the Travis build as they depend on a fully functional OpenWhisk environment.
237 |
238 | ---
239 |
240 | ## Contributing to the project
241 |
242 | ### Git repository setup
243 |
244 | 1. [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) the Apache repository
245 |
246 | If you intend to contribute code, you will want to fork the `apache/openwhisk-cli` repository into your github account and use that as the source for your clone.
247 |
248 | 1. Clone the repository from your fork:
249 |
250 | ```sh
251 | git clone git@github.com:${GITHUB_ACCOUNT_USERNAME}/openwhisk-cli.git
252 | ```
253 |
254 | 1. Add the Apache repository as a remote with the `upstream` alias:
255 |
256 | ```sh
257 | git remote add upstream git@github.com:apache/openwhisk-cli
258 | ```
259 |
260 | You can now use `git push` to push local `commit` changes to your `origin` repository and submit pull requests to the `upstream` project repository.
261 |
262 | 1. Optionally, prevent accidental pushes to `upstream` using this command:
263 |
264 | ```sh
265 | git remote set-url --push upstream no_push
266 | ```
267 |
268 | > Be sure to [Sync your fork](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/syncing-a-fork) before starting any contributions to keep it up-to-date with the upstream repository.
269 |
270 | ### Adding new dependencies
271 |
272 | Please use `go get` to add new dependencies to the `go.mod` file:
273 |
274 | ```sh
275 | go get -u github.com/project/libname@v1.2.0
276 | ```
277 |
278 | > Please avoid using commit hashes for referencing non-OpenWhisk libraries.
279 |
280 | ### Removing unused dependencies
281 |
282 | Please us `go tidy` to remove any unused dependencies after any significant code changes:
283 |
284 | ```sh
285 | go mod tidy
286 | ```
287 |
288 | ### Updating dependency versions
289 |
290 | Although you might be tempted to edit the go.mod file directly, please use the recommended method of using the `go get` command:
291 |
292 | ```sh
293 | go get -u github.com/project/libname # Using "latest" version
294 | go get -u github.com/project/libname@v1.1.0 # Using tagged version
295 | go get -u github.com/project/libname@aee5cab1c # Using a commit hash
296 | ```
297 |
298 | ### Updating Go version
299 |
300 | Although you could edit the version directly in the go.mod file, it is better to use the `go edit` command:
301 |
302 | ```sh
303 | go mod edit -go=1.15
304 | ```
305 |
306 | ---
307 |
308 | ## Continuous Integration
309 |
310 | Travis CI is used as a continuous delivery service for Linux and Mac. Currently, Travis CI supports the environments of Linux and Mac, but it is not available for Windows. The project would like to add AppVeyor CI in the future to run test cases for Windows.
311 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | /* - Scalafmt Toolage -*/
19 | buildscript {
20 | repositories {
21 | jcenter()
22 | mavenCentral()
23 | }
24 | dependencies {
25 | classpath "cz.alenkacz:gradle-scalafmt:${gradle.scalafmt.version}"
26 | }
27 | }
28 |
29 | plugins {
30 | id 'com.github.blindpirate.gogradle' version '0.10'
31 | }
32 |
33 | subprojects {
34 | apply plugin: 'scalafmt'
35 | scalafmt.configFilePath = gradle.scalafmt.config
36 | }
37 |
38 | golang {
39 | packagePath = 'github.com/apache/openwhisk-cli' as String
40 | buildTags = (rootProject.findProperty('goTags')?:'').split(',')
41 | }
42 |
43 | // The `gogradle` plugin was designed to work with `govendor` and `godeps` tools
44 | // We must disable its tasks that attempt to "fetch" dependencies
45 | // into a "/vendor" directory and use them to build the project (which will fail)
46 | installDependencies.enabled = false
47 | resolveBuildDependencies.enabled = false
48 | resolveTestDependencies.enabled = false
49 |
50 | // Disable "go vet" and "gofmt" as gogradle uses deprecated syntax
51 | goVet.enabled = false
52 | gofmt.enabled = false
53 |
54 | /*
55 | The OpenWhiskPlatform class is a utility class to make the rest of what
56 | happens with platforms a bit more understandable. A "Platform" is a tuple
57 | of an operating system and a processor. Currently, the OpenWhisk CLI
58 | supports three OS's: Linux, Mac/Darwin, and Windows. It supports x86
59 | (32-bit or 64-bit) on all OS's. On Linux, it also support System Z (s390x),
60 | PowerPC (ppc64le), and ARM (32-bit and 64-bit) architectures.
61 |
62 | Different contexts use different codings to refer to these architectures --
63 | the class attempts to provide and interpret all needed codings. Internal
64 | storage is in "GO" format:
65 |
66 | OS: linux, darwin, windows
67 | Arch: 386, amd64, s390x, ppc64le, arm
68 |
69 | TODO - It may be appropriate to refactor into a general purpose Platform
70 | class for all builds, then to extend with specifics needed for
71 | the OpenWhisk CLI build.
72 | */
73 | class OpenWhiskPlatform {
74 | String goOs
75 | String goArch
76 |
77 | /*
78 | The 'zipFileName' property is the root file name to use for archives.
79 | */
80 | static String zipFileName
81 |
82 | /*
83 | Create a platform for the local platform
84 | */
85 | OpenWhiskPlatform() {
86 | this(System.properties['os.name'], System.properties['os.arch'])
87 | }
88 |
89 | OpenWhiskPlatform(String platformSpec) {
90 | this(*platformSpec.split('-'))
91 | }
92 |
93 | OpenWhiskPlatform(String inOs, String inArch) {
94 | goOs=inOs.toLowerCase()
95 | .replaceAll(~/^mac.*$/,'darwin')
96 | .replaceAll(~/^.*n[ui]x.*$/,'linux')
97 | goArch=inArch.toLowerCase()
98 | .replaceAll('x86_64','amd64')
99 | .replaceAll('i386','386')
100 | .replaceAll('x86_32','386')
101 | }
102 |
103 | /**
104 | * Return the Openwhisk OS for this Platform
105 | */
106 | String getOwOs() {
107 | ((goOs == 'darwin') ? 'mac' : goOs)
108 | }
109 |
110 | String getGoPlatform() {
111 | "${goOs}-${goArch}"
112 | }
113 |
114 | /*
115 | Everything below here is specific to the CLI build and could be
116 | factored out into a subclass.
117 | */
118 | String getArchiveDirName() {
119 | "${this.owOs}/${goArch}"
120 | }
121 |
122 | String getArchiveFileName() {
123 | String suffix
124 | switch (goArch) {
125 | case "386": suffix = '-32bit'; break;
126 | case "amd64": suffix = ''; break;
127 | default: suffix = "-${goArch}"; break;
128 | }
129 | String archivetype = (goOs == 'linux') ? 'tgz' : 'zip'
130 | "${zipFileName}-${this.owOs}${suffix}.${archivetype}"
131 | }
132 | }
133 |
134 | /*
135 | Configuration of OpenWhisk Platform behavior based on environment and defaults
136 | */
137 | OpenWhiskPlatform.zipFileName =
138 | System.env['zip_file_name'] ?:
139 | (rootProject.findProperty('zipFileName') ?: 'OpenWhisk_CLI')
140 |
141 | project.ext.packageVersion =
142 | rootProject.findProperty('packageVersion') ?: 'latest'
143 |
144 | project.ext.cliBuildLocation =
145 | rootProject.findProperty('cliBuildLocation') ?: './build'
146 |
147 | project.ext.cliReleaseLocation =
148 | rootProject.findProperty('cliReleaseLocation') ?: './release'
149 |
150 | String buildFileName = System.env['build_file_name'] ?:
151 | (rootProject.findProperty('buildFileName') ?: 'wsk')
152 |
153 | /*
154 | 'platforms' property will be null for a local compile, or a list (comma or
155 | space-separated) of hyphenated Goos-Goarch pairs. Some transformation is
156 | done when parsing to handle misconceptions.
157 |
158 | TODO: More syntax/validity checking and feedback, perhaps as part of a
159 | Platform object as proposed above...
160 | */
161 | rootProject.ext.localPlatform = new OpenWhiskPlatform()
162 |
163 | if (rootProject.hasProperty('buildPlatforms')) {
164 | rootProject.ext.platforms = buildPlatforms.tokenize(' ,').collect {
165 | new OpenWhiskPlatform(it)
166 | }
167 | } else {
168 | if (!rootProject.hasProperty('nativeCompile')) {
169 | rootProject.ext.platforms = [
170 | 'linux-386', 'linux-amd64',
171 | 'linux-s390x', 'linux-ppc64le', 'linux-arm', 'linux-arm64',
172 | 'darwin-amd64',
173 | 'windows-386', 'windows-amd64'
174 | ].collect { new OpenWhiskPlatform(it) }
175 | } else {
176 | rootProject.ext.platforms = [ rootProject.localPlatform ]
177 | }
178 | }
179 |
180 | /*
181 | I18n support
182 | */
183 |
184 | // task getGoPath(type: Exec) {
185 | // executable = 'echo'
186 | // args = ["$System.env.GOPATH"]
187 |
188 | // doLast{
189 | // println commandLine
190 | // }
191 | // }
192 |
193 | task getGoBinData(type: Exec) {
194 | executable = 'go'
195 | args = ['get', '-u', 'github.com/jteeuwen/go-bindata/...']
196 |
197 | doLast{
198 | println commandLine
199 | }
200 | }
201 |
202 | task goI18n(type: Exec) {
203 | dependsOn 'getGoBinData'
204 | executable = "$System.env.GOPATH" + '/bin/go-bindata'
205 | // run '${GOPATH}/bin/go-bindata -pkg wski18n -o wski18n/i18n_resources.go wski18n/resources'
206 | args = ['-pkg', 'wski18n', '-o', 'wski18n/i18n_resources.go', 'wski18n/resources']
207 |
208 | doLast{
209 | println commandLine
210 | }
211 | }
212 |
213 | /*
214 | Checks -- add golint to the checks run prior to build.
215 | The get step is needed to be sure a golint binary is available to run.
216 | */
217 |
218 | task getGoLint(type: Exec) {
219 | executable = 'go'
220 | args = ['get', '-u', 'golang.org/x/lint/golint']
221 |
222 | doLast{
223 | println commandLine
224 | }
225 | }
226 |
227 | task goLint(type: Exec) {
228 | dependsOn 'getGoLint'
229 | executable = 'golint'
230 | //args = ['./', './commands/']
231 | args = ['./']
232 |
233 | doLast{
234 | println commandLine
235 | }
236 | }
237 |
238 | goCheck.dependsOn(goLint)
239 | goPrepare.dependsOn(goI18n)
240 |
241 | goBuild {
242 | targetPlatform = rootProject.platforms*.goPlatform
243 | def now = new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
244 |
245 | // WARNING: The single quotes are intentional! The gogradle plugin will
246 | // parse the command with the GString engine at execution time.
247 | go(['build',
248 | '-ldflags', "-X main.CLI_BUILD_TIME=${now}" as String,
249 | '-o', cliBuildLocation+'/${GOOS}-${GOARCH}/'+buildFileName+'${GOEXE}',
250 | golang.packagePath ] as List)
251 | }
252 |
253 | gofmt {
254 | gofmt "-s -w ."
255 | }
256 |
257 | task compile(type: Copy, dependsOn: goBuild) {
258 | destinationDir = file(cliBuildLocation)
259 | from("${cliBuildLocation}/${rootProject.localPlatform.goOs}-${rootProject.localPlatform.goArch}")
260 | }
261 |
262 | task build(type: DefaultTask, dependsOn: compile)
263 |
264 | /*
265 | For each platform, create an individual archive in a platform appropriate
266 | format (tarball for Linux, zipfile for Mac & Windows).
267 | */
268 | task individualArchives(
269 | dependsOn: rootProject.platforms.collect() { p ->
270 | task("release${p.goOs.capitalize()}${p.goArch.capitalize()}",
271 | type: (p.goOs == 'linux') ? Tar : Zip, dependsOn: compile) {
272 | if (p.goOs == 'linux') { compression = Compression.GZIP }
273 | destinationDir = file(cliReleaseLocation)
274 | baseName = "${p.zipFileName}-${packageVersion}-${p.owOs}-${p.goArch}"
275 | from("${cliBuildLocation}/${p.goOs}-${p.goArch}/") {
276 | include "${buildFileName}*"
277 | }
278 | from("./") {
279 | include "LICENSE.txt", "NOTICE.txt", "README.md"
280 | exclude "wski18n"
281 | }
282 | }
283 | })
284 |
285 | /*
286 | Create a 'content.json' file representing all that was
287 | compiled and its appropriate directory in the Tarball that will be created
288 | for deployment to local Nginx instances.
289 | */
290 |
291 | task index() {
292 | def content = [:]
293 | for (p in platforms) {
294 | def pathObject = [ "path" : "${p.archiveDirName}/${p.archiveFileName}" ]
295 | content.get(p.owOs,[:])[p.goArch] = pathObject
296 | // TODO: Default architecture should be configurable as a property
297 | if (p.goArch == 'amd64') {
298 | content.get(p.owOs,[:])['default'] = pathObject
299 | }
300 | }
301 |
302 | doLast {
303 | mkdir(cliBuildLocation)
304 | file("${cliBuildLocation}/content.json").text = groovy.json.JsonOutput.toJson(["cli": content])
305 | }
306 | }
307 |
308 | task releaseBinaries(type: Tar, dependsOn: [individualArchives, index]) {
309 | compression = Compression.GZIP
310 | destinationDir = file(cliReleaseLocation)
311 | baseName = "${OpenWhiskPlatform.zipFileName}-${packageVersion}-all"
312 | from("${cliBuildLocation}/content.json") { into('.') }
313 | rootProject.platforms.each() { p ->
314 | from(cliReleaseLocation) {
315 | include("${p.zipFileName}-${packageVersion}-${p.owOs}-${p.goArch}.*")
316 | into p.archiveDirName
317 | rename { p.archiveFileName }
318 | }
319 | }
320 | }
321 |
322 | task clean(type: Delete, dependsOn: goClean) {
323 | delete cliBuildLocation, cliReleaseLocation
324 | }
325 |
--------------------------------------------------------------------------------
/commands/action_test.go:
--------------------------------------------------------------------------------
1 | // +build unit
2 |
3 | /*
4 | * Licensed to the Apache Software Foundation (ASF) under one or more
5 | * contributor license agreements. See the NOTICE file distributed with
6 | * this work for additional information regarding copyright ownership.
7 | * The ASF licenses this file to You under the Apache License, Version 2.0
8 | * (the "License"); you may not use this file except in compliance with
9 | * the License. You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | */
19 |
20 | package commands
21 |
22 | import (
23 | "encoding/base64"
24 | "github.com/stretchr/testify/assert"
25 | "os"
26 | "testing"
27 | )
28 |
29 | func TestBallerinaBinaryFile(t *testing.T) {
30 | file := "file.balx"
31 | args := []string{"name", file}
32 | parms := ActionFlags{}
33 |
34 | f, error := os.Create(file)
35 | if error != nil {
36 | t.Fatalf("could not create file: %s", error.Error())
37 | }
38 | d := []byte("balx")
39 | f.Write(d)
40 | f.Close()
41 |
42 | exec, error := getExec(args, parms)
43 | os.Remove(file)
44 |
45 | if error != nil {
46 | t.Errorf("unexpected exec error: %s", error.Error())
47 | } else {
48 | assert.Equal(t, exec.Kind, "ballerina:default")
49 | assert.Equal(t, base64.StdEncoding.EncodeToString(d), *exec.Code)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/commands/commands.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package commands
19 |
20 | import (
21 | "errors"
22 | "net/http"
23 | "os"
24 | "runtime"
25 |
26 | "github.com/apache/openwhisk-cli/wski18n"
27 | "github.com/apache/openwhisk-client-go/whisk"
28 |
29 | "github.com/spf13/cobra"
30 | )
31 |
32 | var Client *whisk.Client
33 |
34 | const DefaultOpenWhiskApiPath string = "/api"
35 |
36 | var UserAgent string = "OpenWhisk-CLI"
37 | var AdditionalHeaders http.Header
38 |
39 | func SetupClientConfig(cmd *cobra.Command, args []string) error {
40 | baseURL, err := whisk.GetURLBase(Properties.APIHost, DefaultOpenWhiskApiPath)
41 |
42 | // Determine if the parent command will require the API host to be set
43 | apiHostRequired := (cmd.Parent().Name() == "property" && cmd.Name() == "get" && (Flags.property.auth ||
44 | Flags.property.cert || Flags.property.key || Flags.property.apihost ||
45 | Flags.property.apiversion || Flags.property.cliversion)) ||
46 | (cmd.Parent().Name() == "property" && cmd.Name() == "set" && (len(Flags.property.apihostSet) > 0 ||
47 | len(Flags.property.apiversionSet) > 0 || len(Flags.Global.Auth) > 0)) ||
48 | (cmd.Parent().Name() == "sdk" && cmd.Name() == "install" && len(args) > 0 && args[0] == "bashauto")
49 |
50 | // Display an error if the parent command requires an API host to be set, and the current API host is not valid
51 | if err != nil && !apiHostRequired {
52 | whisk.Debug(whisk.DbgError, "whisk.GetURLBase(%s, %s) error: %s\n", Properties.APIHost, DefaultOpenWhiskApiPath, err)
53 | errMsg := wski18n.T("The API host is not valid: {{.err}}", map[string]interface{}{"err": err})
54 | whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_GENERAL,
55 | whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
56 | return whiskErr
57 | }
58 |
59 | clientConfig := &whisk.Config{
60 | Cert: Properties.Cert,
61 | Key: Properties.Key,
62 | AuthToken: Properties.Auth,
63 | Namespace: Properties.Namespace,
64 | BaseURL: baseURL,
65 | Version: Properties.APIVersion,
66 | Insecure: Flags.Global.Insecure,
67 | Host: Properties.APIHost,
68 | UserAgent: UserAgent + "/1.0 (" + Properties.CLIVersion + ") " + runtime.GOOS + " " + runtime.GOARCH,
69 | AdditionalHeaders: AdditionalHeaders,
70 | }
71 |
72 | if len(clientConfig.Host) == 0 {
73 | config, _ := whisk.GetDefaultConfig()
74 | clientConfig.Host = config.Host
75 | if len(clientConfig.Host) == 0 {
76 | clientConfig.Host = "openwhisk.ng.bluemix.net"
77 | }
78 | }
79 |
80 | // Setup client
81 | Client, err = whisk.NewClient(http.DefaultClient, clientConfig)
82 |
83 | if err != nil {
84 | whisk.Debug(whisk.DbgError, "whisk.NewClient(%#v, %#v) error: %s\n", http.DefaultClient, clientConfig, err)
85 | errMsg := wski18n.T("Unable to initialize server connection: {{.err}}", map[string]interface{}{"err": err})
86 | whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_GENERAL,
87 | whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
88 | return whiskErr
89 | }
90 |
91 | return nil
92 | }
93 |
94 | func init() {}
95 |
96 | func getKeyValueArgs(args []string, argIndex int, parsedArgs []string) ([]string, []string, error) {
97 | var whiskErr error
98 | var key string
99 | var value string
100 |
101 | if len(args)-1 >= argIndex+2 {
102 | key = args[argIndex+1]
103 | value = args[argIndex+2]
104 | parsedArgs = append(parsedArgs, getFormattedJSON(key, value))
105 | args = append(args[:argIndex], args[argIndex+3:]...)
106 | } else {
107 | whisk.Debug(whisk.DbgError, "Arguments for '%s' must be a key/value pair; args: %s", args[argIndex], args)
108 | errMsg := wski18n.T("Arguments for '{{.arg}}' must be a key/value pair",
109 | map[string]interface{}{"arg": args[argIndex]})
110 | whiskErr = whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG,
111 | whisk.DISPLAY_USAGE)
112 | }
113 |
114 | return parsedArgs, args, whiskErr
115 | }
116 |
117 | func getValueFromArgs(args []string, argIndex int, parsedArgs []string) ([]string, []string, error) {
118 | var whiskErr error
119 |
120 | if len(args)-1 >= argIndex+1 {
121 | parsedArgs = append(parsedArgs, args[argIndex+1])
122 | args = append(args[:argIndex], args[argIndex+2:]...)
123 | } else {
124 | whisk.Debug(whisk.DbgError, "An argument must be provided for '%s'; args: %s", args[argIndex], args)
125 | errMsg := wski18n.T("An argument must be provided for '{{.arg}}'",
126 | map[string]interface{}{"arg": args[argIndex]})
127 | whiskErr = whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG,
128 | whisk.DISPLAY_USAGE)
129 | }
130 |
131 | return parsedArgs, args, whiskErr
132 | }
133 |
134 | func parseArgs(args []string) ([]string, []string, []string, []string, []string, error) {
135 | var paramArgs []string
136 | var annotArgs []string
137 | var feedParamArgs []string
138 | var triggerParamArgs []string
139 | var whiskErr error
140 |
141 | i := 0
142 |
143 | for i < len(args) {
144 | if args[i] == "-P" || args[i] == "--param-file" {
145 | paramArgs, args, whiskErr = getValueFromArgs(args, i, paramArgs)
146 | if whiskErr != nil {
147 | whisk.Debug(whisk.DbgError, "getValueFromArgs(%#v, %d) failed: %s\n", args, i, whiskErr)
148 | errMsg := wski18n.T("The parameter arguments are invalid: {{.err}}",
149 | map[string]interface{}{"err": whiskErr})
150 | whiskErr = whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG,
151 | whisk.DISPLAY_USAGE)
152 | return nil, nil, nil, nil, nil, whiskErr
153 | }
154 |
155 | filename := paramArgs[len(paramArgs)-1]
156 | paramArgs[len(paramArgs)-1], whiskErr = ReadFile(filename)
157 | if whiskErr != nil {
158 | whisk.Debug(whisk.DbgError, "readFile(%s) error: %s\n", filename, whiskErr)
159 | return nil, nil, nil, nil, nil, whiskErr
160 | }
161 | } else if args[i] == "-A" || args[i] == "--annotation-file" {
162 | annotArgs, args, whiskErr = getValueFromArgs(args, i, annotArgs)
163 | if whiskErr != nil {
164 | whisk.Debug(whisk.DbgError, "getValueFromArgs(%#v, %d) failed: %s\n", args, i, whiskErr)
165 | errMsg := wski18n.T("The annotation arguments are invalid: {{.err}}",
166 | map[string]interface{}{"err": whiskErr})
167 | whiskErr = whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG,
168 | whisk.DISPLAY_USAGE)
169 | return nil, nil, nil, nil, nil, whiskErr
170 | }
171 |
172 | filename := annotArgs[len(annotArgs)-1]
173 | annotArgs[len(annotArgs)-1], whiskErr = ReadFile(filename)
174 | if whiskErr != nil {
175 | whisk.Debug(whisk.DbgError, "readFile(%s) error: %s\n", filename, whiskErr)
176 | return nil, nil, nil, nil, nil, whiskErr
177 | }
178 | } else if args[i] == "-p" || args[i] == "--param" {
179 | paramArgs, args, whiskErr = getKeyValueArgs(args, i, paramArgs)
180 | if whiskErr != nil {
181 | whisk.Debug(whisk.DbgError, "getKeyValueArgs(%#v, %d) failed: %s\n", args, i, whiskErr)
182 | errMsg := wski18n.T("The parameter arguments are invalid: {{.err}}",
183 | map[string]interface{}{"err": whiskErr})
184 | whiskErr = whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG,
185 | whisk.DISPLAY_USAGE)
186 | return nil, nil, nil, nil, nil, whiskErr
187 | }
188 | } else if args[i] == "-a" || args[i] == "--annotation" {
189 | annotArgs, args, whiskErr = getKeyValueArgs(args, i, annotArgs)
190 | if whiskErr != nil {
191 | whisk.Debug(whisk.DbgError, "getKeyValueArgs(%#v, %d) failed: %s\n", args, i, whiskErr)
192 | errMsg := wski18n.T("The annotation arguments are invalid: {{.err}}",
193 | map[string]interface{}{"err": whiskErr})
194 | whiskErr = whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG,
195 | whisk.DISPLAY_USAGE)
196 | return nil, nil, nil, nil, nil, whiskErr
197 | }
198 | } else if args[i] == "-F" || args[i] == "--feed-param" {
199 | feedParamArgs, args, whiskErr = getKeyValueArgs(args, i, feedParamArgs)
200 | if whiskErr != nil {
201 | whisk.Debug(whisk.DbgError, "getKeyValueArgs(%#v, %d) failed: %s\n", args, i, whiskErr)
202 | whiskErr = whisk.MakeWskError(whiskErr, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG,
203 | whisk.DISPLAY_USAGE)
204 | return nil, nil, nil, nil, nil, whiskErr
205 | }
206 | } else if args[i] == "-T" || args[i] == "--trigger-param" {
207 | triggerParamArgs, args, whiskErr = getKeyValueArgs(args, i, triggerParamArgs)
208 | if whiskErr != nil {
209 | whisk.Debug(whisk.DbgError, "getKeyValueArgs(%#v, %d) failed: %s\n", args, i, whiskErr)
210 | whiskErr = whisk.MakeWskError(whiskErr, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG,
211 | whisk.DISPLAY_USAGE)
212 | return nil, nil, nil, nil, nil, whiskErr
213 | }
214 | } else {
215 | i++
216 | }
217 | }
218 |
219 | whisk.Debug(whisk.DbgInfo, "Found param args '%s'.\n", paramArgs)
220 | whisk.Debug(whisk.DbgInfo, "Found annotations args '%s'.\n", annotArgs)
221 | whisk.Debug(whisk.DbgInfo, "Found feed param args '%s'.\n", feedParamArgs)
222 | whisk.Debug(whisk.DbgInfo, "Found trigger param args '%s'.\n", triggerParamArgs)
223 | whisk.Debug(whisk.DbgInfo, "Arguments with param args removed '%s'.\n", args)
224 |
225 | return args, paramArgs, annotArgs, feedParamArgs, triggerParamArgs, nil
226 | }
227 |
228 | func Execute() error {
229 | var err error
230 |
231 | whisk.Debug(whisk.DbgInfo, "wsk args: %#v\n", os.Args)
232 | os.Args, Flags.common.param, Flags.common.annotation, Flags.trigger.feedParam, Flags.trigger.triggerParam, err = parseArgs(os.Args)
233 |
234 | if err != nil {
235 | whisk.Debug(whisk.DbgError, "parseParams(%s) failed: %s\n", os.Args, err)
236 | errMsg := wski18n.T("Failed to parse arguments: {{.err}}", map[string]interface{}{"err": err})
237 | whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_GENERAL,
238 | whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
239 | return whiskErr
240 | }
241 |
242 | err = loadProperties()
243 | if err != nil {
244 | whisk.Debug(whisk.DbgError, "loadProperties() error: %s\n", err)
245 | errMsg := wski18n.T("Unable to access configuration properties: {{.err}}", map[string]interface{}{"err": err})
246 | whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_GENERAL,
247 | whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
248 | return whiskErr
249 | }
250 |
251 | return WskCmd.Execute()
252 | }
253 |
--------------------------------------------------------------------------------
/commands/commands_suite_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package commands
19 |
20 | import (
21 | . "github.com/onsi/ginkgo"
22 | . "github.com/onsi/gomega"
23 |
24 | "testing"
25 | )
26 |
27 | func TestCommands(t *testing.T) {
28 | RegisterFailHandler(Fail)
29 | RunSpecs(t, "Commands Suite")
30 | }
31 |
--------------------------------------------------------------------------------
/commands/flags.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package commands
19 |
20 | import (
21 | "os"
22 | )
23 |
24 | ///////////
25 | // Flags //
26 | ///////////
27 |
28 | const (
29 | MEMORY_FLAG = "memory"
30 | LOG_SIZE_FLAG = "logsize"
31 | CONCURRENCY_FLAG = "concurrency"
32 | TIMEOUT_FLAG = "timeout"
33 | WEB_FLAG = "web"
34 | WEB_SECURE_FLAG = "web-secure"
35 | SAVE_FLAG = "save"
36 | SAVE_AS_FLAG = "save-as"
37 | )
38 |
39 | var cliDebug = os.Getenv("WSK_CLI_DEBUG") // Useful for tracing init() code
40 |
41 | var Flags FlagsStruct
42 |
43 | type FlagsStruct struct {
44 | Global struct {
45 | Verbose bool
46 | Debug bool
47 | Cert string
48 | Key string
49 | Auth string
50 | Apihost string
51 | Apiversion string
52 | Insecure bool
53 | }
54 |
55 | common struct {
56 | blocking bool
57 | annotation []string
58 | annotFile string
59 | param []string
60 | paramFile string
61 | shared string // AKA "public" or "publish"
62 | skip int // skip first N records
63 | limit int // return max N records
64 | full bool // return full records (docs=true for client request)
65 | summary bool
66 | feed string // name of feed
67 | detail bool
68 | format string
69 | nameSort bool // sorts list alphabetically by entity name
70 | overwrite bool
71 | }
72 |
73 | property struct {
74 | cert bool
75 | key bool
76 | auth bool
77 | apihost bool
78 | apiversion bool
79 | namespace bool
80 | cliversion bool
81 | apibuild bool
82 | apibuildno bool
83 | insecure bool
84 | all bool
85 | apihostSet string
86 | apiversionSet string
87 | namespaceSet string
88 | output string
89 | }
90 |
91 | action ActionFlags
92 |
93 | activation struct {
94 | action string // retrieve results for this action
95 | upto int64 // retrieve results up to certain time
96 | since int64 // retrieve results after certain time
97 | seconds int // stop polling for activation upda
98 | sinceSeconds int
99 | sinceMinutes int
100 | sinceHours int
101 | sinceDays int
102 | exit int
103 | last bool
104 | strip bool
105 | logs bool
106 | }
107 |
108 | // rule
109 | rule struct {
110 | disable bool
111 | summary bool
112 | }
113 |
114 | // trigger
115 | trigger struct {
116 | summary bool
117 | feedParam []string
118 | triggerParam []string
119 | }
120 |
121 | //sdk
122 | sdk struct {
123 | stdout bool
124 | }
125 |
126 | // api
127 | api struct {
128 | action string
129 | path string
130 | verb string
131 | basepath string
132 | apiname string
133 | configfile string
134 | resptype string
135 | }
136 | }
137 |
138 | type ActionFlags struct {
139 | docker string
140 | native bool
141 | copy bool
142 | web string
143 | websecure string
144 | sequence bool
145 | timeout int
146 | memory int
147 | logsize int
148 | concurrency int
149 | result bool
150 | kind string
151 | main string
152 | url bool
153 | save bool
154 | saveAs string
155 | delAnnotation []string
156 | }
157 |
158 | func IsVerbose() bool {
159 | return Flags.Global.Verbose || IsDebug()
160 | }
161 |
162 | func IsDebug() bool {
163 | return len(cliDebug) > 0 || Flags.Global.Debug
164 | }
165 |
--------------------------------------------------------------------------------
/commands/messages.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package commands
19 |
20 | /**
21 | * Mapping from identifiers to message ids in wski18n resources.
22 | *
23 | * NOTE: this list is not complete as message will be moved incrementally.
24 | */
25 | const (
26 | FEED_CONFIGURATION_FAILURE = "FEED_CONFIGURATION_FAILURE"
27 | CMD_DESC_LONG_DEPLOY = "CMD_DESC_LONG_DEPLOY"
28 | CMD_DESC_LONG_SYNC = "CMD_DESC_LONG_SYNC"
29 | CMD_DESC_LONG_UNDEPLOY = "CMD_DESC_LONG_UNDEPLOY"
30 | CMD_DESC_LONG_EXPORT = "CMD_DESC_LONG_EXPORT"
31 | )
32 |
--------------------------------------------------------------------------------
/commands/namespace.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package commands
19 |
20 | import (
21 | "errors"
22 | "fmt"
23 |
24 | "github.com/fatih/color"
25 | "github.com/spf13/cobra"
26 |
27 | "github.com/apache/openwhisk-cli/wski18n"
28 | "github.com/apache/openwhisk-client-go/whisk"
29 | )
30 |
31 | // namespaceCmd represents the namespace command
32 | var namespaceCmd = &cobra.Command{
33 | Use: "namespace",
34 | Short: wski18n.T("work with namespaces"),
35 | }
36 |
37 | var namespaceListCmd = &cobra.Command{
38 | Use: "list",
39 | Short: wski18n.T("list available namespaces"),
40 | SilenceUsage: true,
41 | SilenceErrors: true,
42 | PreRunE: SetupClientConfig,
43 | RunE: func(cmd *cobra.Command, args []string) error {
44 | // add "TYPE" --> public / private
45 |
46 | if whiskErr := CheckArgs(args, 0, 0, "Namespace list", wski18n.T("No arguments are required.")); whiskErr != nil {
47 | return whiskErr
48 | }
49 |
50 | namespaces, _, err := Client.Namespaces.List()
51 | if err != nil {
52 | whisk.Debug(whisk.DbgError, "Client.Namespaces.List() error: %s\n", err)
53 | errStr := wski18n.T("Unable to obtain the list of available namespaces: {{.err}}",
54 | map[string]interface{}{"err": err})
55 | werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_NETWORK, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
56 | return werr
57 | }
58 | printList(namespaces, false) // `-n` flag applies to `namespace get`, not list, so must pass value false for printList here
59 | return nil
60 | },
61 | }
62 |
63 | var namespaceGetCmd = &cobra.Command{
64 | Use: "get",
65 | Short: wski18n.T("get triggers, actions, and rules in the registry for namespace"),
66 | SilenceUsage: true,
67 | SilenceErrors: true,
68 | PreRunE: SetupClientConfig,
69 | RunE: func(cmd *cobra.Command, args []string) error {
70 | var err error
71 | var namespace string = getClientNamespace()
72 |
73 | if !(len(args) == 1 && args[0] == "/_") {
74 | if whiskErr := CheckArgs(args, 0, 0, "Namespace get",
75 | wski18n.T("No arguments are required.")); whiskErr != nil {
76 | return whiskErr
77 | }
78 | }
79 |
80 | actions, _, err := Client.Actions.List("", &whisk.ActionListOptions{Skip: 0, Limit: 0})
81 | if err != nil {
82 | return entityListError(err, namespace, "Actions")
83 | }
84 |
85 | packages, _, err := Client.Packages.List(&whisk.PackageListOptions{Skip: 0, Limit: 0})
86 | if err != nil {
87 | return entityListError(err, namespace, "Packages")
88 | }
89 |
90 | triggers, _, err := Client.Triggers.List(&whisk.TriggerListOptions{Skip: 0, Limit: 0})
91 | if err != nil {
92 | return entityListError(err, namespace, "Triggers")
93 | }
94 |
95 | rules, _, err := Client.Rules.List(&whisk.RuleListOptions{Skip: 0, Limit: 0})
96 | if err != nil {
97 | return entityListError(err, namespace, "Rules")
98 | }
99 | //No errors, lets attempt to retrieve the status of each rule
100 | for index, rule := range rules {
101 | ruleStatus, _, err := Client.Rules.Get(rule.Name)
102 | if err != nil {
103 | errStr := wski18n.T("Unable to get status of rule '{{.name}}': {{.err}}",
104 | map[string]interface{}{"name": rule.Name, "err": err})
105 | fmt.Println(errStr)
106 | werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
107 | return werr
108 | }
109 | rules[index].Status = ruleStatus.Status
110 | }
111 |
112 | fmt.Fprintf(color.Output, wski18n.T("Entities in namespace: {{.namespace}}\n",
113 | map[string]interface{}{"namespace": boldString(getClientNamespace())}))
114 | sortByName := Flags.common.nameSort
115 | printList(packages, sortByName)
116 | printList(actions, sortByName)
117 | printList(triggers, sortByName)
118 | printList(rules, sortByName)
119 |
120 | return nil
121 | },
122 | }
123 |
124 | func init() {
125 | namespaceGetCmd.Flags().BoolVarP(&Flags.common.nameSort, "name-sort", "n", false, wski18n.T("sorts a list alphabetically by entity name; only applicable within the limit/skip returned entity block"))
126 |
127 | namespaceCmd.AddCommand(
128 | namespaceListCmd,
129 | namespaceGetCmd,
130 | )
131 | }
132 |
--------------------------------------------------------------------------------
/commands/project.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package commands
19 |
20 | import (
21 | "github.com/apache/openwhisk-cli/wski18n"
22 | "github.com/apache/openwhisk-wskdeploy/cmd"
23 | "github.com/apache/openwhisk-wskdeploy/utils"
24 | wskdeploy_wski18n "github.com/apache/openwhisk-wskdeploy/wski18n"
25 | "github.com/spf13/cobra"
26 | )
27 |
28 | var projectCmd = &cobra.Command{
29 | Use: "project",
30 | Short: "The OpenWhisk Project Management Tool",
31 | }
32 |
33 | var projectDeployCmd = &cobra.Command{
34 | Use: "deploy",
35 | Short: wski18n.T(wskdeploy_wski18n.ID_CMD_DESC_SHORT_ROOT),
36 | Long: wski18n.T(CMD_DESC_LONG_DEPLOY),
37 | SilenceUsage: true,
38 | SilenceErrors: true,
39 | RunE: func(cobraCMD *cobra.Command, args []string) error {
40 | return cmd.Deploy(cobraCMD)
41 | },
42 | }
43 |
44 | var projectUnDeployCmd = &cobra.Command{
45 | Use: "undeploy",
46 | Short: wski18n.T(wskdeploy_wski18n.ID_CMD_DESC_SHORT_UNDEPLOY),
47 | Long: wski18n.T(CMD_DESC_LONG_UNDEPLOY),
48 | SilenceUsage: true,
49 | SilenceErrors: true,
50 | RunE: func(cobraCMD *cobra.Command, args []string) error {
51 | return cmd.Undeploy(cobraCMD)
52 | },
53 | }
54 |
55 | var projectSyncCmd = &cobra.Command{
56 | Use: "sync",
57 | Short: wski18n.T(wskdeploy_wski18n.ID_CMD_DESC_SHORT_SYNC),
58 | Long: wski18n.T(CMD_DESC_LONG_SYNC),
59 | SilenceUsage: true,
60 | SilenceErrors: true,
61 | RunE: func(cobraCMD *cobra.Command, args []string) error {
62 | utils.Flags.Sync = true
63 | return cmd.Deploy(cobraCMD)
64 | },
65 | }
66 |
67 | var projectExportCmd = &cobra.Command{
68 | Use: "export",
69 | Short: wski18n.T(wskdeploy_wski18n.ID_CMD_DESC_SHORT_EXPORT),
70 | Long: wski18n.T(CMD_DESC_LONG_EXPORT),
71 | SilenceUsage: true,
72 | SilenceErrors: true,
73 | RunE: func(cobraCMD *cobra.Command, args []string) error {
74 | return cmd.ExportCmdImp(cobraCMD, args)
75 | },
76 | }
77 |
78 | func init() {
79 | projectCmd.PersistentFlags().StringVar(&utils.Flags.CfgFile, cmd.FLAG_CONFIG, "", wski18n.T(wskdeploy_wski18n.ID_CMD_FLAG_CONFIG))
80 | projectCmd.PersistentFlags().StringVarP(&utils.Flags.ManifestPath, cmd.FLAG_MANIFEST, "", "", wski18n.T(wskdeploy_wski18n.ID_CMD_FLAG_MANIFEST))
81 | projectCmd.PersistentFlags().StringVarP(&utils.Flags.ProjectPath, cmd.FLAG_PROJECT, "", ".", wski18n.T(wskdeploy_wski18n.ID_CMD_FLAG_PROJECT))
82 | projectCmd.PersistentFlags().StringVarP(&utils.Flags.DeploymentPath, cmd.FLAG_DEPLOYMENT, "", "", wski18n.T(wskdeploy_wski18n.ID_CMD_FLAG_DEPLOYMENT))
83 | projectCmd.PersistentFlags().BoolVarP(&utils.Flags.Strict, cmd.FLAG_STRICT, "", false, wski18n.T(wskdeploy_wski18n.ID_CMD_FLAG_STRICT))
84 | projectCmd.PersistentFlags().BoolVarP(&utils.Flags.Preview, cmd.FLAG_PREVIEW, "", false, wski18n.T(wskdeploy_wski18n.ID_CMD_FLAG_PREVIEW))
85 | projectCmd.PersistentFlags().StringSliceVarP(&utils.Flags.Param, cmd.FLAG_PARAM, "", []string{}, wski18n.T(wskdeploy_wski18n.ID_CMD_FLAG_PARAM))
86 | projectCmd.PersistentFlags().StringVarP(&utils.Flags.ParamFile, cmd.FLAG_PARAMFILE, "", "", wski18n.T(wskdeploy_wski18n.ID_CMD_FLAG_PARAM_FILE))
87 | projectCmd.PersistentFlags().StringVarP(&utils.Flags.ApiHost, cmd.FLAG_API_HOST, "", "", wski18n.T(wskdeploy_wski18n.ID_CMD_FLAG_API_HOST))
88 | projectCmd.PersistentFlags().StringVarP(&utils.Flags.Namespace, cmd.FLAG_NAMESPACE, cmd.FLAG_NAMESPACE_SHORT, "", wski18n.T(wskdeploy_wski18n.ID_CMD_FLAG_NAMESPACE))
89 | projectCmd.PersistentFlags().StringVarP(&utils.Flags.Auth, cmd.FLAG_AUTH, cmd.FLAG_AUTH_SHORT, "", wski18n.T(wskdeploy_wski18n.ID_CMD_FLAG_AUTH_KEY))
90 | projectCmd.PersistentFlags().BoolVarP(&utils.Flags.Verbose, cmd.FLAG_VERBOSE, cmd.FLAG_VERBOSE_SHORT, false, wski18n.T(wskdeploy_wski18n.ID_CMD_FLAG_VERBOSE))
91 | projectCmd.PersistentFlags().StringVarP(&utils.Flags.Key, cmd.FLAG_KEY, cmd.FLAG_KEY_SHORT, "", wski18n.T(wskdeploy_wski18n.ID_CMD_FLAG_KEY_FILE))
92 | projectCmd.PersistentFlags().StringVarP(&utils.Flags.Cert, cmd.FLAG_CERT, cmd.FLAG_CERT_SHORT, "", wski18n.T(wskdeploy_wski18n.ID_CMD_FLAG_CERT_FILE))
93 | projectCmd.PersistentFlags().StringVarP(&utils.Flags.ProjectName, cmd.FLAG_PROJECTNAME, "", "", wski18n.T(wskdeploy_wski18n.ID_CMD_FLAG_PROJECTNAME))
94 |
95 | projectCmd.AddCommand(projectDeployCmd)
96 | projectCmd.AddCommand(projectUnDeployCmd)
97 | projectCmd.AddCommand(projectSyncCmd)
98 | projectCmd.AddCommand(projectExportCmd)
99 | }
100 |
--------------------------------------------------------------------------------
/commands/qualified_name.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package commands
19 |
20 | import (
21 | "errors"
22 | "fmt"
23 | "github.com/apache/openwhisk-cli/wski18n"
24 | "github.com/apache/openwhisk-client-go/whisk"
25 | "strings"
26 | )
27 |
28 | type QualifiedName struct {
29 | namespace string // namespace. does not include leading '/'. may be "" (i.e. default namespace)
30 | packageName string // package. may be "". does not include leading/trailing '/'
31 | entity string // entity. should not be ""
32 | EntityName string // pkg+entity
33 | }
34 |
35 | ///////////////////////////
36 | // QualifiedName Methods //
37 | ///////////////////////////
38 |
39 | // GetFullQualifiedName() returns a full qualified name in proper string format
40 | // from qualifiedName with proper syntax.
41 | // Example: /namespace/[package/]entity
42 | func (qualifiedName *QualifiedName) GetFullQualifiedName() string {
43 | output := []string{}
44 |
45 | if len(qualifiedName.GetNamespace()) > 0 {
46 | output = append(output, "/", qualifiedName.GetNamespace(), "/")
47 | }
48 | if len(qualifiedName.GetPackageName()) > 0 {
49 | output = append(output, qualifiedName.GetPackageName(), "/")
50 | }
51 | output = append(output, qualifiedName.GetEntity())
52 |
53 | return strings.Join(output, "")
54 | }
55 |
56 | // GetPackageName() returns the package name from qualifiedName without a
57 | // leading '/'
58 | func (qualifiedName *QualifiedName) GetPackageName() string {
59 | return qualifiedName.packageName
60 | }
61 |
62 | // GetEntityName() returns the entity name ([package/]entity) of qualifiedName
63 | // without a leading '/'
64 | func (qualifiedName *QualifiedName) GetEntityName() string {
65 | return qualifiedName.EntityName
66 | }
67 |
68 | // GetEntity() returns the name of entity in qualifiedName without a leading '/'
69 | func (qualifiedName *QualifiedName) GetEntity() string {
70 | return qualifiedName.entity
71 | }
72 |
73 | // GetNamespace() returns the name of the namespace in qualifiedName without
74 | // a leading '/'
75 | func (qualifiedName *QualifiedName) GetNamespace() string {
76 | return qualifiedName.namespace
77 | }
78 |
79 | // NewQualifiedName(name) initializes and constructs a (possibly fully qualified)
80 | // QualifiedName struct.
81 | //
82 | // NOTE: If the given qualified name is None, then this is a default qualified
83 | // name and it is resolved from properties.
84 | // NOTE: If the namespace is missing from the qualified name, the namespace
85 | // is also resolved from the property file.
86 | //
87 | // Examples:
88 | // foo => qualifiedName {namespace: "_", entityName: foo}
89 | // pkg/foo => qualifiedName {namespace: "_", entityName: pkg/foo}
90 | // /ns/foo => qualifiedName {namespace: ns, entityName: foo}
91 | // /ns/pkg/foo => qualifiedName {namespace: ns, entityName: pkg/foo}
92 | func NewQualifiedName(name string) (*QualifiedName, error) {
93 | qualifiedName := new(QualifiedName)
94 |
95 | // If name has a preceding delimiter (/), or if it has two delimiters with a
96 | // leading non-empty string, then it contains a namespace. Otherwise the name
97 | // does not specify a namespace, so default the namespace to the namespace
98 | // value set in the properties file; if that is not set, use "_"
99 | name = addLeadSlash(name)
100 | parts := strings.Split(name, "/")
101 | if strings.HasPrefix(name, "/") {
102 | qualifiedName.namespace = parts[1]
103 |
104 | if len(parts) < 2 || len(parts) > 4 {
105 | return qualifiedName, qualifiedNameNotSpecifiedErr()
106 | }
107 |
108 | for i := 1; i < len(parts); i++ {
109 | if len(parts[i]) == 0 || parts[i] == "." {
110 | return qualifiedName, qualifiedNameNotSpecifiedErr()
111 | }
112 | }
113 |
114 | qualifiedName.EntityName = strings.Join(parts[2:], "/")
115 | if len(parts) == 4 {
116 | qualifiedName.packageName = parts[2]
117 | }
118 | qualifiedName.entity = parts[len(parts)-1]
119 | } else {
120 | if len(name) == 0 || name == "." {
121 | return qualifiedName, qualifiedNameNotSpecifiedErr()
122 | }
123 |
124 | qualifiedName.entity = parts[len(parts)-1]
125 | if len(parts) == 2 {
126 | qualifiedName.packageName = parts[0]
127 | }
128 | qualifiedName.EntityName = name
129 | qualifiedName.namespace = getNamespaceFromProp()
130 | }
131 |
132 | whisk.Debug(whisk.DbgInfo, "Qualified pkg+entity (EntityName): %s\n", qualifiedName.GetEntityName())
133 | whisk.Debug(whisk.DbgInfo, "Qualified namespace: %s\n", qualifiedName.GetNamespace())
134 | whisk.Debug(whisk.DbgInfo, "Qualified package: %s\n", qualifiedName.GetPackageName())
135 | whisk.Debug(whisk.DbgInfo, "Qualified entity: %s\n", qualifiedName.GetEntity())
136 |
137 | return qualifiedName, nil
138 | }
139 |
140 | /////////////////////
141 | // Error Functions //
142 | /////////////////////
143 |
144 | // qualifiedNameNotSpecifiedErr() returns generic whisk error for
145 | // invalid qualified names detected while building a new
146 | // QualifiedName struct.
147 | func qualifiedNameNotSpecifiedErr() error {
148 | whisk.Debug(whisk.DbgError, "A valid qualified name was not detected\n")
149 | errStr := wski18n.T("A valid qualified name must be specified.")
150 | return whisk.MakeWskError(errors.New(errStr), whisk.NOT_ALLOWED, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
151 | }
152 |
153 | // NewQualifiedNameError(entityName, err) returns specific whisk error
154 | // for invalid qualified names.
155 | func NewQualifiedNameError(entityName string, err error) error {
156 | whisk.Debug(whisk.DbgError, "NewQualifiedName(%s) failed: %s\n", entityName, err)
157 |
158 | errMsg := wski18n.T(
159 | "'{{.name}}' is not a valid qualified name: {{.err}}",
160 | map[string]interface{}{
161 | "name": entityName,
162 | "err": err,
163 | })
164 |
165 | return whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
166 | }
167 |
168 | ///////////////////////////
169 | // Helper/Misc Functions //
170 | ///////////////////////////
171 |
172 | // addLeadSlash(name) returns a (possibly fully qualified) resource name,
173 | // inserting a leading '/' if it is of 3 parts (namespace/package/action)
174 | // and lacking the leading '/'.
175 | func addLeadSlash(name string) string {
176 | parts := strings.Split(name, "/")
177 | if len(parts) == 3 && parts[0] != "" {
178 | name = "/" + name
179 | }
180 | return name
181 | }
182 |
183 | // getNamespaceFromProp() returns a namespace from Properties if one exists,
184 | // else defaults to returning "_"
185 | func getNamespaceFromProp() string {
186 | namespace := "_"
187 |
188 | if Properties.Namespace != "" {
189 | namespace = Properties.Namespace
190 | }
191 |
192 | return namespace
193 | }
194 |
195 | // getQualifiedName(name, namespace) returns a fully qualified name given a
196 | // (possibly fully qualified) resource name and optional namespace.
197 | //
198 | // Examples:
199 | // (foo, None) => /_/foo
200 | // (pkg/foo, None) => /_/pkg/foo
201 | // (foo, ns) => /ns/foo
202 | // (/ns/pkg/foo, None) => /ns/pkg/foo
203 | // (/ns/pkg/foo, otherns) => /ns/pkg/foo
204 | func getQualifiedName(name string, namespace string) string {
205 | name = addLeadSlash(name)
206 | if strings.HasPrefix(name, "/") {
207 | return name
208 | } else if strings.HasPrefix(namespace, "/") {
209 | return fmt.Sprintf("%s/%s", namespace, name)
210 | } else {
211 | if len(namespace) == 0 {
212 | namespace = Properties.Namespace
213 | }
214 | return fmt.Sprintf("/%s/%s", namespace, name)
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/commands/rule.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package commands
19 |
20 | import (
21 | "errors"
22 | "fmt"
23 |
24 | "github.com/apache/openwhisk-cli/wski18n"
25 | "github.com/apache/openwhisk-client-go/whisk"
26 |
27 | "github.com/fatih/color"
28 | "github.com/spf13/cobra"
29 | )
30 |
31 | // ruleCmd represents the rule command
32 | var ruleCmd = &cobra.Command{
33 | Use: "rule",
34 | Short: wski18n.T("work with rules"),
35 | }
36 |
37 | var ruleEnableCmd = &cobra.Command{
38 | Use: "enable RULE_NAME",
39 | Short: wski18n.T("enable rule"),
40 | SilenceUsage: true,
41 | SilenceErrors: true,
42 | PreRunE: SetupClientConfig,
43 | RunE: func(cmd *cobra.Command, args []string) error {
44 | var err error
45 | var qualifiedName = new(QualifiedName)
46 |
47 | if whiskErr := CheckArgs(args, 1, 1, "Rule enable", wski18n.T("A rule name is required.")); whiskErr != nil {
48 | return whiskErr
49 | }
50 |
51 | if qualifiedName, err = NewQualifiedName(args[0]); err != nil {
52 | return NewQualifiedNameError(args[0], err)
53 | }
54 |
55 | Client.Namespace = qualifiedName.GetNamespace()
56 | ruleName := qualifiedName.GetEntityName()
57 |
58 | _, _, err = Client.Rules.SetState(ruleName, "active")
59 | if err != nil {
60 | whisk.Debug(whisk.DbgError, "Client.Rules.SetState(%s, active) failed: %s\n", ruleName, err)
61 | errStr := wski18n.T("Unable to enable rule '{{.name}}': {{.err}}",
62 | map[string]interface{}{"name": ruleName, "err": err})
63 | werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
64 | return werr
65 | }
66 |
67 | fmt.Fprintf(color.Output,
68 | wski18n.T("{{.ok}} enabled rule {{.name}}\n",
69 | map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName)}))
70 | return nil
71 | },
72 | }
73 |
74 | var ruleDisableCmd = &cobra.Command{
75 | Use: "disable RULE_NAME",
76 | Short: wski18n.T("disable rule"),
77 | SilenceUsage: true,
78 | SilenceErrors: true,
79 | PreRunE: SetupClientConfig,
80 | RunE: func(cmd *cobra.Command, args []string) error {
81 | var err error
82 | var qualifiedName = new(QualifiedName)
83 |
84 | if whiskErr := CheckArgs(args, 1, 1, "Rule disable", wski18n.T("A rule name is required.")); whiskErr != nil {
85 | return whiskErr
86 | }
87 |
88 | if qualifiedName, err = NewQualifiedName(args[0]); err != nil {
89 | return NewQualifiedNameError(args[0], err)
90 | }
91 |
92 | Client.Namespace = qualifiedName.GetNamespace()
93 | ruleName := qualifiedName.GetEntityName()
94 |
95 | _, _, err = Client.Rules.SetState(ruleName, "inactive")
96 | if err != nil {
97 | whisk.Debug(whisk.DbgError, "Client.Rules.SetState(%s, inactive) failed: %s\n", ruleName, err)
98 | errStr := wski18n.T("Unable to disable rule '{{.name}}': {{.err}}",
99 | map[string]interface{}{"name": ruleName, "err": err})
100 | werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
101 | return werr
102 | }
103 |
104 | fmt.Fprintf(color.Output,
105 | wski18n.T("{{.ok}} disabled rule {{.name}}\n",
106 | map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName)}))
107 | return nil
108 | },
109 | }
110 |
111 | var ruleStatusCmd = &cobra.Command{
112 | Use: "status RULE_NAME",
113 | Short: wski18n.T("get rule status"),
114 | SilenceUsage: true,
115 | SilenceErrors: true,
116 | PreRunE: SetupClientConfig,
117 | RunE: func(cmd *cobra.Command, args []string) error {
118 | var err error
119 | var qualifiedName = new(QualifiedName)
120 |
121 | if whiskErr := CheckArgs(args, 1, 1, "Rule status", wski18n.T("A rule name is required.")); whiskErr != nil {
122 | return whiskErr
123 | }
124 |
125 | if qualifiedName, err = NewQualifiedName(args[0]); err != nil {
126 | return NewQualifiedNameError(args[0], err)
127 | }
128 |
129 | Client.Namespace = qualifiedName.GetNamespace()
130 | ruleName := qualifiedName.GetEntityName()
131 |
132 | rule, _, err := Client.Rules.Get(ruleName)
133 | if err != nil {
134 | whisk.Debug(whisk.DbgError, "Client.Rules.Get(%s) failed: %s\n", ruleName, err)
135 | errStr := wski18n.T("Unable to get status of rule '{{.name}}': {{.err}}",
136 | map[string]interface{}{"name": ruleName, "err": err})
137 | werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
138 | return werr
139 | }
140 |
141 | fmt.Fprintf(color.Output,
142 | wski18n.T("{{.ok}} rule {{.name}} is {{.status}}\n",
143 | map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName), "status": boldString(rule.Status)}))
144 | return nil
145 | },
146 | }
147 |
148 | var ruleCreateCmd = &cobra.Command{
149 | Use: "create RULE_NAME TRIGGER_NAME ACTION_NAME",
150 | Short: wski18n.T("create new rule"),
151 | SilenceUsage: true,
152 | SilenceErrors: true,
153 | PreRunE: SetupClientConfig,
154 | RunE: func(cmd *cobra.Command, args []string) error {
155 | var err error
156 | var qualifiedName = new(QualifiedName)
157 |
158 | if whiskErr := CheckArgs(args, 3, 3, "Rule create",
159 | wski18n.T("A rule, trigger and action name are required.")); whiskErr != nil {
160 | return whiskErr
161 | }
162 |
163 | if qualifiedName, err = NewQualifiedName(args[0]); err != nil {
164 | return NewQualifiedNameError(args[0], err)
165 | }
166 |
167 | Client.Namespace = qualifiedName.GetNamespace()
168 | ruleName := qualifiedName.GetEntityName()
169 | triggerName := getQualifiedName(args[1], Properties.Namespace)
170 | actionName := getQualifiedName(args[2], Properties.Namespace)
171 |
172 | rule := &whisk.Rule{
173 | Name: ruleName,
174 | Trigger: triggerName,
175 | Action: actionName,
176 | }
177 |
178 | whisk.Debug(whisk.DbgInfo, "Inserting rule:\n%+v\n", rule)
179 | var retRule *whisk.Rule
180 | retRule, _, err = Client.Rules.Insert(rule, false)
181 | if err != nil {
182 | whisk.Debug(whisk.DbgError, "Client.Rules.Insert(%#v) failed: %s\n", rule, err)
183 | errStr := wski18n.T("Unable to create rule '{{.name}}': {{.err}}",
184 | map[string]interface{}{"name": ruleName, "err": err})
185 | werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
186 | return werr
187 | }
188 | whisk.Debug(whisk.DbgInfo, "Inserted rule:\n%+v\n", retRule)
189 |
190 | fmt.Fprintf(color.Output,
191 | wski18n.T("{{.ok}} created rule {{.name}}\n",
192 | map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName)}))
193 | return nil
194 | },
195 | }
196 |
197 | var ruleUpdateCmd = &cobra.Command{
198 | Use: "update RULE_NAME TRIGGER_NAME ACTION_NAME",
199 | Short: wski18n.T("update an existing rule, or create a rule if it does not exist"),
200 | SilenceUsage: true,
201 | SilenceErrors: true,
202 | PreRunE: SetupClientConfig,
203 | RunE: func(cmd *cobra.Command, args []string) error {
204 | var err error
205 | var qualifiedName = new(QualifiedName)
206 |
207 | if whiskErr := CheckArgs(args, 3, 3, "Rule update",
208 | wski18n.T("A rule, trigger and action name are required.")); whiskErr != nil {
209 | return whiskErr
210 | }
211 |
212 | if qualifiedName, err = NewQualifiedName(args[0]); err != nil {
213 | return NewQualifiedNameError(args[0], err)
214 | }
215 |
216 | Client.Namespace = qualifiedName.GetNamespace()
217 | ruleName := qualifiedName.GetEntityName()
218 | triggerName := getQualifiedName(args[1], Properties.Namespace)
219 | actionName := getQualifiedName(args[2], Properties.Namespace)
220 |
221 | rule := &whisk.Rule{
222 | Name: ruleName,
223 | Trigger: triggerName,
224 | Action: actionName,
225 | }
226 |
227 | _, _, err = Client.Rules.Insert(rule, true)
228 | if err != nil {
229 | whisk.Debug(whisk.DbgError, "Client.Rules.Insert(%#v) failed: %s\n", rule, err)
230 | errStr := wski18n.T("Unable to update rule '{{.name}}': {{.err}}",
231 | map[string]interface{}{"name": rule.Name, "err": err})
232 | werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
233 | return werr
234 | }
235 |
236 | fmt.Fprintf(color.Output,
237 | wski18n.T("{{.ok}} updated rule {{.name}}\n",
238 | map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName)}))
239 | return nil
240 | },
241 | }
242 |
243 | var ruleGetCmd = &cobra.Command{
244 | Use: "get RULE_NAME",
245 | Short: wski18n.T("get rule"),
246 | SilenceUsage: true,
247 | SilenceErrors: true,
248 | PreRunE: SetupClientConfig,
249 | RunE: func(cmd *cobra.Command, args []string) error {
250 | var err error
251 | var field string
252 | var qualifiedName = new(QualifiedName)
253 |
254 | if whiskErr := CheckArgs(args, 1, 2, "Rule get", wski18n.T("A rule name is required.")); whiskErr != nil {
255 | return whiskErr
256 | }
257 |
258 | if len(args) > 1 {
259 | field = args[1]
260 |
261 | if !fieldExists(&whisk.Rule{}, field) {
262 | errMsg := wski18n.T("Invalid field filter '{{.arg}}'.", map[string]interface{}{"arg": field})
263 | whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
264 | whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
265 | return whiskErr
266 | }
267 | }
268 |
269 | if qualifiedName, err = NewQualifiedName(args[0]); err != nil {
270 | return NewQualifiedNameError(args[0], err)
271 | }
272 |
273 | Client.Namespace = qualifiedName.GetNamespace()
274 | ruleName := qualifiedName.GetEntityName()
275 |
276 | rule, _, err := Client.Rules.Get(ruleName)
277 | if err != nil {
278 | whisk.Debug(whisk.DbgError, "Client.Rules.Get(%s) failed: %s\n", ruleName, err)
279 | errStr := wski18n.T("Unable to get rule '{{.name}}': {{.err}}",
280 | map[string]interface{}{"name": ruleName, "err": err})
281 | werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
282 | return werr
283 | }
284 |
285 | if Flags.rule.summary {
286 | printRuleSummary(rule)
287 | } else {
288 | if len(field) > 0 {
289 | fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got rule {{.name}}, displaying field {{.field}}\n",
290 | map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName),
291 | "field": field}))
292 | printField(rule, field)
293 | } else {
294 | fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got rule {{.name}}\n",
295 | map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName)}))
296 | printJSON(rule)
297 | }
298 | }
299 |
300 | return nil
301 | },
302 | }
303 |
304 | var ruleDeleteCmd = &cobra.Command{
305 | Use: "delete RULE_NAME",
306 | Short: wski18n.T("delete rule"),
307 | SilenceUsage: true,
308 | SilenceErrors: true,
309 | PreRunE: SetupClientConfig,
310 | RunE: func(cmd *cobra.Command, args []string) error {
311 | var err error
312 | var qualifiedName = new(QualifiedName)
313 |
314 | if whiskErr := CheckArgs(args, 1, 1, "Rule delete", wski18n.T("A rule name is required.")); whiskErr != nil {
315 | return whiskErr
316 | }
317 |
318 | if qualifiedName, err = NewQualifiedName(args[0]); err != nil {
319 | return NewQualifiedNameError(args[0], err)
320 | }
321 |
322 | Client.Namespace = qualifiedName.GetNamespace()
323 | ruleName := qualifiedName.GetEntityName()
324 |
325 | if Flags.rule.disable {
326 | _, _, err := Client.Rules.SetState(ruleName, "inactive")
327 | if err != nil {
328 | whisk.Debug(whisk.DbgError, "Client.Rules.SetState(%s, inactive) failed: %s\n", ruleName, err)
329 | errStr := wski18n.T("Unable to disable rule '{{.name}}': {{.err}}",
330 | map[string]interface{}{"name": ruleName, "err": err})
331 | werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
332 | return werr
333 | }
334 | }
335 |
336 | _, err = Client.Rules.Delete(ruleName)
337 | if err != nil {
338 | whisk.Debug(whisk.DbgError, "Client.Rules.Delete(%s) error: %s\n", ruleName, err)
339 | errStr := wski18n.T("Unable to delete rule '{{.name}}': {{.err}}",
340 | map[string]interface{}{"name": ruleName, "err": err})
341 | werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
342 | return werr
343 | }
344 |
345 | fmt.Fprintf(color.Output,
346 | wski18n.T("{{.ok}} deleted rule {{.name}}\n",
347 | map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName)}))
348 | return nil
349 | },
350 | }
351 |
352 | var ruleListCmd = &cobra.Command{
353 | Use: "list [NAMESPACE]",
354 | Short: wski18n.T("list all rules"),
355 | SilenceUsage: true,
356 | SilenceErrors: true,
357 | PreRunE: SetupClientConfig,
358 | RunE: func(cmd *cobra.Command, args []string) error {
359 | var err error
360 | var qualifiedName = new(QualifiedName)
361 |
362 | if whiskErr := CheckArgs(args, 0, 1, "Rule list",
363 | wski18n.T("An optional namespace is the only valid argument.")); whiskErr != nil {
364 | return whiskErr
365 | }
366 |
367 | if len(args) == 1 {
368 | if qualifiedName, err = NewQualifiedName(args[0]); err != nil {
369 | return NewQualifiedNameError(args[0], err)
370 | }
371 |
372 | if len(qualifiedName.GetEntityName()) > 0 {
373 | return entityNameError(qualifiedName.GetEntityName())
374 | }
375 |
376 | Client.Namespace = qualifiedName.GetNamespace()
377 | }
378 |
379 | ruleListOptions := &whisk.RuleListOptions{
380 | Skip: Flags.common.skip,
381 | Limit: Flags.common.limit,
382 | }
383 |
384 | rules, _, err := Client.Rules.List(ruleListOptions)
385 | if err != nil {
386 | whisk.Debug(whisk.DbgError, "Client.Rules.List(%#v) error: %s\n", ruleListOptions, err)
387 | errStr := wski18n.T("Unable to obtain the list of rules for namespace '{{.name}}': {{.err}}",
388 | map[string]interface{}{"name": getClientNamespace(), "err": err})
389 | werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
390 | return werr
391 | } else {
392 | //No errors, lets attempt to retrieve the status of each rule #312
393 | for index, rule := range rules {
394 | ruleStatus, _, err := Client.Rules.Get(rule.Name)
395 | if err != nil {
396 | errStr := wski18n.T("Unable to get status of rule '{{.name}}': {{.err}}",
397 | map[string]interface{}{"name": rule.Name, "err": err})
398 | fmt.Println(errStr)
399 | werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
400 | return werr
401 | }
402 | rules[index].Status = ruleStatus.Status
403 | }
404 | }
405 |
406 | sortByName := Flags.common.nameSort
407 | printList(rules, sortByName)
408 | return nil
409 | },
410 | }
411 |
412 | func init() {
413 | ruleDeleteCmd.Flags().BoolVar(&Flags.rule.disable, "disable", false, wski18n.T("automatically disable rule before deleting it"))
414 |
415 | ruleGetCmd.Flags().BoolVarP(&Flags.rule.summary, "summary", "s", false, wski18n.T("summarize rule details"))
416 |
417 | ruleListCmd.Flags().IntVarP(&Flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of rules from the result"))
418 | ruleListCmd.Flags().IntVarP(&Flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of rules from the collection"))
419 | ruleListCmd.Flags().BoolVarP(&Flags.common.nameSort, "name-sort", "n", false, wski18n.T("sorts a list alphabetically by entity name; only applicable within the limit/skip returned entity block"))
420 |
421 | ruleCmd.AddCommand(
422 | ruleCreateCmd,
423 | ruleEnableCmd,
424 | ruleDisableCmd,
425 | ruleStatusCmd,
426 | ruleUpdateCmd,
427 | ruleGetCmd,
428 | ruleDeleteCmd,
429 | ruleListCmd,
430 | )
431 |
432 | }
433 |
--------------------------------------------------------------------------------
/commands/sdk.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package commands
19 |
20 | import (
21 | "errors"
22 | "fmt"
23 | "io"
24 | "os"
25 | "strings"
26 |
27 | "github.com/spf13/cobra"
28 |
29 | "github.com/apache/openwhisk-cli/wski18n"
30 | "github.com/apache/openwhisk-client-go/whisk"
31 | )
32 |
33 | // sdkCmd represents the sdk command
34 | var sdkCmd = &cobra.Command{
35 | Use: "sdk",
36 | Short: wski18n.T("work with the sdk"),
37 | }
38 |
39 | type sdkInfo struct {
40 | UrlPath string
41 | FileName string
42 | isGzTar bool
43 | IsGzip bool
44 | IsZip bool
45 | IsTar bool
46 | Unpack bool
47 | UnpackDir string
48 | }
49 |
50 | var sdkMap map[string]*sdkInfo
51 |
52 | const SDK_DOCKER_COMPONENT_NAME string = "docker"
53 | const SDK_IOS_COMPONENT_NAME string = "ios"
54 | const BASH_AUTOCOMPLETE_FILENAME string = "wsk_cli_bash_completion.sh"
55 |
56 | var sdkInstallCmd = &cobra.Command{
57 | Use: "install COMPONENT",
58 | Short: wski18n.T("install SDK artifacts"),
59 | Long: wski18n.T("install SDK artifacts, where valid COMPONENT values are docker, ios, and bashauto"),
60 | SilenceUsage: true,
61 | SilenceErrors: true,
62 | PreRunE: SetupClientConfig,
63 | RunE: func(cmd *cobra.Command, args []string) error {
64 | var err error
65 | if len(args) != 1 {
66 | whisk.Debug(whisk.DbgError, "Invalid number of arguments: %d\n", len(args))
67 | errStr := wski18n.T("The SDK component argument is missing. One component (docker, ios, or bashauto) must be specified")
68 | werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
69 | return werr
70 | }
71 | component := strings.ToLower(args[0])
72 | switch component {
73 | case "docker":
74 | err = dockerInstall()
75 | case "ios":
76 | err = iOSInstall()
77 | case "bashauto":
78 | if Flags.sdk.stdout {
79 | if err = WskCmd.GenBashCompletion(os.Stdout); err != nil {
80 | whisk.Debug(whisk.DbgError, "GenBashCompletion error: %s\n", err)
81 | errStr := wski18n.T("Unable to output bash command completion {{.err}}",
82 | map[string]interface{}{"err": err})
83 | werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
84 | return werr
85 | }
86 | } else {
87 | err = WskCmd.GenBashCompletionFile(BASH_AUTOCOMPLETE_FILENAME)
88 | if err != nil {
89 | whisk.Debug(whisk.DbgError, "GenBashCompletionFile('%s`) error: %s\n", BASH_AUTOCOMPLETE_FILENAME, err)
90 | errStr := wski18n.T("Unable to generate '{{.name}}': {{.err}}",
91 | map[string]interface{}{"name": BASH_AUTOCOMPLETE_FILENAME, "err": err})
92 | werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
93 | return werr
94 | }
95 | fmt.Printf(
96 | wski18n.T("bash_completion_msg",
97 | map[string]interface{}{"name": BASH_AUTOCOMPLETE_FILENAME}))
98 | }
99 | default:
100 | whisk.Debug(whisk.DbgError, "Invalid component argument '%s'\n", component)
101 | errStr := wski18n.T("The SDK component argument '{{.component}}' is invalid. Valid components are docker, ios and bashauto",
102 | map[string]interface{}{"component": component})
103 | err = whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
104 | }
105 |
106 | if err != nil {
107 | return err
108 | }
109 | return nil
110 | },
111 | }
112 |
113 | func dockerInstall() error {
114 | var err error
115 |
116 | targetFile := sdkMap[SDK_DOCKER_COMPONENT_NAME].FileName
117 | if _, err = os.Stat(targetFile); err == nil {
118 | whisk.Debug(whisk.DbgError, "os.Stat reports file '%s' exists\n", targetFile)
119 | errStr := wski18n.T("The file '{{.name}}' already exists. Delete it and retry.",
120 | map[string]interface{}{"name": targetFile})
121 | werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
122 | return werr
123 | }
124 |
125 | if err = sdkInstall(SDK_DOCKER_COMPONENT_NAME); err != nil {
126 | whisk.Debug(whisk.DbgError, "sdkInstall(%s) failed: %s\n", SDK_DOCKER_COMPONENT_NAME, err)
127 | errStr := wski18n.T("The {{.component}} SDK installation failed: {{.err}}",
128 | map[string]interface{}{"component": SDK_DOCKER_COMPONENT_NAME, "err": err})
129 | werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
130 | return werr
131 | }
132 |
133 | fmt.Println(wski18n.T("The docker skeleton is now installed at the current directory."))
134 | return nil
135 | }
136 |
137 | func iOSInstall() error {
138 | var err error
139 |
140 | if err = sdkInstall(SDK_IOS_COMPONENT_NAME); err != nil {
141 | whisk.Debug(whisk.DbgError, "sdkInstall(%s) failed: %s\n", SDK_IOS_COMPONENT_NAME, err)
142 | errStr := wski18n.T("The {{.component}} SDK installation failed: {{.err}}",
143 | map[string]interface{}{"component": SDK_IOS_COMPONENT_NAME, "err": err})
144 | werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
145 | return werr
146 | }
147 |
148 | fmt.Printf(
149 | wski18n.T("Downloaded OpenWhisk iOS starter app. Unzip '{{.name}}' and open the project in Xcode.\n",
150 | map[string]interface{}{"name": sdkMap[SDK_IOS_COMPONENT_NAME].FileName}))
151 | return nil
152 | }
153 |
154 | func sdkInstall(componentName string) error {
155 | targetFile := sdkMap[componentName].FileName
156 | if _, err := os.Stat(targetFile); err == nil {
157 | whisk.Debug(whisk.DbgError, "os.Stat reports file '%s' exists\n", targetFile)
158 | errStr := wski18n.T("The file '{{.name}}' already exists. Delete it and retry.",
159 | map[string]interface{}{"name": targetFile})
160 | werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
161 | return werr
162 | }
163 |
164 | resp, err := Client.Sdks.Install(sdkMap[componentName].UrlPath)
165 | if err != nil {
166 | whisk.Debug(whisk.DbgError, "Client.Sdks.Install(%s) failed: %s\n", sdkMap[componentName].UrlPath, err)
167 | errStr := wski18n.T("Unable to retrieve '{{.urlpath}}' SDK: {{.err}}",
168 | map[string]interface{}{"urlpath": sdkMap[componentName].UrlPath, "err": err})
169 | werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
170 | return werr
171 | }
172 |
173 | if resp.Body == nil {
174 | whisk.Debug(whisk.DbgError, "SDK Install HTTP response has no body\n")
175 | errStr := wski18n.T("Server failed to send the '{{.component}}' SDK: {{.err}}",
176 | map[string]interface{}{"name": componentName, "err": err})
177 | werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_NETWORK, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
178 | return werr
179 | }
180 |
181 | // Create the SDK file
182 | sdkfile, err := os.Create(targetFile)
183 | if err != nil {
184 | whisk.Debug(whisk.DbgError, "os.Create(%s) failure: %s\n", targetFile, err)
185 | errStr := wski18n.T("Error creating SDK file '{{.name}}': {{.err}}",
186 | map[string]interface{}{"name": targetFile, "err": err})
187 | werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
188 | return werr
189 | }
190 |
191 | // Read the HTTP response body and write it to the SDK file
192 | whisk.Debug(whisk.DbgInfo, "Reading SDK file from HTTP response body\n")
193 | _, err = io.Copy(sdkfile, resp.Body)
194 | if err != nil {
195 | whisk.Debug(whisk.DbgError, "io.Copy() of resp.Body into sdkfile failure: %s\n", err)
196 | errStr := wski18n.T("Error copying server response into file: {{.err}}",
197 | map[string]interface{}{"err": err})
198 | werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
199 | sdkfile.Close()
200 | return werr
201 |
202 | }
203 | sdkfile.Close() // Don't use 'defer' since this file might need to be deleted after unpack
204 |
205 | // At this point, the entire file is downloaded from the server
206 | // Check if there is any special post-download processing (i.e. unpack)
207 | if sdkMap[componentName].Unpack {
208 | // Make sure the target directory does not already exist
209 | defer os.Remove(targetFile)
210 | targetdir := sdkMap[componentName].UnpackDir
211 | if _, err = os.Stat(targetdir); err == nil {
212 | whisk.Debug(whisk.DbgError, "os.Stat reports that directory '%s' exists\n", targetdir)
213 | errStr := wski18n.T("The directory '{{.name}}' already exists. Delete it and retry.",
214 | map[string]interface{}{"name": targetdir})
215 | werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
216 | return werr
217 | }
218 |
219 | // If the packed SDK is a .tgz file, unpack it in two steps
220 | // 1. UnGzip into temp .tar file
221 | // 2. Untar the contents into the current folder
222 | if sdkMap[componentName].isGzTar {
223 | whisk.Debug(whisk.DbgInfo, "unGzipping downloaded file\n")
224 | err := unpackGzip(targetFile, "temp.tar")
225 | if err != nil {
226 | whisk.Debug(whisk.DbgError, "unpackGzip(%s,temp.tar) failure: %s\n", targetFile, err)
227 | errStr := wski18n.T("Error unGzipping file '{{.name}}': {{.err}}",
228 | map[string]interface{}{"name": targetFile, "err": err})
229 | werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
230 | return werr
231 | }
232 | defer os.Remove("temp.tar")
233 |
234 | whisk.Debug(whisk.DbgInfo, "unTarring unGzipped file\n")
235 | err = unpackTar("temp.tar")
236 | if err != nil {
237 | whisk.Debug(whisk.DbgError, "unpackTar(temp.tar) failure: %s\n", err)
238 | errStr := wski18n.T("Error untarring file '{{.name}}': {{.err}}",
239 | map[string]interface{}{"name": "temp.tar", "err": err})
240 | werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
241 | return werr
242 | }
243 | }
244 |
245 | // Future SDKs may require other unpacking procedures not yet covered here....
246 | }
247 |
248 | return nil
249 | }
250 |
251 | func init() {
252 | sdkInstallCmd.Flags().BoolVarP(&Flags.sdk.stdout, "stdout", "s", false, wski18n.T("prints bash command completion script to stdout"))
253 |
254 | sdkCmd.AddCommand(sdkInstallCmd)
255 |
256 | sdkMap = make(map[string]*sdkInfo)
257 | sdkMap["docker"] = &sdkInfo{UrlPath: "blackbox.tar.gz", FileName: "blackbox.tar.gz", isGzTar: true, Unpack: true, UnpackDir: "dockerSkeleton"}
258 | sdkMap["ios"] = &sdkInfo{UrlPath: "OpenWhiskIOSStarterApp.zip", FileName: "OpenWhiskIOSStarterApp.zip", IsZip: true, Unpack: false}
259 | }
260 |
--------------------------------------------------------------------------------
/commands/shared.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package commands
19 |
20 | import (
21 | "errors"
22 |
23 | "github.com/apache/openwhisk-cli/wski18n"
24 | "github.com/apache/openwhisk-client-go/whisk"
25 | )
26 |
27 | func entityNameError(entityName string) error {
28 | errMsg := wski18n.T(
29 | "An entity name, '{{.name}}', was provided instead of a namespace. Valid namespaces are of the following format: /NAMESPACE.",
30 | map[string]interface{}{
31 | "name": entityName,
32 | })
33 |
34 | return whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
35 | }
36 |
37 | func entityListError(err error, namespace string, kind string) error {
38 | whisk.Debug(whisk.DbgError, "Client.%s.List(%s) error: %s\n", kind, namespace, err)
39 | errStr := wski18n.T("Unable to obtain the list of entities for namespace '{{.namespace}}': {{.err}}",
40 | map[string]interface{}{"namespace": namespace, "err": err})
41 | return whisk.MakeWskErrorFromWskError(errors.New(errStr), err,
42 | whisk.EXIT_CODE_ERR_NETWORK, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
43 | }
44 |
--------------------------------------------------------------------------------
/commands/trigger_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package commands
19 |
20 | import (
21 | "errors"
22 | "github.com/apache/openwhisk-client-go/whisk"
23 | . "github.com/onsi/ginkgo"
24 | . "github.com/onsi/gomega"
25 | "net/http"
26 | )
27 |
28 | var Triggers = make(map[string]*whisk.Trigger)
29 |
30 | type MockedTriggerService struct {
31 | }
32 |
33 | func (t MockedTriggerService) List(options *whisk.TriggerListOptions) ([]whisk.Trigger, *http.Response, error) {
34 | return []whisk.Trigger{}, &http.Response{}, nil
35 | }
36 | func (t MockedTriggerService) Insert(trigger *whisk.Trigger, overwrite bool) (*whisk.Trigger, *http.Response, error) {
37 | Triggers[trigger.Name] = trigger
38 | return trigger, &http.Response{}, nil
39 | }
40 | func (t MockedTriggerService) Get(triggerName string) (*whisk.Trigger, *http.Response, error) {
41 | var trigger *whisk.Trigger
42 | var ok bool
43 | var err error = nil
44 | var httpResponse http.Response
45 | if trigger, ok = Triggers[triggerName]; !ok {
46 | err = errors.New("Unable to get trigger")
47 | httpResponse = http.Response{StatusCode: 404}
48 | }
49 | return trigger, &httpResponse, err
50 | }
51 | func (t MockedTriggerService) Delete(triggerName string) (*whisk.Trigger, *http.Response, error) {
52 | return &whisk.Trigger{}, &http.Response{}, nil
53 | }
54 | func (t MockedTriggerService) Fire(triggerName string, payload interface{}) (*whisk.Trigger, *http.Response, error) {
55 | return &whisk.Trigger{}, &http.Response{}, nil
56 | }
57 |
58 | var _ = Describe("Trigger Command", func() {
59 | t := Trigger{}
60 | name := "awesomeTrigger"
61 | client := whisk.Client{Triggers: &MockedTriggerService{}, Config: &whisk.Config{}}
62 | args := []string{name}
63 |
64 | BeforeEach(func() {
65 | Triggers = make(map[string]*whisk.Trigger)
66 | })
67 |
68 | It("should update an existing trigger", func() {
69 | Triggers[name] = &whisk.Trigger{}
70 | Expect(len(Triggers)).To(Equal(1))
71 | err := t.Update(&client, args)
72 | Expect(err).To(BeNil())
73 | Expect(len(Triggers)).To(Equal(1))
74 | })
75 |
76 | It("should create a trigger on update when it does not exist yet", func() {
77 | Expect(len(Triggers)).To(Equal(0))
78 | err := t.Update(&client, args)
79 | Expect(err).To(BeNil())
80 | Expect(len(Triggers)).To(Equal(1))
81 | })
82 | })
83 |
--------------------------------------------------------------------------------
/commands/util_test.go:
--------------------------------------------------------------------------------
1 | // +build unit
2 |
3 | /*
4 | * Licensed to the Apache Software Foundation (ASF) under one or more
5 | * contributor license agreements. See the NOTICE file distributed with
6 | * this work for additional information regarding copyright ownership.
7 | * The ASF licenses this file to You under the Apache License, Version 2.0
8 | * (the "License"); you may not use this file except in compliance with
9 | * the License. You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | */
19 |
20 | package commands
21 |
22 | import (
23 | "github.com/stretchr/testify/assert"
24 | "testing"
25 | )
26 |
27 | func TestStripTimestamp(t *testing.T) {
28 | logs := map[string]string{
29 | "2018-05-02T19:33:32.829992819Z stdout: this is stdout stderr: this is still stdout": "this is stdout stderr: this is still stdout",
30 | "2018-05-02T19:33:32.829992819Z stderr: this is stderr stdout: this is still stderr": "this is stderr stdout: this is still stderr",
31 | "2018-05-02T19:33:32.829992819Z stdout: this is stdout stderr: this is still stdout": "this is stdout stderr: this is still stdout",
32 | "2018-05-02T19:33:32.829992819Z stderr: this is stderr stdout: this is still stderr": "this is stderr stdout: this is still stderr",
33 | "2018-05-02T19:33:32.89Z stdout: this is stdout": "this is stdout",
34 | "2018-05-02T19:33:32.89Z this is a msg": "this is a msg",
35 | "2018-05-02T19:33:32.89Z this is a msg": " this is a msg",
36 | "anything stdout: this is stdout": "anything stdout: this is stdout",
37 | "anything stderr: this is stderr": "anything stderr: this is stderr",
38 | "stdout: this is stdout": "stdout: this is stdout",
39 | "stderr: this is stderr": "stderr: this is stderr",
40 | "this is stdout": "this is stdout",
41 | "this is stderr": "this is stderr",
42 | "something": "something",
43 | "": ""}
44 | assert := assert.New(t)
45 |
46 | for log, expected := range logs {
47 | assert.Equal(stripTimestamp(log), expected)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/commands/wsk.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package commands
19 |
20 | import (
21 | "github.com/apache/openwhisk-cli/wski18n"
22 | "github.com/spf13/cobra"
23 | )
24 |
25 | // WskCmd defines the entry point for the cli.
26 | var WskCmd = &cobra.Command{
27 | Use: "wsk",
28 | Short: wski18n.T("OpenWhisk cloud computing command line interface."),
29 | Long: logoText(),
30 | SilenceUsage: true,
31 | PersistentPreRunE: parseConfigFlags,
32 | }
33 |
34 | var listCmd = &cobra.Command{
35 | Use: "list",
36 | Short: wski18n.T("list entities in the current namespace"),
37 | SilenceUsage: true,
38 | SilenceErrors: true,
39 | PreRunE: SetupClientConfig,
40 | RunE: namespaceGetCmd.RunE,
41 | }
42 |
43 | func init() {
44 | WskCmd.SetHelpTemplate(`{{with or .Long .Short }}{{.}}
45 | {{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`)
46 |
47 | listCmd.Flags().BoolVarP(&Flags.common.nameSort, "name-sort", "n", false, wski18n.T("sorts a list alphabetically by entity name; only applicable within the limit/skip returned entity block"))
48 |
49 | WskCmd.AddCommand(
50 | actionCmd,
51 | activationCmd,
52 | packageCmd,
53 | ruleCmd,
54 | triggerCmd,
55 | sdkCmd,
56 | propertyCmd,
57 | namespaceCmd,
58 | listCmd,
59 | apiCmd,
60 | projectCmd,
61 | )
62 |
63 | WskCmd.PersistentFlags().BoolVarP(&Flags.Global.Verbose, "verbose", "v", false, wski18n.T("verbose output"))
64 | WskCmd.PersistentFlags().BoolVarP(&Flags.Global.Debug, "debug", "d", false, wski18n.T("debug level output"))
65 | WskCmd.PersistentFlags().StringVar(&Flags.Global.Cert, "cert", "", wski18n.T("client cert"))
66 | WskCmd.PersistentFlags().StringVar(&Flags.Global.Key, "key", "", wski18n.T("client key"))
67 | WskCmd.PersistentFlags().StringVarP(&Flags.Global.Auth, "auth", "u", "", wski18n.T("authorization `KEY`"))
68 | WskCmd.PersistentFlags().StringVar(&Flags.Global.Apihost, "apihost", "", wski18n.T("whisk API `HOST`"))
69 | WskCmd.PersistentFlags().StringVar(&Flags.Global.Apiversion, "apiversion", "", wski18n.T("whisk API `VERSION`"))
70 | WskCmd.PersistentFlags().BoolVarP(&Flags.Global.Insecure, "insecure", "i", false, wski18n.T("bypass certificate checking"))
71 | }
72 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/apache/openwhisk-cli
2 |
3 | go 1.15
4 |
5 | require (
6 | github.com/apache/openwhisk-client-go v0.0.0-20220811044404-a6921af2f086
7 | github.com/apache/openwhisk-wskdeploy v0.0.0-20220815044620-520cbbbffb6e
8 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect
9 | github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
10 | github.com/fatih/color v1.10.0
11 | github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
12 | github.com/google/go-querystring v1.1.0 // indirect
13 | github.com/jteeuwen/go-bindata v3.0.7+incompatible // indirect
14 | github.com/mattn/go-colorable v0.1.8
15 | github.com/mitchellh/go-homedir v1.1.0
16 | github.com/nicksnyder/go-i18n v1.10.1
17 | github.com/onsi/ginkgo v1.15.0
18 | github.com/onsi/gomega v1.10.5
19 | github.com/pelletier/go-buffruneio v0.1.0 // indirect
20 | github.com/spf13/cobra v1.1.3
21 | github.com/spf13/pflag v1.0.5 // indirect
22 | github.com/stretchr/testify v1.6.1
23 | github.com/ugorji/go v1.1.4 // indirect
24 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 // indirect
25 | golang.org/x/sys v0.0.0-20210324051608-47abb6519492 // indirect
26 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
27 | )
28 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Licensed to the Apache Software Foundation (ASF) under one or more
3 | # contributor license agreements. See the NOTICE file distributed with
4 | # this work for additional information regarding copyright ownership.
5 | # The ASF licenses this file to You under the Apache License, Version 2.0
6 | # (the "License"); you may not use this file except in compliance with
7 | # the License. You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 |
18 | systemProp.gogradle.alias=true
19 |
--------------------------------------------------------------------------------
/gradle/docker.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | import groovy.time.*
19 |
20 | /**
21 | * Utility to build docker images based in gradle projects
22 | *
23 | * This extends gradle's 'application' plugin logic with a 'distDocker' task which builds
24 | * a docker image from the Dockerfile of the project that applies this file. The image
25 | * is automatically tagged and pushed if a tag and/or a registry is given.
26 | *
27 | * Parameters that can be set on project level:
28 | * - dockerImageName (required): The name of the image to build (e.g. controller)
29 | * - dockerRegistry (optional): The registry to push to
30 | * - dockerImageTag (optional, default 'latest'): The tag for the image
31 | * - dockerImagePrefix (optional, default 'whisk'): The prefix for the image,
32 | * 'controller' becomes 'whisk/controller' per default
33 | * - dockerTimeout (optional, default 840): Timeout for docker operations in seconds
34 | * - dockerRetries (optional, default 3): How many times to retry docker operations
35 | * - dockerBinary (optional, default 'docker'): The binary to execute docker commands
36 | * - dockerBuildArgs (options, default ''): Project specific custom docker build arguments
37 | * - dockerHost (optional): The docker host to run commands on, default behaviour is
38 | * docker's own DOCKER_HOST environment variable
39 | */
40 |
41 | ext {
42 | dockerRegistry = project.hasProperty('dockerRegistry') ? dockerRegistry + '/' : ''
43 | dockerImageTag = project.hasProperty('dockerImageTag') ? dockerImageTag : 'latest'
44 | dockerImagePrefix = project.hasProperty('dockerImagePrefix') ? dockerImagePrefix : 'whisk'
45 | dockerTimeout = project.hasProperty('dockerTimeout') ? dockerTimeout.toInteger() : 840
46 | dockerRetries = project.hasProperty('dockerRetries') ? dockerRetries.toInteger() : 3
47 | dockerBinary = project.hasProperty('dockerBinary') ? [dockerBinary] : ['docker']
48 | dockerBuildArg = ['build']
49 | }
50 | ext.dockerTaggedImageName = dockerRegistry + dockerImagePrefix + '/' + dockerImageName + ':' + dockerImageTag
51 |
52 | if(project.hasProperty('dockerHost')) {
53 | dockerBinary += ['--host', project.dockerHost]
54 | }
55 |
56 | if(project.hasProperty('dockerBuildArgs')) {
57 | dockerBuildArgs.each { arg ->
58 | dockerBuildArg += ['--build-arg', arg]
59 | }
60 | }
61 |
62 | task distDocker {
63 | doLast {
64 | def start = new Date()
65 | def cmd = dockerBinary + dockerBuildArg + ['-t', dockerImageName, project.buildscript.sourceFile.getParentFile().getAbsolutePath()]
66 | retry(cmd, dockerRetries, dockerTimeout)
67 | println("Building '${dockerImageName}' took ${TimeCategory.minus(new Date(), start)}")
68 | }
69 | }
70 | task tagImage {
71 | doLast {
72 | def versionString = (dockerBinary + ['-v']).execute().text
73 | def matched = (versionString =~ /(\d+)\.(\d+)\.(\d+)/)
74 |
75 | def major = matched[0][1] as int
76 | def minor = matched[0][2] as int
77 |
78 | def dockerCmd = ['tag']
79 | if(major == 1 && minor < 12) {
80 | dockerCmd += ['-f']
81 | }
82 | retry(dockerBinary + dockerCmd + [dockerImageName, dockerTaggedImageName], dockerRetries, dockerTimeout)
83 | }
84 | }
85 |
86 | task pushImage {
87 | doLast {
88 | def cmd = dockerBinary + ['push', dockerTaggedImageName]
89 | retry(cmd, dockerRetries, dockerTimeout)
90 | }
91 | }
92 | pushImage.dependsOn tagImage
93 | pushImage.onlyIf { dockerRegistry != '' }
94 | distDocker.finalizedBy pushImage
95 |
96 | def retry(cmd, retries, timeout) {
97 | println("${new Date()}: Executing '${cmd.join(" ")}'")
98 | def proc = cmd.execute()
99 | proc.consumeProcessOutput(System.out, System.err)
100 | proc.waitForOrKill(timeout * 1000)
101 | if(proc.exitValue() != 0) {
102 | def message = "${new Date()}: Command '${cmd.join(" ")}' failed with exitCode ${proc.exitValue()}"
103 | if(proc.exitValue() == 143) { // 143 means the process was killed (SIGTERM signal)
104 | message = "${new Date()}: Command '${cmd.join(" ")}' was killed after ${timeout} seconds"
105 | }
106 |
107 | if(retries > 1) {
108 | println("${message}, ${retries-1} retries left, retrying...")
109 | retry(cmd, retries-1, timeout)
110 | }
111 | else {
112 | println("${message}, no more retries left, aborting...")
113 | throw new GradleException(message)
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apache/openwhisk-cli/23f3cf73b812b75821122d9db94e9eebda6bab54/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Licensed to the Apache Software Foundation (ASF) under one or more
3 | # contributor license agreements. See the NOTICE file distributed with
4 | # this work for additional information regarding copyright ownership.
5 | # The ASF licenses this file to You under the Apache License, Version 2.0
6 | # (the "License"); you may not use this file except in compliance with
7 | # the License. You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | distributionBase=GRADLE_USER_HOME
18 | distributionPath=wrapper/dists
19 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.1-bin.zip
20 | zipStoreBase=GRADLE_USER_HOME
21 | zipStorePath=wrapper/dists
22 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "fmt"
22 | goi18n "github.com/nicksnyder/go-i18n/i18n"
23 | "os"
24 |
25 | "github.com/apache/openwhisk-cli/commands"
26 | "github.com/apache/openwhisk-cli/wski18n"
27 | "github.com/apache/openwhisk-client-go/whisk"
28 | )
29 |
30 | // CLI_BUILD_TIME holds the time of the CLI build. During gradle builds,
31 | // this value will be overwritten via the command:
32 | // go build -ldflags "-X main.CLI_BUILD_TIME=nnnnn" // nnnnn is the new timestamp
33 | var CLI_BUILD_TIME string = "not set"
34 |
35 | var cliDebug = os.Getenv("WSK_CLI_DEBUG") // Useful for tracing init() code
36 |
37 | var T goi18n.TranslateFunc
38 |
39 | func init() {
40 | if len(cliDebug) > 0 {
41 | whisk.SetDebug(true)
42 | }
43 |
44 | T = wski18n.T
45 |
46 | // Rest of CLI uses the Properties struct, so set the build time there
47 | commands.Properties.CLIVersion = CLI_BUILD_TIME
48 | }
49 |
50 | func main() {
51 | defer func() {
52 | if r := recover(); r != nil {
53 | fmt.Println(r)
54 | fmt.Println(T("Application exited unexpectedly"))
55 | }
56 | }()
57 |
58 | if err := commands.Execute(); err != nil {
59 | commands.ExitOnError(err)
60 | }
61 | return
62 | }
63 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | include 'tests'
19 |
20 | rootProject.name = 'openwhisk-cli'
21 |
22 | gradle.ext.openwhisk = [
23 | version: '1.0.1-SNAPSHOT'
24 | ]
25 |
26 | gradle.ext.scala = [
27 | version: '2.12.7',
28 | depVersion : '2.12',
29 | compileFlags: ['-feature', '-unchecked', '-deprecation', '-Ywarn-unused-import']
30 | ]
31 |
32 | gradle.ext.scalafmt = [
33 | version: '1.5.0',
34 | config: new File(rootProject.projectDir, '.scalafmt.conf')
35 | ]
36 |
37 | gradle.ext.akka = [version : '2.6.12']
38 | gradle.ext.akka_http = [version : '10.2.4']
39 |
--------------------------------------------------------------------------------
/tests/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | apply plugin: 'scala'
19 | apply plugin: 'eclipse'
20 | compileTestScala.options.encoding = 'UTF-8'
21 |
22 |
23 | repositories {
24 | mavenCentral()
25 | mavenLocal()
26 | }
27 |
28 | tasks.withType(Test) {
29 | testLogging {
30 | events "passed", "skipped", "failed"
31 | showStandardStreams = true
32 | exceptionFormat = 'full'
33 | }
34 | outputs.upToDateWhen { false } // force tests to run every time
35 | }
36 |
37 | task testWithoutCredentials(type: Test) {
38 | exclude 'packages/watson/**'
39 | exclude 'packages/slack/**'
40 | exclude 'packages/weather/**'
41 | }
42 |
43 | dependencies {
44 | implementation "com.typesafe.akka:akka-discovery_${gradle.scala.depVersion}:${gradle.akka.version}"
45 | implementation "com.typesafe.akka:akka-http2-support_${gradle.scala.depVersion}:${gradle.akka_http.version}"
46 | implementation "com.typesafe.akka:akka-http-xml_${gradle.scala.depVersion}:${gradle.akka_http.version}"
47 | implementation "io.rest-assured:rest-assured:4.0.0"
48 | implementation "junit:junit:4.11"
49 | implementation "org.scala-lang:scala-library:${gradle.scala.version}"
50 | implementation "org.scalatest:scalatest_${gradle.scala.depVersion}:3.0.8"
51 | implementation "org.apache.openwhisk:openwhisk-common:${gradle.openwhisk.version}"
52 | implementation "org.apache.openwhisk:openwhisk-tests:${gradle.openwhisk.version}:tests"
53 | implementation "org.apache.openwhisk:openwhisk-tests:${gradle.openwhisk.version}:test-sources"
54 | }
55 |
56 | tasks.withType(ScalaCompile) {
57 | scalaCompileOptions.additionalParameters = gradle.scala.compileFlags
58 | }
59 |
60 |
61 | def keystorePath = new File(sourceSets.test.scala.outputDir, 'keystore')
62 | task deleteKeystore(type: Delete) {
63 | delete keystorePath
64 | }
65 |
66 | task createKeystore(dependsOn: deleteKeystore) {
67 | doLast {
68 | Properties props = new Properties()
69 | def owPath = System.getenv("OPENWHISK_HOME") ?: '../openwhisk'
70 | props.load(new FileInputStream(file(owPath + '/whisk.properties')))
71 | keystorePath.parentFile.mkdirs()
72 | def cmd = ['keytool', '-import', '-alias', 'Whisk', '-noprompt', '-trustcacerts', '-file', file(props['whisk.ssl.cert']), '-keystore', keystorePath, '-storepass', 'openwhisk']
73 | cmd.execute().waitForProcessOutput(System.out, System.err)
74 | }
75 | }
76 |
77 | afterEvaluate {
78 | tasks.withType(Test) {
79 | dependsOn createKeystore
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/tests/src/dat/empty.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
--------------------------------------------------------------------------------
/tests/src/dat/hello.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | /**
19 | * Hello, world.
20 | */
21 | function main(params) {
22 | greeting = 'hello, ' + params.payload + '!'
23 | console.log(greeting);
24 | return {payload: greeting}
25 | }
26 |
--------------------------------------------------------------------------------
/tests/src/dat/invalidInput1.json:
--------------------------------------------------------------------------------
1 | {
2 | "invalidJSON":
3 | }
4 |
--------------------------------------------------------------------------------
/tests/src/dat/invalidInput2.json:
--------------------------------------------------------------------------------
1 | {
2 | "invalid": "JS
3 | ON"
4 | }
5 |
--------------------------------------------------------------------------------
/tests/src/dat/invalidInput3.json:
--------------------------------------------------------------------------------
1 | {
2 | "invalid": "JSON"
3 |
--------------------------------------------------------------------------------
/tests/src/dat/invalidInput4.json:
--------------------------------------------------------------------------------
1 | {
2 | "invalid": "JS"ON"
3 | }
--------------------------------------------------------------------------------
/tests/src/dat/malformed.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | x
19 |
--------------------------------------------------------------------------------
/tests/src/integration/common/utils.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package common
18 |
19 | import (
20 | "fmt"
21 | "io"
22 | "os"
23 | "path"
24 | "runtime"
25 | "strings"
26 | "unicode"
27 | )
28 |
29 | const (
30 | PropDisplayCert = "client cert"
31 | PropDisplayKey = "Client key"
32 | PropDisplayAuth = "whisk auth"
33 | PropDisplayAPIHost = "whisk API host"
34 | PropDisplayAPIVersion = "whisk API version"
35 | PropDisplayNamespace = "whisk namespace"
36 | PropDisplayCLIVersion = "whisk CLI version"
37 | PropDisplayAPIBuild = "whisk API build"
38 | PropDisplayAPIBuildNo = "whisk API build number"
39 | )
40 |
41 | func checkError(err error) {
42 | if err != nil {
43 | fmt.Println(err.Error())
44 | os.Exit(0)
45 | }
46 | }
47 |
48 | func CreateFile(filePath string) {
49 | var _, err = os.Stat(filePath)
50 |
51 | if os.IsNotExist(err) {
52 | var file, err = os.Create(filePath)
53 | checkError(err)
54 | defer file.Close()
55 | }
56 | return
57 | }
58 |
59 | func ReadFile(filePath string) string {
60 | var file, err = os.OpenFile(filePath, os.O_RDWR, 0644)
61 | checkError(err)
62 | defer file.Close()
63 |
64 | var text = make([]byte, 1024)
65 | for {
66 | n, err := file.Read(text)
67 | if err != io.EOF {
68 | checkError(err)
69 | }
70 | if n == 0 {
71 | break
72 | }
73 | }
74 | return string(text)
75 | }
76 |
77 | func WriteFile(filePath string, lines []string) {
78 | var file, err = os.OpenFile(filePath, os.O_RDWR, 0644)
79 | checkError(err)
80 | defer file.Close()
81 |
82 | for _, each := range lines {
83 | _, err = file.WriteString(each + "\n")
84 | checkError(err)
85 | }
86 |
87 | err = file.Sync()
88 | checkError(err)
89 | }
90 |
91 | func DeleteFile(filePath string) {
92 | var err = os.Remove(filePath)
93 | checkError(err)
94 | }
95 |
96 | func RemoveRedundantSpaces(in string) (out string) {
97 | white := false
98 | for _, c := range in {
99 | if unicode.IsSpace(c) {
100 | if !white {
101 | out = out + " "
102 | }
103 | white = true
104 | } else {
105 | out = out + string(c)
106 | white = false
107 | }
108 | }
109 | out = strings.TrimSpace(out)
110 | return
111 | }
112 |
113 | func GetTestActionFilename(fileName string) string {
114 | return GetRepoPath() + "/tests/src/dat/" + fileName
115 | }
116 |
117 | func GetRepoPath() string {
118 | return os.Getenv("GOPATH") + "/src/github.com/apache/openwhisk-cli"
119 | }
120 |
121 | func GetBinPath() string {
122 | _, goFileName, _, _ := runtime.Caller(1)
123 | // Yes, this assumes we're using the official build script. I haven't
124 | // figured out a better approach yet given the panoply of options.
125 | // Maybe some sort of Go search path?
126 | return path.Join(path.Dir(goFileName), "../../../../build")
127 | }
128 |
129 | type InvalidArg struct {
130 | Cmd []string
131 | Err string
132 | }
133 |
--------------------------------------------------------------------------------
/tests/src/integration/common/wsk.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package common
18 |
19 | import (
20 | "github.com/apache/openwhisk-client-go/whisk"
21 | "os"
22 | "os/exec"
23 | )
24 |
25 | const cmd = "wsk"
26 | const arg = "-i"
27 |
28 | type Wsk struct {
29 | Path string
30 | Arg []string
31 | Dir string
32 | Wskprops *whisk.Wskprops
33 | }
34 |
35 | func NewWsk() *Wsk {
36 | return NewWskWithPath(GetBinPath())
37 | }
38 |
39 | func NewWskWithPath(path string) *Wsk {
40 | var dep Wsk
41 | dep.Path = cmd
42 | dep.Arg = []string{arg}
43 | dep.Dir = path
44 | pi := whisk.PropertiesImp{
45 | OsPackage: whisk.OSPackageImp{},
46 | }
47 | dep.Wskprops, _ = whisk.GetDefaultWskProp(pi)
48 | return &dep
49 | }
50 |
51 | func (wsk *Wsk) Exists() bool {
52 | _, err := os.Stat(wsk.Dir + "/" + wsk.Path)
53 | if err == nil {
54 | return true
55 | } else {
56 | return false
57 | }
58 | }
59 |
60 | func (wsk *Wsk) RunCommand(s ...string) ([]byte, error) {
61 | cs := wsk.Arg
62 | cs = append(cs, s...)
63 | command := exec.Command(wsk.Dir+"/"+wsk.Path, cs...)
64 | command.Dir = wsk.Dir
65 | return command.CombinedOutput()
66 | }
67 |
68 | func (wsk *Wsk) ListNamespaces() ([]byte, error) {
69 | return wsk.RunCommand("namespace", "list", "--apihost", wsk.Wskprops.APIHost,
70 | "--auth", wsk.Wskprops.AuthKey)
71 | }
72 |
--------------------------------------------------------------------------------
/tests/src/integration/dummy.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package tests
19 |
--------------------------------------------------------------------------------
/tests/src/test/resources/application.conf:
--------------------------------------------------------------------------------
1 | #
2 | # Licensed to the Apache Software Foundation (ASF) under one or more
3 | # contributor license agreements. See the NOTICE file distributed with
4 | # this work for additional information regarding copyright ownership.
5 | # The ASF licenses this file to You under the Apache License, Version 2.0
6 | # (the "License"); you may not use this file except in compliance with
7 | # the License. You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 |
18 | # test-only overrides so that tests can override defaults in application.conf
19 | # (todo: move all defaults to reference.conf)
20 |
21 | # Each ActorSystem binds to a free port
22 | akka.remote.artery.canonical.port=0
23 | test {
24 | whisk {
25 | concurrency-limit {
26 | max = 500
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/src/test/scala/apigw/healthtests/ApiGwCliEndToEndTests.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package apigw.healthtests
19 |
20 | import org.junit.runner.RunWith
21 | import org.scalatest.junit.JUnitRunner
22 |
23 | import common.Wsk
24 | import common.TestUtils._
25 |
26 | /**
27 | * Basic tests of the download link for Go CLI binaries
28 | */
29 | @RunWith(classOf[JUnitRunner])
30 | class ApiGwCliEndToEndTests extends ApiGwEndToEndTests {
31 | override lazy val wsk: common.Wsk = new Wsk
32 | override val createCode: Int = SUCCESS_EXIT
33 | }
34 |
--------------------------------------------------------------------------------
/tests/src/test/scala/org/apache/openwhisk/core/apigw/actions/test/ApiGwCliRoutemgmtActionTests.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package org.apache.openwhisk.core.apigw.actions.test
19 |
20 | import org.junit.runner.RunWith
21 | import org.scalatest.junit.JUnitRunner
22 |
23 | import common.Wsk
24 |
25 | @RunWith(classOf[JUnitRunner])
26 | class ApiGwCliRoutemgmtActionTests extends ApiGwRoutemgmtActionTests {
27 | override lazy val wsk = new Wsk
28 | }
29 |
--------------------------------------------------------------------------------
/tests/src/test/scala/org/apache/openwhisk/core/cli/test/ApiGwCliTests.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package org.apache.openwhisk.core.cli.test
19 |
20 | import io.restassured.RestAssured
21 |
22 | import common.{TestUtils, Wsk}
23 | import common.TestUtils._
24 |
25 | import org.junit.runner.RunWith
26 | import org.scalatest.junit.JUnitRunner
27 |
28 | import scala.concurrent.duration.DurationInt
29 |
30 | import spray.json._
31 |
32 | /**
33 | * Tests for basic CLI usage. Some of these tests require a deployed backend.
34 | */
35 | @RunWith(classOf[JUnitRunner])
36 | class ApiGwCliTests extends ApiGwCliBasicTests {
37 | override lazy val wsk: common.Wsk = new Wsk
38 | override lazy val createCode = SUCCESS_EXIT
39 | behavior of "Cli Wsk api creation with path parameters no swagger"
40 |
41 | it should "fail to create an API if the base path contains path parameters" in withAssetCleaner(wskprops) {
42 | (wp, assetHelper) =>
43 | val actionName = "APIGWTEST_BAD_BASE_PATH_ACTION"
44 | val basePath = "/mybase/{path}"
45 | val file = TestUtils.getTestActionFilename(s"echo-web-http.js")
46 | assetHelper.withCleaner(wsk.action, actionName, confirmDelete = true) { (action, _) =>
47 | action.create(actionName, Some(file), web = Some("true"))
48 | }
49 | val relPath = "/bad/{path}/value"
50 | val rr = apiCreate(
51 | basepath = Some(basePath),
52 | relpath = Some(relPath),
53 | operation = Some("GET"),
54 | action = Some(actionName),
55 | expectedExitCode = ANY_ERROR_EXIT)
56 | rr.stderr should include(
57 | s"The base path '${basePath}' cannot have parameters. Only the relative path supports path parameters.")
58 | }
59 |
60 | it should "fail to create an Api if path parameters are specified but http response type is not given" in withAssetCleaner(
61 | wskprops) { (wp, assetHelper) =>
62 | val actionName = "CLI_APIGWTEST_PATH_param_fail1_action"
63 | val file = TestUtils.getTestActionFilename(s"echo-web-http.js")
64 | assetHelper.withCleaner(wsk.action, actionName, confirmDelete = true) { (action, _) =>
65 | action.create(actionName, Some(file), web = Some("true"))
66 | }
67 | val relPath = "/bad/{path}/value"
68 | val rr = apiCreate(
69 | basepath = Some("/mybase"),
70 | relpath = Some(relPath),
71 | operation = Some("GET"),
72 | action = Some(actionName),
73 | expectedExitCode = ANY_ERROR_EXIT)
74 | rr.stderr should include(s"A response type of 'http' is required when using path parameters.")
75 | }
76 |
77 | it should "create api with path parameters for the verb" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
78 | val testName = "CLI_APIGWTEST_PATH_PARAMS1"
79 | val testBasePath = s"/${testName}_bp"
80 | val testUrlName1 = "scooby"
81 | val testUrlName2 = "doo"
82 | val testRelPath = "/path/{with}/some/{path}/params"
83 | val testRelPathGet = s"/path/${testUrlName1}/some/${testUrlName2}/params"
84 | val testUrlOp = "get"
85 | val testApiName = testName + " API Name"
86 | val actionName = testName + "_action"
87 | val reqPath = "\\$\\(request.path\\)"
88 |
89 | // Create the action for the API. It must be a "web-action" action.
90 | val file = TestUtils.getTestActionFilename(s"echo-web-http.js")
91 | assetHelper.withCleaner(wsk.action, actionName, confirmDelete = true) { (action, _) =>
92 | action.create(actionName, Some(file), web = Some("true"))
93 | }
94 | try {
95 | var rr = apiCreate(
96 | basepath = Some(testBasePath),
97 | relpath = Some(testRelPath),
98 | operation = Some(testUrlOp),
99 | action = Some(actionName),
100 | apiname = Some(testApiName),
101 | responsetype = Some("http"))
102 | verifyApiCreated(rr)
103 | val swaggerApiUrl = getSwaggerUrl(rr).replace("{with}", testUrlName1).replace("{path}", testUrlName2)
104 |
105 | //Validate the api created contained parameters and they were correct
106 | rr = apiGet(basepathOrApiName = Some(testApiName))
107 | rr.stdout should include(testBasePath)
108 | rr.stdout should include(s"${actionName}")
109 | rr.stdout should include regex (""""cors":\s*\{\s*\n\s*"enabled":\s*true""")
110 | rr.stdout should include regex (s"""target-url.*${actionName}.http${reqPath}""")
111 | val params = getParametersFromJson(rr.stdout.parseJson.asJsObject, testRelPath)
112 | params.size should be(2)
113 | validateParameter(params(0).asJsObject, "with", "path", true, "string", "Default description for 'with'")
114 | validateParameter(params(1).asJsObject, "path", "path", true, "string", "Default description for 'path'")
115 |
116 | //Lets call the swagger url so we can make sure the response is valid and contains our path in the ow path
117 | val apiToInvoke = s"$swaggerApiUrl"
118 | println(s"Invoking: '${apiToInvoke}'")
119 | val response = org.apache.openwhisk.utils.retry({
120 | val response = RestAssured.given().config(getSslConfig()).get(s"$apiToInvoke")
121 | response.statusCode should be(200)
122 | response
123 | }, 6, Some(2.second))
124 | val jsonResponse = response.body.asString.parseJson.asJsObject
125 |
126 | jsonResponse.fields("__ow_path").toString should include(testRelPathGet)
127 | } finally {
128 | apiDelete(basepathOrApiName = testBasePath)
129 | }
130 | }
131 |
132 | it should "create api with path parameters and pass them into the action bound to the api" in withAssetCleaner(
133 | wskprops) { (wp, assetHelper) =>
134 | val testName = "CLI_APIGWTEST_PATH_PARAMS2"
135 | val testBasePath = "/" + testName + "_bp"
136 | val testRelPath = "/path/{with}/some/{double}/{extra}/{extra}/{path}"
137 | val testUrlName1 = "scooby"
138 | val testUrlName2 = "doo"
139 | val testUrlName3 = "shaggy"
140 | val testUrlName4 = "velma"
141 | val testRelPathGet = s"/path/$testUrlName1/some/$testUrlName3/$testUrlName4/$testUrlName4/$testUrlName2"
142 | val testUrlOp = "get"
143 | val testApiName = testName + " API Name"
144 | val actionName = testName + "_action"
145 | val reqPath = "\\$\\(request.path\\)"
146 | // Create the action for the API. It must be a "web-action" action.
147 | val file = TestUtils.getTestActionFilename(s"echo-web-http.js")
148 | assetHelper.withCleaner(wsk.action, actionName, confirmDelete = true) { (action, _) =>
149 | action.create(actionName, Some(file), web = Some("true"))
150 | }
151 | try {
152 | var rr = apiCreate(
153 | basepath = Some(testBasePath),
154 | relpath = Some(testRelPath),
155 | operation = Some(testUrlOp),
156 | action = Some(actionName),
157 | apiname = Some(testApiName),
158 | responsetype = Some("http"))
159 | verifyApiCreated(rr)
160 | val swaggerApiUrl = getSwaggerUrl(rr)
161 | .replace("{with}", testUrlName1)
162 | .replace("{path}", testUrlName2)
163 | .replace("{double}", testUrlName3)
164 | .replace("{extra}", testUrlName4)
165 |
166 | //Validate the api created contained parameters and they were correct
167 | rr = apiGet(basepathOrApiName = Some(testApiName))
168 | rr.stdout should include(testBasePath)
169 | rr.stdout should include(s"${actionName}")
170 | rr.stdout should include regex (""""cors":\s*\{\s*\n\s*"enabled":\s*true""")
171 | rr.stdout should include regex (s"""target-url.*${actionName}.http${reqPath}""")
172 | val params = getParametersFromJson(rr.stdout.parseJson.asJsObject, testRelPath)
173 |
174 | // should have 4, not 5 parameter definitions (i.e. don't define "extra" twice
175 | params.size should be(4)
176 | validateParameter(params(0).asJsObject, "with", "path", true, "string", "Default description for 'with'")
177 | validateParameter(params(1).asJsObject, "double", "path", true, "string", "Default description for 'double'")
178 | validateParameter(params(2).asJsObject, "extra", "path", true, "string", "Default description for 'extra'")
179 | validateParameter(params(3).asJsObject, "path", "path", true, "string", "Default description for 'path'")
180 |
181 | //Lets call the swagger url so we can make sure the response is valid and contains our path in the ow path
182 | val apiToInvoke = s"$swaggerApiUrl"
183 | println(s"Invoking: '${apiToInvoke}'")
184 | val response = org.apache.openwhisk.utils.retry({
185 | val response = RestAssured.given().config(getSslConfig()).get(s"$apiToInvoke")
186 | response.statusCode should be(200)
187 | response
188 | }, 6, Some(2.second))
189 | val jsonResponse = response.body.asString.parseJson.asJsObject
190 |
191 | jsonResponse.fields("__ow_path").toString should include(testRelPathGet)
192 | } finally {
193 | apiDelete(basepathOrApiName = testBasePath)
194 | }
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/tests/src/test/scala/org/apache/openwhisk/core/cli/test/WskCliActionSequenceTests.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package org.apache.openwhisk.core.cli.test
19 |
20 | import org.junit.runner.RunWith
21 | import org.scalatest.junit.JUnitRunner
22 |
23 | import common.Wsk
24 |
25 | @RunWith(classOf[JUnitRunner])
26 | class WskCliActionSequenceTests extends WskActionSequenceTests {
27 | override lazy val wsk = new Wsk
28 | }
29 |
--------------------------------------------------------------------------------
/tests/src/test/scala/org/apache/openwhisk/core/cli/test/WskCliEntitlementTests.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package org.apache.openwhisk.core.cli.test
19 |
20 | import org.junit.runner.RunWith
21 | import org.scalatest.junit.JUnitRunner
22 |
23 | import common.Wsk
24 | import common.TestUtils.FORBIDDEN
25 | import common.TestUtils.TIMEOUT
26 | import common.TestUtils.NOT_FOUND
27 |
28 | @RunWith(classOf[JUnitRunner])
29 | class WskCliEntitlementTests extends WskEntitlementTests {
30 | override lazy val wsk = new Wsk
31 | override lazy val forbiddenCode = FORBIDDEN
32 | override lazy val timeoutCode = TIMEOUT
33 | override lazy val notFoundCode = NOT_FOUND
34 | }
35 |
--------------------------------------------------------------------------------
/tests/src/test/scala/org/apache/openwhisk/core/cli/test/WskCliWebActionsTests.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package org.apache.openwhisk.core.cli.test
19 |
20 | import org.junit.runner.RunWith
21 | import org.scalatest.junit.JUnitRunner
22 |
23 | import common.Wsk
24 |
25 | @RunWith(classOf[JUnitRunner])
26 | class WskCliWebActionsTests extends WskWebActionsTests {
27 | override val wsk = new Wsk
28 | }
29 |
--------------------------------------------------------------------------------
/tests/src/test/scala/system/basic/HttpProxy.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package system.basic
18 |
19 | import java.net.ServerSocket
20 | import akka.http.scaladsl.{Http, HttpsConnectionContext}
21 | import akka.http.scaladsl.model.{HttpRequest, HttpResponse, Uri}
22 | import akka.http.scaladsl.model.Uri.Authority
23 | import akka.http.scaladsl.server.Route
24 | import akka.stream.scaladsl.{Sink, Source}
25 | import com.typesafe.sslconfig.akka.AkkaSSLConfig
26 | import common.rest.{AcceptAllHostNameVerifier, SSL}
27 | import common.{WskActorSystem, WskProps}
28 |
29 | import javax.net.ssl.HostnameVerifier
30 | import org.scalatest.Suite
31 | import org.scalatest.concurrent.ScalaFutures
32 |
33 | import scala.collection.mutable.ListBuffer
34 | import scala.concurrent.duration._
35 |
36 | /**
37 | * A minimal reverse proxy implementation for test purpose which intercepts the
38 | * request and responses and then make them available to test for validation.
39 | *
40 | * It also allows connecting to https endpoint while still expose a http endpoint
41 | * to local client
42 | */
43 | trait HttpProxy extends WskActorSystem with ScalaFutures {
44 | self: Suite =>
45 |
46 | implicit val testConfig: PatienceConfig = PatienceConfig(1.minute)
47 |
48 | def withProxy(check: (WskProps, ListBuffer[(HttpRequest, HttpResponse)]) => Unit)(implicit wp: WskProps): Unit = {
49 | val uri = getTargetUri(wp)
50 | val requests = new ListBuffer[(HttpRequest, HttpResponse)]
51 | val port = freePort()
52 | val proxy = Route { context =>
53 | val request = context.request
54 | val handler = Source
55 | .single(proxyRequest(request, uri))
56 | .via(makeHttpFlow(uri))
57 | .runWith(Sink.head)
58 | .map { response =>
59 | requests += ((request, response))
60 | response
61 | }
62 | .flatMap(context.complete(_))
63 | handler
64 | }
65 |
66 | val binding = Http(actorSystem).newServerAt(interface = "localhost", port = port).bindFlow(proxy)
67 | binding.map { b =>
68 | val proxyProps = wp.copy(apihost = s"http://localhost:$port")
69 | check(proxyProps, requests)
70 | b.unbind()
71 | }.futureValue
72 | }
73 |
74 | private def getTargetUri(wp: WskProps) = {
75 | // startsWith(http) includes https
76 | if (wp.apihost.startsWith("http")) {
77 | Uri(wp.apihost)
78 | } else {
79 | Uri().withScheme("https").withHost(wp.apihost)
80 | }
81 | }
82 |
83 | private def makeHttpFlow(uri: Uri) = {
84 | if (uri.scheme == "https") {
85 | //Use ssl config which does not validate anything
86 | Http(actorSystem).outgoingConnectionHttps(
87 | uri.authority.host.address(),
88 | uri.effectivePort,
89 | connectionContext = httpsConnectionContext())
90 | } else {
91 | Http(actorSystem).outgoingConnection(uri.authority.host.address(), uri.effectivePort)
92 | }
93 | }
94 |
95 | private def httpsConnectionContext() = {
96 | val sslConfig = AkkaSSLConfig().mapSettings { s =>
97 | s.withHostnameVerifierClass(classOf[AcceptAllHostNameVerifier].asInstanceOf[Class[HostnameVerifier]])
98 | }
99 | //SSL.httpsConnectionContext initializes config which is not there in cli test
100 | //So inline the flow as we do not need client auth for this case
101 | new HttpsConnectionContext(SSL.nonValidatingContext(false), Some(sslConfig))
102 | }
103 |
104 | private def proxyRequest(req: HttpRequest, uri: Uri): HttpRequest = {
105 | //https://github.com/akka/akka-http/issues/64
106 | req
107 | .withHeaders(headers = req.headers.filterNot(h => h.is("timeout-access")))
108 | .withUri(uri = req.uri.copy(scheme = "", authority = Authority.Empty)) //Strip the authority as it refers to proxy
109 | }
110 |
111 | private def freePort(): Int = {
112 | val socket = new ServerSocket(0)
113 | try socket.getLocalPort
114 | finally if (socket != null) socket.close()
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/tests/src/test/scala/system/basic/WskCliActionTests.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package system.basic
19 |
20 | import org.junit.runner.RunWith
21 | import org.scalatest.junit.JUnitRunner
22 |
23 | import common.TestUtils
24 | import common.TestUtils.NOT_ALLOWED
25 | import common.Wsk
26 |
27 | @RunWith(classOf[JUnitRunner])
28 | class WskCliActionTests extends WskActionTests {
29 | override val wsk = new Wsk
30 |
31 | it should "not be able to use --kind and --docker at the same time when running action create or update" in {
32 | val file = TestUtils.getTestActionFilename(s"echo.js")
33 | Seq(false, true).foreach { updateValue =>
34 | val out = wsk.action.create(
35 | name = "kindAndDockerAction",
36 | artifact = Some(file),
37 | expectedExitCode = NOT_ALLOWED,
38 | kind = Some("nodejs:6"),
39 | docker = Some("mydockerimagename"),
40 | update = updateValue)
41 | out.stderr should include("Cannot specify both --kind and --docker at the same time")
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tests/src/test/scala/system/basic/WskCliActivationTests.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package system.basic
18 |
19 | import akka.stream.scaladsl.{Sink, Source}
20 | import common.rest.WskRestOperations
21 | import common.{TestHelpers, TestUtils, Wsk, WskProps, WskTestHelpers}
22 | import org.junit.runner.RunWith
23 | import org.scalatest.junit.JUnitRunner
24 | import spray.json.DefaultJsonProtocol._
25 | import spray.json._
26 |
27 | import scala.concurrent._
28 | import scala.concurrent.duration._
29 | import scala.concurrent.Future
30 |
31 | @RunWith(classOf[JUnitRunner])
32 | class WskCliActivationTests extends TestHelpers with WskTestHelpers with HttpProxy {
33 | val wsk = new Wsk
34 | val wskRest = new WskRestOperations
35 | val defaultAction = Some(TestUtils.getTestActionFilename("hello.js"))
36 |
37 | behavior of "Wsk poll"
38 |
39 | implicit val wskprops: WskProps = WskProps()
40 |
41 | it should "change the since time as it polls" in withAssetCleaner(wskprops) {
42 | val name = "pollTest"
43 | (wp, assetHelper) =>
44 | assetHelper.withCleaner(wsk.action, name) { (action, _) =>
45 | action.create(name, Some(TestUtils.getTestActionFilename("hello.js")))
46 | }
47 |
48 | val args = Map("payload" -> "test".toJson)
49 | //This test spin up 2 parallel tasks
50 | // 1. Perform blocking invocations with 1 second interval
51 | // 2. Perform poll to pick up those activation results
52 | // For poll it inserts a proxy which intercepts the request sent to server
53 | // and then it asserts if the request sent have there `since` time getting changed or not
54 | withProxy { (proxyProps, requests) =>
55 | //It may taken some time for the activations to show up in poll result
56 | //based on view lag. So keep a bit longer time span for the poll
57 | val pollDuration = 10.seconds
58 | println(s"Running poll for $pollDuration")
59 |
60 | val consoleFuture = Future {
61 | //pass the `proxyProps` such that calls from poll cli command go via our proxy
62 | wsk.activation.console(pollDuration, actionName = Some(name))(proxyProps)
63 | }
64 |
65 | val runsFuture = Source(1 to 5)
66 | .map { _ =>
67 | val r = wskRest.action.invoke(name, args, blocking = true)
68 | Thread.sleep(2.second.toMillis)
69 | r
70 | }
71 | .runWith(Sink.seq)
72 |
73 | val f = for {
74 | rr <- runsFuture
75 | cr <- consoleFuture
76 | } yield (cr, rr)
77 |
78 | val (consoleResult, runResult) = Await.result(f, 1.minute)
79 |
80 | val activations = runResult.filter(_.statusCode.isSuccess()).map(_.respData.parseJson.asJsObject)
81 | val ids = activations.flatMap(_.fields.get("activationId").map(_.convertTo[String]))
82 | val idsInPoll = ids.filter(consoleResult.stdout.contains(_))
83 |
84 | //There should be more than 1 activationId in common between poll output
85 | //and actual invoked actions output
86 | //This is required to ensure that since time can change which would only
87 | //happen if more than one activation result is picked up in poll
88 | withClue(
89 | s"activations received ${activations.mkString("\n")}, console output $consoleResult. Expecting" +
90 | s"more than one matching activation between these 2") {
91 | idsInPoll.size should be > 1
92 |
93 | //Collect the 'since' value passed during poll requests
94 | val sinceTimes = requests.map(_._1.uri.query()).flatMap(_.get("since")).toSet
95 |
96 | withClue(s"value of 'since' $sinceTimes should have changed") {
97 | sinceTimes.size should be > 1
98 | }
99 | }
100 | }
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/tests/src/test/scala/system/basic/WskCliConsoleTests.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package system.basic;
19 |
20 | import org.junit.runner.RunWith
21 | import org.scalatest.junit.JUnitRunner
22 | import common.Wsk
23 |
24 | /**
25 | * Tests of the text console
26 | */
27 | @RunWith(classOf[JUnitRunner])
28 | class WskCliConsoleTests extends WskConsoleTests {
29 | override val wsk = new Wsk
30 | }
31 |
--------------------------------------------------------------------------------
/tests/src/test/scala/system/basic/WskCliPackageTests.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package system.basic
19 |
20 | import org.junit.runner.RunWith
21 | import org.scalatest.junit.JUnitRunner
22 |
23 | import common.Wsk
24 |
25 | @RunWith(classOf[JUnitRunner])
26 | class WskCliPackageTests extends WskPackageTests {
27 | override val wsk = new Wsk
28 | }
29 |
--------------------------------------------------------------------------------
/tests/src/test/scala/system/basic/WskCliRuleTests.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package system.basic
19 |
20 | import org.junit.runner.RunWith
21 | import org.scalatest.junit.JUnitRunner
22 |
23 | import common.Wsk
24 |
25 | @RunWith(classOf[JUnitRunner])
26 | class WskCliRuleTests extends WskRuleTests {
27 | override val wsk = new Wsk
28 | }
29 |
--------------------------------------------------------------------------------
/tests/src/test/scala/system/basic/WskCliSequenceTests.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package system.basic
19 |
20 | import org.junit.runner.RunWith
21 | import org.scalatest.junit.JUnitRunner
22 |
23 | import common.Wsk
24 |
25 | /**
26 | * Tests sequence execution
27 | */
28 | @RunWith(classOf[JUnitRunner])
29 | class WskCliSequenceTests extends WskSequenceTests {
30 | override val wsk = new Wsk
31 | }
32 |
--------------------------------------------------------------------------------
/tests/src/test/scala/system/basic/WskSdkTests.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package system.basic
19 |
20 | import java.io.File
21 | import java.io.BufferedWriter
22 | import java.io.FileWriter
23 |
24 | import org.apache.commons.io.FileUtils
25 | import org.junit.runner.RunWith
26 | import org.scalatest.junit.JUnitRunner
27 | import common.TestHelpers
28 | import common.TestUtils.ERROR_EXIT
29 | import common.TestUtils.SUCCESS_EXIT
30 | import common.Wsk
31 | import common.WskProps
32 | import common.WskTestHelpers
33 | import java.nio.charset.StandardCharsets
34 |
35 | @RunWith(classOf[JUnitRunner])
36 | class WskSdkTests extends TestHelpers with WskTestHelpers {
37 |
38 | implicit val wskprops = WskProps()
39 | val wsk = new Wsk
40 |
41 | behavior of "Wsk SDK"
42 |
43 | it should "prefix https to apihost if no scheme given" in {
44 | val result = wsk.cli(Seq("--apihost", "localhost:54321", "sdk", "install", "docker"), expectedExitCode = ERROR_EXIT)
45 | result.stderr should include regex ("""(?i)Get [\"]https://localhost:54321/""")
46 | }
47 |
48 | it should "not prefix https to http apihost" in {
49 | val result =
50 | wsk.cli(Seq("--apihost", "http://localhost:54321", "sdk", "install", "docker"), expectedExitCode = ERROR_EXIT)
51 | result.stderr should include regex ("""(?i)Get [\"]http://localhost:54321/""")
52 | }
53 |
54 | it should "not double prefix https to https apihost" in {
55 | val result =
56 | wsk.cli(Seq("--apihost", "https://localhost:54321", "sdk", "install", "docker"), expectedExitCode = ERROR_EXIT)
57 | result.stderr should include regex ("""(?i)Get [\"]https://localhost:54321/""")
58 | }
59 |
60 | it should "download docker action sdk" in {
61 | val dir = File.createTempFile("wskinstall", ".tmp")
62 | dir.delete()
63 | dir.mkdir() should be(true)
64 | try {
65 | wsk.cli(wskprops.overrides ++ Seq("sdk", "install", "docker"), workingDir = dir).stdout should include(
66 | "The docker skeleton is now installed at the current directory.")
67 |
68 | val sdk = new File(dir, "dockerSkeleton")
69 | sdk.exists() should be(true)
70 | sdk.isDirectory() should be(true)
71 |
72 | val dockerfile = new File(sdk, "Dockerfile")
73 | dockerfile.exists() should be(true)
74 | dockerfile.isFile() should be(true)
75 | val lines = FileUtils.readLines(dockerfile, StandardCharsets.UTF_8)
76 | // confirm that the image is correct
77 | lines.get(1) shouldBe "FROM openwhisk/dockerskeleton"
78 |
79 | val buildAndPushFile = new File(sdk, "buildAndPush.sh")
80 | buildAndPushFile.canExecute() should be(true)
81 | } finally {
82 | FileUtils.deleteDirectory(dir)
83 | }
84 | }
85 |
86 | it should "install the bash auto-completion bash script" in {
87 | // Use a temp dir for testing to not disturb user's local folder
88 | val dir = File.createTempFile("wskinstall", ".tmp")
89 | dir.delete()
90 | dir.mkdir() should be(true)
91 |
92 | val scriptfilename = "wsk_cli_bash_completion.sh"
93 | var scriptfile = new File(dir.getPath(), scriptfilename)
94 | try {
95 | val stdout = wsk.cli(Seq("sdk", "install", "bashauto"), workingDir = dir, expectedExitCode = SUCCESS_EXIT).stdout
96 | stdout should include("is installed in the current directory")
97 | val fileContent = FileUtils.readFileToString(scriptfile, StandardCharsets.UTF_8)
98 | fileContent should include("bash completion for wsk")
99 | } finally {
100 | scriptfile.delete()
101 | FileUtils.deleteDirectory(dir)
102 | }
103 | }
104 |
105 | it should "print bash command completion script to STDOUT" in {
106 | val msg = "bash completion for wsk" // Subject to change, dependent on Cobra script
107 |
108 | val stdout = wsk.cli(Seq("sdk", "install", "bashauto", "--stdout")).stdout
109 | stdout should include(msg)
110 | }
111 |
112 | def verifyMissingSecurityFile(config: String, fileName: String, expectedErrorMessage: String) = {
113 | val tmpwskprops = File.createTempFile("wskprops", ".tmp")
114 | val securityFile = File.createTempFile(fileName, ".pem")
115 | try {
116 | val writer = new BufferedWriter(new FileWriter(tmpwskprops))
117 | writer.write(s"$config=${securityFile.getAbsolutePath()}\n")
118 | writer.close()
119 | val env = Map("WSK_CONFIG_FILE" -> tmpwskprops.getAbsolutePath())
120 | val stderr = wsk
121 | .cli(
122 | Seq("sdk", "install", "docker", "--apihost", wskprops.apihost, "--apiversion", wskprops.apiversion),
123 | env = env,
124 | expectedExitCode = ERROR_EXIT)
125 | .stderr
126 | stderr should include regex (expectedErrorMessage)
127 | } finally {
128 | tmpwskprops.delete()
129 | securityFile.delete()
130 | }
131 | }
132 |
133 | it should "return configure the missing Key file" in {
134 | verifyMissingSecurityFile("CERT", "cert", "The Key file is not configured. Please configure the missing Key file.")
135 | }
136 |
137 | it should "return configure the missing Cert file" in {
138 | verifyMissingSecurityFile("KEY", "key", "The Cert file is not configured. Please configure the missing Cert file.")
139 | }
140 |
141 | it should "return unable to load the X509 key pair with both Cert and Key files missing" in {
142 | val tmpwskprops = File.createTempFile("wskprops", ".tmp")
143 | val certFile = File.createTempFile("cert", ".pem")
144 | val keyFile = File.createTempFile("key", ".pem")
145 | try {
146 | val writer = new BufferedWriter(new FileWriter(tmpwskprops))
147 | writer.write(s"CERT=${certFile.getAbsolutePath()}\n")
148 | writer.write(s"KEY=${keyFile.getAbsolutePath()}\n")
149 | writer.close()
150 | val env = Map("WSK_CONFIG_FILE" -> tmpwskprops.getAbsolutePath())
151 | val stderr = wsk
152 | .cli(
153 | Seq("sdk", "install", "docker", "--apihost", wskprops.apihost, "--apiversion", wskprops.apiversion),
154 | env = env,
155 | expectedExitCode = ERROR_EXIT)
156 | .stderr
157 | stderr should include regex ("""Unable to load the X509 key pair due to the following reason""")
158 | } finally {
159 | tmpwskprops.delete()
160 | certFile.delete()
161 | keyFile.delete()
162 | }
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/tools/git/pre-commit-gofmt.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | #
3 | # Licensed to the Apache Software Foundation (ASF) under one or more
4 | # contributor license agreements. See the NOTICE file distributed with
5 | # this work for additional information regarding copyright ownership.
6 | # The ASF licenses this file to You under the Apache License, Version 2.0
7 | # (the "License"); you may not use this file except in compliance with
8 | # the License. You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | set -e
20 |
21 | ROOT_DIR="$(git rev-parse --show-toplevel)"
22 |
23 | set +e
24 | FILE_EXT=".go"
25 | STAGED_FILES=$(git diff --name-only --no-color --diff-filter=d --exit-code -- "${ROOT_DIR}/*$FILE_EXT")
26 | STAGED_FILES_DETECTED=$?
27 | set -e
28 |
29 | if [ "${STAGED_FILES_DETECTED}" -eq 1 ]; then
30 | # Re-format and re-add all staged files
31 | for FILE in ${STAGED_FILES}
32 | do
33 | gofmt -s -w "${ROOT_DIR}/${FILE}"
34 | git add -- "${ROOT_DIR}/${FILE}"
35 | done
36 | fi
37 |
38 | exit 0
39 |
--------------------------------------------------------------------------------
/tools/travis/test_openwhisk.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Licensed to the Apache Software Foundation (ASF) under one or more
4 | # contributor license agreements. See the NOTICE file distributed with
5 | # this work for additional information regarding copyright ownership.
6 | # The ASF licenses this file to You under the Apache License, Version 2.0
7 | # (the "License"); you may not use this file except in compliance with
8 | # the License. You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | set -e
20 |
21 | #
22 | # At this point, the Travis build should already have built the binaries and
23 | # the release. If you're running manually, this command should get you to
24 | # the same place:
25 | #
26 | # ./gradlew releaseBinaries
27 | #
28 | # Also at this point, you should already have the openwhisk main repo. pulled down
29 | # from gradle in the parent directory, using a command such as:
30 | #
31 | # git clone --depth 3 https://github.com/apache/openwhisk.git
32 | #
33 | # To be clear, your directory structure will look something like...
34 | #
35 | # $HOMEDIR
36 | # |- openwhisk
37 | # |- openwhisk-cli (This project)
38 | # |- openwhisk-utilities (For scancode)
39 | #
40 | # The idea is to only build once and to be transparent about building in
41 | # the Travis script. To that end, some of the other builds that had been
42 | # done in this script will be moved into Travis.yml.
43 | #
44 |
45 | #
46 | # Determine default directories, etc., so we're not beholden to Travis
47 | # when running tests of the script during the development cycle.
48 | #
49 | openwhisk_cli_tag=${1:-"latest"}
50 | scriptdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
51 |
52 | TRAVIS_BUILD_DIR="$( cd "${TRAVIS_BUILD_DIR:-$scriptdir/../..}" && pwd )"
53 | export TRAVIS_BUILD_DIR
54 |
55 | # For the gradle builds.
56 | HOMEDIR="$(dirname "$TRAVIS_BUILD_DIR")"
57 | OPENWHISK_HOME="$( cd "${OPENWHISK_HOME:-$HOMEDIR/openwhisk}" && pwd )"
58 | export OPENWHISK_HOME
59 |
60 | #
61 | # Run scancode using the ASF Release configuration
62 | #
63 | UTILDIR="$( cd "${UTILDIR:-$HOMEDIR/openwhisk-utilities}" && pwd )"
64 | export UTILDIR
65 | cd $UTILDIR
66 | scancode/scanCode.py --config scancode/ASF-Release.cfg $TRAVIS_BUILD_DIR
67 |
68 | #
69 | # Run Golint
70 | #
71 | cd $TRAVIS_BUILD_DIR
72 | ./gradlew --console=plain goLint
73 |
74 | #
75 | # Run Unit and native tests
76 | #
77 | ./gradlew --console=plain --info goTest -PgoTags=unit
78 | ./gradlew --console=plain --info goTest -PgoTags=native
79 |
80 | #
81 | # Set up the OpenWhisk environment for integration testing
82 | #
83 | cd $OPENWHISK_HOME
84 |
85 | # Build openwhisk image to keep test case code consistent with latest openwhisk core code
86 | ./gradlew distDocker -PdockerImagePrefix=openwhisk -PdockerImageTag=latest
87 |
88 | # Install Ansible and other pre-reqs
89 | #./tools/travis/setup.sh
90 |
91 | # Fire up the cluster
92 | echo 'limit_invocations_per_minute: 120' >> $OPENWHISK_HOME/ansible/environments/local/group_vars/all
93 | ANSIBLE_CMD="ansible-playbook -i environments/local -e docker_image_prefix=openwhisk -e docker_image_tag=latest"
94 | cd $OPENWHISK_HOME/ansible
95 | $ANSIBLE_CMD setup.yml
96 | $ANSIBLE_CMD prereq.yml
97 | $ANSIBLE_CMD couchdb.yml
98 | $ANSIBLE_CMD initdb.yml
99 | $ANSIBLE_CMD wipe.yml
100 | $ANSIBLE_CMD elasticsearch.yml
101 | $ANSIBLE_CMD etcd.yml
102 | $ANSIBLE_CMD openwhisk.yml -e cli_tag=$openwhisk_cli_tag -e cli_installation_mode=local -e openwhisk_cli_home=$TRAVIS_BUILD_DIR -e controller_protocol=http -e db_activation_backend=ElasticSearch
103 | $ANSIBLE_CMD properties.yml
104 | $ANSIBLE_CMD apigateway.yml
105 | $ANSIBLE_CMD routemgmt.yml
106 |
107 | # avoid does not find pureconfig during testing CLI tests
108 | cat <> $TRAVIS_BUILD_DIR/tests/src/test/resources/application.conf
109 | whisk {
110 | controller {
111 | https {
112 | keystore-flavor = "PKCS12"
113 | keystore-path = "$OPENWHISK_HOME/ansible/roles/controller/files/controller-openwhisk-keystore.p12"
114 | keystore-password = "openwhisk"
115 | client-auth = "true"
116 | }
117 | }
118 | }
119 | EOT
120 |
121 | # Run the test cases under openwhisk to ensure the quality of the runnint API.
122 | cd $TRAVIS_BUILD_DIR
123 | ./gradlew --console=plain :tests:test --tests=*ApiGwCliTests*
124 | sleep 30
125 | ./gradlew --console=plain :tests:test --tests=*ApiGwCliRoutemgmtActionTests*
126 | sleep 30
127 | ./gradlew --console=plain :tests:test --tests=*ApiGwCliEndToEndTests*
128 | sleep 30
129 | ./gradlew --console=plain :tests:test --tests=*Wsk*Tests*
130 |
131 | #
132 | # Finally, run the integration test for the CLI
133 | #
134 | ./gradlew --console=plain --info goTest -PgoTags=unit
135 | ./gradlew --console=plain --info goTest -PgoTags=integration
136 |
--------------------------------------------------------------------------------
/wski18n/detection.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package wski18n
19 |
20 | import "github.com/cloudfoundry/jibber_jabber"
21 |
22 | type Detector interface {
23 | DetectLocale() string
24 | DetectLanguage() string
25 | }
26 |
27 | type JibberJabberDetector struct{}
28 |
29 | func (d *JibberJabberDetector) DetectLocale() string {
30 | userLocale, err := jibber_jabber.DetectIETF()
31 | if err != nil {
32 | userLocale = ""
33 | }
34 | return userLocale
35 | }
36 |
37 | func (d *JibberJabberDetector) DetectLanguage() string {
38 | lang, err := jibber_jabber.DetectLanguage()
39 | if err != nil {
40 | lang = ""
41 | }
42 | return lang
43 | }
44 |
--------------------------------------------------------------------------------
/wski18n/i18n.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package wski18n
19 |
20 | import (
21 | "path/filepath"
22 | "strings"
23 |
24 | goi18n "github.com/nicksnyder/go-i18n/i18n"
25 | )
26 |
27 | const (
28 | DEFAULT_LOCALE = "en_US"
29 | )
30 |
31 | var SUPPORTED_LOCALES = []string{
32 | "de_DE",
33 | "en_US",
34 | "es_ES",
35 | "fr_FR",
36 | "it_IT",
37 | "ja_JA",
38 | "ko_KR",
39 | "pt_BR",
40 | "zh_Hans",
41 | "zh_Hant",
42 | }
43 |
44 | var resourcePath = filepath.Join("wski18n", "resources")
45 |
46 | func GetResourcePath() string {
47 | return resourcePath
48 | }
49 |
50 | func SetResourcePath(path string) {
51 | resourcePath = path
52 | }
53 |
54 | var T goi18n.TranslateFunc
55 | var curLocale string
56 |
57 | func init() {
58 | curLocale = Init(new(JibberJabberDetector))
59 | }
60 |
61 | func CurLocale() string {
62 | return curLocale
63 | }
64 |
65 | func Locale(detector Detector) string {
66 |
67 | // Use default locale until strings are translated
68 | /*sysLocale := normalize(detector.DetectLocale())
69 | if isSupported(sysLocale) {
70 | return sysLocale
71 | }
72 |
73 | locale := defaultLocaleForLang(detector.DetectLanguage())
74 | if locale != "" {
75 | return locale
76 | }*/
77 |
78 | return DEFAULT_LOCALE
79 | }
80 |
81 | func Init(detector Detector) string {
82 | l := Locale(detector)
83 | InitWithLocale(l)
84 | return l
85 | }
86 |
87 | func InitWithLocale(locale string) {
88 | err := loadFromAsset(locale)
89 | if err != nil {
90 | panic(err)
91 | }
92 | T = goi18n.MustTfunc(locale)
93 | }
94 |
95 | func loadFromAsset(locale string) (err error) {
96 | assetName := locale + ".all.json"
97 | assetKey := filepath.Join(resourcePath, assetName)
98 | bytes, err := Asset(assetKey)
99 | if err != nil {
100 | return
101 | }
102 | err = goi18n.ParseTranslationFileBytes(assetName, bytes)
103 | return
104 | }
105 |
106 | func normalize(locale string) string {
107 | locale = strings.ToLower(strings.Replace(locale, "-", "_", 1))
108 | for _, l := range SUPPORTED_LOCALES {
109 | if strings.EqualFold(locale, l) {
110 | return l
111 | }
112 | }
113 | switch locale {
114 | case "zh_cn", "zh_sg":
115 | return "zh_Hans"
116 | case "zh_hk", "zh_tw":
117 | return "zh_Hant"
118 | }
119 | return locale
120 | }
121 |
122 | func isSupported(locale string) bool {
123 | for _, l := range SUPPORTED_LOCALES {
124 | if strings.EqualFold(locale, l) {
125 | return true
126 | }
127 | }
128 | return false
129 | }
130 |
131 | func defaultLocaleForLang(lang string) string {
132 | if lang != "" {
133 | lang = strings.ToLower(lang)
134 | for _, l := range SUPPORTED_LOCALES {
135 | if lang == LangOfLocale(l) {
136 | return l
137 | }
138 | }
139 | }
140 | return ""
141 | }
142 |
143 | func LangOfLocale(locale string) string {
144 | if len(locale) < 2 {
145 | return ""
146 | }
147 | return locale[0:2]
148 | }
149 |
--------------------------------------------------------------------------------
/wski18n/resources/de_DE.all.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apache/openwhisk-cli/23f3cf73b812b75821122d9db94e9eebda6bab54/wski18n/resources/de_DE.all.json
--------------------------------------------------------------------------------
/wski18n/resources/es_ES.all.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apache/openwhisk-cli/23f3cf73b812b75821122d9db94e9eebda6bab54/wski18n/resources/es_ES.all.json
--------------------------------------------------------------------------------
/wski18n/resources/fr_FR.all.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "bypass certificate checking",
4 | "translation": "Some translation in French"
5 | }
6 | ]
7 |
--------------------------------------------------------------------------------
/wski18n/resources/it_IT.all.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apache/openwhisk-cli/23f3cf73b812b75821122d9db94e9eebda6bab54/wski18n/resources/it_IT.all.json
--------------------------------------------------------------------------------
/wski18n/resources/ja_JA.all.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apache/openwhisk-cli/23f3cf73b812b75821122d9db94e9eebda6bab54/wski18n/resources/ja_JA.all.json
--------------------------------------------------------------------------------
/wski18n/resources/ko_KR.all.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apache/openwhisk-cli/23f3cf73b812b75821122d9db94e9eebda6bab54/wski18n/resources/ko_KR.all.json
--------------------------------------------------------------------------------
/wski18n/resources/pt_BR.all.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apache/openwhisk-cli/23f3cf73b812b75821122d9db94e9eebda6bab54/wski18n/resources/pt_BR.all.json
--------------------------------------------------------------------------------
/wski18n/resources/zh_Hans.all.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apache/openwhisk-cli/23f3cf73b812b75821122d9db94e9eebda6bab54/wski18n/resources/zh_Hans.all.json
--------------------------------------------------------------------------------
/wski18n/resources/zh_Hant.all.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apache/openwhisk-cli/23f3cf73b812b75821122d9db94e9eebda6bab54/wski18n/resources/zh_Hant.all.json
--------------------------------------------------------------------------------