├── .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 | [![Build Status](https://travis-ci.com/apache/openwhisk-cli.svg?branch=master)](https://travis-ci.com/apache/openwhisk-cli) 23 | [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0) 24 | [![Join Slack](https://img.shields.io/badge/join-slack-9B69A0.svg)](http://slack.openwhisk.org/) 25 | [![Twitter](https://img.shields.io/twitter/follow/openwhisk.svg?style=social&logo=twitter)](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 --------------------------------------------------------------------------------