├── .asf.yaml ├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── Makefile ├── NOTICE.txt ├── README.md ├── example └── example_list_actions.go ├── go.mod ├── go.sum ├── whisk ├── action.go ├── action_test.go ├── activation.go ├── api.go ├── client.go ├── client_test.go ├── info.go ├── keyvaluearr_test.go ├── namespace.go ├── package.go ├── rule.go ├── sdk.go ├── shared.go ├── start.go ├── start_test.go ├── trace.go ├── trigger.go ├── trigger_test.go ├── util.go ├── wskerror.go ├── wskprops.go └── wskprops_test.go └── wski18n ├── detection.go ├── i18n.go ├── i18n_resources.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: "Go client library for the Apache OpenWhisk platform" 20 | homepage: https://openwhisk.apache.org/ 21 | labels: 22 | - openwhisk 23 | - apache 24 | - serverless 25 | - faas 26 | - functions-as-a-service 27 | - cloud 28 | - serverless-architectures 29 | - serverless-functions 30 | protected_branches: 31 | master: 32 | required_status_checks: 33 | strict: false 34 | required_pull_request_reviews: 35 | required_approving_review_count: 1 36 | required_signatures: false 37 | enabled_merge_buttons: 38 | merge: false 39 | squash: true 40 | rebase: true 41 | features: 42 | issues: true 43 | 44 | notifications: 45 | commits: commits@openwhisk.apache.org 46 | issues_status: issues@openwhisk.apache.org 47 | issues_comment: issues@openwhisk.apache.org 48 | pullrequests_status: issues@openwhisk.apache.org 49 | pullrequests_comment: issues@openwhisk.apache.org 50 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with 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, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | # 18 | 19 | name: Continuous Integration 20 | 21 | on: 22 | push: 23 | branches: [ master ] 24 | tags: [ '*' ] 25 | pull_request: 26 | branches: [ master ] 27 | types: [ opened, synchronize, reopened ] 28 | schedule: 29 | - cron: '30 1 * * 1,3,5' 30 | 31 | permissions: read-all 32 | 33 | jobs: 34 | ci: 35 | runs-on: ubuntu-22.04 36 | steps: 37 | # Checkout just this repo and run scanCode before we do anything else 38 | - name: Checkout client-go repo 39 | uses: actions/checkout@v4 40 | with: 41 | path: client-go 42 | - name: Scan Code 43 | uses: apache/openwhisk-utilities/scancode@master 44 | 45 | # Configure Go environment 46 | - name: Set up Go 47 | uses: actions/setup-go@v5 48 | with: 49 | go-version-file: './client-go/go.mod' 50 | 51 | # Run unit tests for this repo 52 | - name: Run unit tests 53 | working-directory: client-go 54 | run: | 55 | make test 56 | 57 | # Run the intergration tests 58 | - name: Run integration tests 59 | working-directory: client-go 60 | run: | 61 | make integration_test 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | vendor/github.com 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 19 | 20 | # Apache OpenWhisk Client Go 21 | 22 | ## 1.2.0 23 | 24 | - Update for travis migration (#140) 25 | - Add omit tag to ErrorResponse.Response field (#121) 26 | - update status code if action returns error (#142) 27 | - Migrate to Go Modules (#143) 28 | - Update docs & provide example code (#144) 29 | - Update testify dep. version and clarify use of go get (#145) 30 | - Update go.mod to use go v1.15 (#146) 31 | 32 | ## 1.1.0 33 | 34 | - Add DelAnnotations field to support del annotation (#137) 35 | - Bump up go version to 1.13.14 for travis (#138) 36 | - Add `updated` field on package, rule, and trigger entity (#135) 37 | - Add updated field on Action struct (#129) 38 | - Parse an action's application error (#133) 39 | - Support alt. namespace resource uuid as tenant id to API gateway service (#130) 40 | 41 | ## 1.0.0 42 | 43 | - Handle err return from url.Parse in GetUrlBase (#123) 44 | - Add dynamic column sizing to wsk activation list command (#120) 45 | 46 | ## 0.10.0-incubating 47 | 48 | - Added extra columns to activation records summary rows (#116) 49 | - Replace godep with govendor (#113) 50 | - Load X509 cert on client creation (#112) 51 | - Add Concurrency to Limits (#94) 52 | - Fix invalid warning message (#91) 53 | - Allow NewClient to run concurrently (#103) 54 | - Update Go Version (#104) 55 | - Allow additional headers to override auth header val (#100) 56 | - Replace trigger service type with interface (#99) 57 | 58 | ## 0.9.0-incubating 59 | 60 | - Initial Apache Release 61 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 19 | 20 | # Contributing to Apache OpenWhisk 21 | 22 | Anyone can contribute to the OpenWhisk project and we welcome your contributions. 23 | 24 | There are multiple ways to contribute: report bugs, improve the docs, and 25 | contribute code, but you must follow these prerequisites and guidelines: 26 | 27 | - [Contributor License Agreement](#contributor-license-agreement) 28 | - [Raising issues](#raising-issues) 29 | - [Coding Standards](#coding-standards) 30 | 31 | ### Contributor License Agreement 32 | 33 | All contributors must sign and submit an Apache CLA (Contributor License Agreement). 34 | 35 | Instructions on how to do this can be found here: 36 | [http://www.apache.org/licenses/#clas](http://www.apache.org/licenses/#clas) 37 | 38 | Once submitted, you will receive a confirmation email from the Apache Software Foundation (ASF) and be added to 39 | the following list: http://people.apache.org/unlistedclas.html. 40 | 41 | Project committers will use this list to verify pull requests (PRs) come from contributors that have signed a CLA. 42 | 43 | We look forward to your contributions! 44 | 45 | ## Raising issues 46 | 47 | Please raise any bug reports on the respective project repository's GitHub issue tracker. Be sure to search the 48 | list to see if your issue has already been raised. 49 | 50 | A good bug report is one that make it easy for us to understand what you were trying to do and what went wrong. 51 | Provide as much context as possible so we can try to recreate the issue. 52 | 53 | ### Discussion 54 | 55 | Please use the project's developer email list to engage our community: 56 | [dev@openwhisk.apache.org](dev@openwhisk.apache.org) 57 | 58 | In addition, we provide a "dev" Slack team channel for conversations at: 59 | https://openwhisk-team.slack.com/messages/dev/ 60 | 61 | ### Coding standards 62 | 63 | Please ensure you follow the coding standards used throughout the existing 64 | code base. Some basic rules include: 65 | 66 | - all files must have the Apache license in the header. 67 | - all PRs must have passing builds for all operating systems. 68 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 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 | deps: 19 | @echo "Installing dependencies" 20 | go get -t ./... 21 | 22 | updatedeps: 23 | @echo "Updating all dependencies" 24 | @go get -u -t ./... 25 | 26 | test: deps 27 | @echo "Testing" 28 | go test -v ./... -tags=unit 29 | 30 | # Run the integration test against OpenWhisk 31 | integration_test: 32 | @echo "Launch the integration tests." 33 | go test -v ./... -tags=integration 34 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Apache OpenWhisk Client Go 2 | Copyright 2016-2024 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 Client Go 21 | [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0) 22 | [![Continuous Integration](https://github.com/apache/openwhisk-client-go/actions/workflows/ci.yaml/badge.svg)](https://github.com/apache/openwhisk-client-go/actions/workflows/ci.yaml) 23 | 24 | This project `openwhisk-client-go` is a Go client library to access the Openwhisk API. 25 | 26 | --- 27 | 28 | ## Building the project 29 | 30 | ### Prerequisites 31 | 32 | The Openwhisk Go Client library requires you to [Download and install GoLang](https://golang.org/dl/) onto your local machine. 33 | 34 | Make sure you select the package that fits your local environment, and [set the GOPATH environment variable](https://go.dev/wiki/SettingGOPATH). 35 | 36 | ### Download the source code from GitHub 37 | 38 | As the code is managed using GitHub, it is easiest to retrieve the code using the `git clone` command. 39 | 40 | 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: 41 | 42 | ```sh 43 | git clone git@github.com:apache/openwhisk-client-go 44 | ``` 45 | 46 | You can also 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-client-go/releases) 47 | 48 | ```sh 49 | git clone --branch 1.1.0 git@github.com:apache/openwhisk-client-go 50 | ``` 51 | 52 | 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. 53 | 54 | ### Building using `go build` 55 | 56 | Change into the cloned project directory and use the following command to build all packages: 57 | 58 | ```sh 59 | $ go build -v ./... 60 | ``` 61 | 62 | or simply build just the whisk commands: 63 | 64 | ```sh 65 | $ go build -v ./whisk 66 | ``` 67 | 68 | > **Note**: There is no `main` function in this project as the `./whisk` packages are treated together as a client library. 69 | 70 | ### Testing using `go test` 71 | 72 | Open a terminal, change into the project directory and use the following command to run the unit tests: 73 | 74 | ```sh 75 | $ go test -v ./... -tags=unit 76 | ``` 77 | 78 | You should see all the unit tests passed; if not, please [log an issue](https://github.com/apache/openwhisk-client-go/issues) for us. 79 | 80 | --- 81 | 82 | ## Configuration 83 | 84 | This Go client library is used to access the OpenWhisk API, so please make sure you have an OpenWhisk service running somewhere 85 | available for you to run this library. 86 | 87 | We use a configuration file called _wskprop_ to specify all the parameters necessary for this Go client library to access the OpenWhisk services. Make sure you create or edit the file _~/.wskprops_, and add the mandatory parameters APIHOST, APIVERSION, NAMESPACE and AUTH. 88 | 89 | - The parameter `APIHOST` is the OpenWhisk API hostname. 90 | - If you are using a local [quick start standalone](https://github.com/apache/openwhisk#quick-start), OpenWhisk services APIHOST will look like `http://localhost:3233` 91 | - If you are using IBM cloud functions as your provider, APIHOST will look like `.functions.cloud.ibm.com` where region can be `us-east`, `us-south` or any additional [regions](https://cloud.ibm.com/docs/openwhisk?topic=openwhisk-cloudfunctions_regions) 92 | 93 | - The parameter `APIVERSION` is the version of OpenWhisk API to be used to access the OpenWhisk resources. 94 | - The parameter `NAMESPACE` is the OpenWhisk namespace used to specify the OpenWhisk resources about to be accessed. 95 | - The parameter `AUTH` is the authentication key used to authenticate the incoming requests to the OpenWhisk services. 96 | 97 | For more information regarding the REST API of OpenWhisk, please refer to [OpenWhisk REST API](https://github.com/apache/openwhisk/blob/master/docs/rest_api.md). 98 | 99 | ## Usage 100 | 101 | ```go 102 | import "github.com/apache/openwhisk-client-go/whisk" 103 | ``` 104 | 105 | Construct a new whisk client, then use various services to access different parts of the whisk api. For example to get the `hello` package actions: 106 | 107 | ```go 108 | client, _ := whisk.NewClient(http.DefaultClient, nil) 109 | actions, resp, err := client.Actions.List("hello", nil) 110 | ``` 111 | 112 | Some API methods have optional parameters that can be passed. For example, to list the first 10 actions of the `hello` package: 113 | ```go 114 | client, _ := whisk.NewClient(http.DefaultClient, nil) 115 | 116 | options := &whisk.ActionListOptions{ 117 | Limit: 10, 118 | Skip: 0, 119 | } 120 | 121 | actions, resp, err := client.Actions.List("hello", options) 122 | ``` 123 | 124 | By default, this Go client library is automatically configured by the configuration file _wskprop_. The parameters of APIHOST, APIVERSION, 125 | NAMESPACE and AUTH will be used to access the OpenWhisk services. 126 | 127 | In addition, it can also be configured by passing in a `*whisk.Config` object as the second argument to `whisk.New( ... )`. For example: 128 | 129 | ```go 130 | config := &whisk.Config{ 131 | Host: "", 132 | Version: "", 133 | Namespace: "", 134 | AuthToken: "", 135 | } 136 | client, err := whisk.Newclient(http.DefaultClient, config) 137 | ``` 138 | 139 | ### Example 140 | 141 | You need to have an OpenWhisk service accessible, to run the following [example](https://github.com/apache/openwhisk-client-go/blob/master/example/example_list_actions.go). 142 | 143 | ```go 144 | package main 145 | 146 | import ( 147 | "os" 148 | "fmt" 149 | "net/http" 150 | 151 | "github.com/apache/openwhisk-client-go/whisk" 152 | ) 153 | 154 | func main() { 155 | client, err := whisk.NewClient(http.DefaultClient, nil) 156 | if err != nil { 157 | fmt.Println(err) 158 | os.Exit(-1) 159 | } 160 | 161 | options := &whisk.ActionListOptions{ 162 | Limit: 10, 163 | Skip: 0, 164 | } 165 | 166 | actions, resp, err := client.Actions.List("", options) 167 | if err != nil { 168 | fmt.Println(err) 169 | os.Exit(-1) 170 | } 171 | 172 | fmt.Println("Returned with status: ", resp.Status) 173 | fmt.Printf("Returned actions: \n%+v", actions) 174 | 175 | } 176 | ``` 177 | 178 | Then run it with the `go` tool: 179 | 180 | ``` 181 | $ cd example 182 | $ go run example_list_actions.go 183 | ``` 184 | 185 | If the openWhisk service is available and your configuration is correct, you should receive the status and the actions with the above example. 186 | 187 | --- 188 | 189 | ## Contributing to the project 190 | 191 | ### Git repository setup 192 | 193 | 1. [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) the Apache repository 194 | 195 | If you intend to contribute code, you will want to fork the `apache/openwhisk-client-go` repository into your github account and use that as the source for your clone. 196 | 197 | 2. Clone the repository from your fork: 198 | 199 | ```sh 200 | git clone git@github.com:${GITHUB_ACCOUNT_USERNAME}/openwhisk-client-go.git 201 | ``` 202 | 203 | 3. Add the Apache repository as a remote with the `upstream` alias: 204 | 205 | ```sh 206 | git remote add upstream git@github.com:apache/openwhisk-client-go 207 | ``` 208 | 209 | You can now use `git push` to push local `commit` changes to your `origin` repository and submit pull requests to the `upstream` project repository. 210 | 211 | 4. Optionally, prevent accidental pushes to `upstream` using this command: 212 | 213 | ```sh 214 | git remote set-url --push upstream no_push 215 | ``` 216 | 217 | > 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. 218 | 219 | ### Adding new dependencies 220 | 221 | Please use `go get` to add new dependencies to the `go.mod` file: 222 | 223 | ```sh 224 | go get -u github.com/project/libname@v1.2.0 225 | ``` 226 | 227 | > Please avoid using commit hashes for referencing non-OpenWhisk libraries. 228 | 229 | ### Updating dependency versions 230 | 231 | Although you might be tempted to edit the go.mod file directly, please use the recommended method of using the `go get` command: 232 | 233 | ```sh 234 | go get -u github.com/project/libname # Using "latest" version 235 | go get -u github.com/project/libname@v1.1.0 # Using tagged version 236 | go get -u github.com/project/libname@aee5cab1c # Using a commit hash 237 | ``` 238 | 239 | ### Updating Go version 240 | 241 | Although you could edit the version directly in the go.mod file, it is better to use the `go edit` command: 242 | 243 | ```sh 244 | go mod edit -go=1.NN 245 | ``` 246 | -------------------------------------------------------------------------------- /example/example_list_actions.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 | "net/http" 23 | "os" 24 | 25 | "github.com/apache/openwhisk-client-go/whisk" 26 | ) 27 | 28 | func main() { 29 | 30 | client, err := whisk.NewClient(http.DefaultClient, nil) 31 | if err != nil { 32 | fmt.Println(err) 33 | os.Exit(-1) 34 | } 35 | 36 | options := &whisk.ActionListOptions{ 37 | Limit: 10, 38 | Skip: 0, 39 | } 40 | 41 | actions, resp, err := client.Actions.List("", options) 42 | if err != nil { 43 | fmt.Println(err) 44 | os.Exit(-1) 45 | } 46 | 47 | fmt.Println("Returned with status: ", resp.Status) 48 | fmt.Printf("Returned actions: \n %+v", actions) 49 | 50 | } 51 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/apache/openwhisk-client-go 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 7 | github.com/fatih/color v1.17.0 8 | github.com/google/go-querystring v1.1.0 9 | github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f 10 | github.com/nicksnyder/go-i18n v1.10.3 11 | github.com/stretchr/testify v1.9.0 12 | ) 13 | 14 | require ( 15 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 16 | github.com/mattn/go-colorable v0.1.13 // indirect 17 | github.com/mattn/go-isatty v0.0.20 // indirect 18 | github.com/onsi/ginkgo v1.15.0 // indirect 19 | github.com/onsi/gomega v1.10.5 // indirect 20 | github.com/pelletier/go-toml v1.9.5 // indirect 21 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 22 | golang.org/x/sys v0.26.0 // indirect 23 | gopkg.in/yaml.v2 v2.4.0 // indirect 24 | gopkg.in/yaml.v3 v3.0.1 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do= 2 | github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 5 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= 7 | github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= 8 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 9 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 10 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 11 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 12 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 13 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 14 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 15 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 16 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 17 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 18 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 19 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 20 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 21 | github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= 22 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 23 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 24 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 25 | github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= 26 | github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= 27 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 28 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 29 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 30 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 31 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 32 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 33 | github.com/nicksnyder/go-i18n v1.10.3 h1:0U60fnLBNrLBVt8vb8Q67yKNs+gykbQuLsIkiesJL+w= 34 | github.com/nicksnyder/go-i18n v1.10.3/go.mod h1:hvLG5HTlZ4UfSuVLSRuX7JRUomIaoKQM19hm6f+no7o= 35 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= 36 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 37 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 38 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 39 | github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4= 40 | github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= 41 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 42 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 43 | github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ= 44 | github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= 45 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 46 | github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= 47 | github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 48 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 49 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 50 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 51 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 52 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 53 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 54 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 55 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 56 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 57 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 58 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 59 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 60 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 61 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 62 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 63 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 64 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 65 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 66 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= 67 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 68 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 69 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 70 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 71 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 72 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 73 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 74 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 75 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 76 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 77 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 78 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 79 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 80 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 81 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 82 | golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= 83 | golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 84 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 85 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 86 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 87 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 88 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 89 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 90 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 91 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 92 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 93 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 94 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 95 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 96 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 97 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 98 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 99 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 100 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 101 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 102 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 103 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 104 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 105 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 106 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 107 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 108 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 109 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 110 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 111 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 112 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 113 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 114 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 115 | -------------------------------------------------------------------------------- /whisk/action.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 whisk 19 | 20 | import ( 21 | "errors" 22 | "fmt" 23 | "net/http" 24 | "net/url" 25 | "strings" 26 | 27 | "github.com/apache/openwhisk-client-go/wski18n" 28 | ) 29 | 30 | type ActionService struct { 31 | client ClientInterface 32 | } 33 | 34 | type Action struct { 35 | Namespace string `json:"namespace,omitempty"` 36 | Name string `json:"name,omitempty"` 37 | Version string `json:"version,omitempty"` 38 | Exec *Exec `json:"exec,omitempty"` 39 | Annotations KeyValueArr `json:"annotations,omitempty"` 40 | DelAnnotations []string `json:"delAnnotations,omitempty"` 41 | Parameters KeyValueArr `json:"parameters,omitempty"` 42 | Limits *Limits `json:"limits,omitempty"` 43 | Error string `json:"error,omitempty"` 44 | Code int `json:"code,omitempty"` 45 | Publish *bool `json:"publish,omitempty"` 46 | Updated int64 `json:"updated,omitempty"` 47 | Env KeyValueArr `json:"env,omitempty"` 48 | } 49 | 50 | type Exec struct { 51 | Kind string `json:"kind,omitempty"` 52 | Code *string `json:"code,omitempty"` 53 | Image string `json:"image,omitempty"` 54 | Init string `json:"init,omitempty"` 55 | Main string `json:"main,omitempty"` 56 | Components []string `json:"components,omitempty"` // List of fully qualified actions 57 | Binary *bool `json:"binary,omitempty"` 58 | } 59 | 60 | type ActionListOptions struct { 61 | Limit int `url:"limit"` 62 | Skip int `url:"skip"` 63 | Docs bool `url:"docs,omitempty"` 64 | } 65 | 66 | // Compare(sortable) compares action to sortable for the purpose of sorting. 67 | // REQUIRED: sortable must also be of type Action. 68 | // ***Method of type Sortable*** 69 | func (action Action) Compare(sortable Sortable) bool { 70 | // Sorts alphabetically by NAMESPACE -> PACKAGE_NAME -> ACTION_NAME, with 71 | // actions under default package at the top. 72 | var actionString string 73 | var compareString string 74 | actionToCompare := sortable.(Action) 75 | 76 | actionString = strings.ToLower(fmt.Sprintf("%s%s", action.Namespace, action.Name)) 77 | compareString = strings.ToLower(fmt.Sprintf("%s%s", actionToCompare.Namespace, 78 | actionToCompare.Name)) 79 | if strings.Contains(action.Namespace, "/") && !strings.Contains(actionToCompare.Namespace, "/") { 80 | return false 81 | } else if !strings.Contains(action.Namespace, "/") && strings.Contains(actionToCompare.Namespace, "/") { 82 | return true 83 | } else if strings.Contains(action.Namespace, "/") && strings.Contains(actionToCompare.Namespace, "/") { 84 | return actionString < compareString 85 | } else { 86 | return action.Name < actionToCompare.Name 87 | } 88 | } 89 | 90 | // ToHeaderString() returns the header for a list of actions 91 | func (action Action) ToHeaderString() string { 92 | return fmt.Sprintf("%s\n", "actions") 93 | } 94 | 95 | // ToSummaryRowString() returns a compound string of required parameters for printing 96 | // from CLI command `wsk action list`. 97 | // ***Method of type Sortable*** 98 | func (action Action) ToSummaryRowString() string { 99 | var kind string 100 | publishState := wski18n.T("private") 101 | 102 | for i := range action.Annotations { 103 | if action.Annotations[i].Key == "exec" { 104 | kind = action.Annotations[i].Value.(string) 105 | break 106 | } 107 | } 108 | return fmt.Sprintf("%-70s %s %s\n", fmt.Sprintf("/%s/%s", action.Namespace, action.Name), publishState, kind) 109 | } 110 | 111 | /* 112 | Determines if an action is a web action by examining the action's annotations. A value of true is returned if the 113 | action's annotations contains a "web-export" key and its associated value is a boolean value of "true". Otherwise, false 114 | is returned. 115 | */ 116 | func (action Action) WebAction() (webExportValue bool) { 117 | webExport := action.Annotations.GetValue("web-export") 118 | webExportValue, _ = webExport.(bool) 119 | 120 | Debug(DbgInfo, "Web export value is '%t'\n", webExportValue) 121 | 122 | return webExportValue 123 | } 124 | 125 | /* 126 | Returns the URL of an action as a string. A valid API host, path and version must be passed. A package that contains the 127 | action must be passed as well. An empty string must be passed if the action is not packaged. 128 | */ 129 | func (action Action) ActionURL(apiHost string, apiPath string, apiVersion string, pkg string) (string, error) { 130 | baseURL, err := GetURLBase(apiHost, apiPath) 131 | if err != nil { 132 | Debug(DbgError, "GetURLBase(%s, %s) failed: %s\n", apiHost, apiPath, err) 133 | return "", err 134 | } 135 | webActionPath := "%s/%s/web/%s/%s/%s" 136 | actionPath := "%s/%s/namespaces/%s/actions/%s" 137 | packagedActionPath := actionPath + "/%s" 138 | namespace := strings.Split(action.Namespace, "/")[0] 139 | namespace = strings.Replace(url.QueryEscape(namespace), "+", "%20", -1) 140 | name := strings.Replace(url.QueryEscape(action.Name), "+", "%20", -1) 141 | pkg = strings.Replace(url.QueryEscape(pkg), "+", "%20", -1) 142 | 143 | var actionURL string 144 | if action.WebAction() { 145 | if len(pkg) == 0 { 146 | pkg = "default" 147 | } 148 | 149 | actionURL = fmt.Sprintf(webActionPath, baseURL, apiVersion, namespace, pkg, name) 150 | Debug(DbgInfo, "Web action URL: %s\n", actionURL) 151 | } else { 152 | if len(pkg) == 0 { 153 | actionURL = fmt.Sprintf(actionPath, baseURL, apiVersion, namespace, name) 154 | Debug(DbgInfo, "Packaged action URL: %s\n", actionURL) 155 | } else { 156 | actionURL = fmt.Sprintf(packagedActionPath, baseURL, apiVersion, namespace, pkg, name) 157 | Debug(DbgInfo, "Action URL: %s\n", actionURL) 158 | } 159 | } 160 | 161 | return actionURL, nil 162 | } 163 | 164 | //////////////////// 165 | // Action Methods // 166 | //////////////////// 167 | 168 | func (s *ActionService) List(packageName string, options *ActionListOptions) ([]Action, *http.Response, error) { 169 | var route string 170 | var actions []Action 171 | 172 | if len(packageName) > 0 { 173 | // Encode resource name as a path (with no query params) before inserting it into the URI 174 | // This way any '?' chars in the name won't be treated as the beginning of the query params 175 | packageName = (&url.URL{Path: packageName}).String() 176 | route = fmt.Sprintf("actions/%s/", packageName) 177 | } else { 178 | route = fmt.Sprintf("actions") 179 | } 180 | 181 | routeUrl, err := addRouteOptions(route, options) 182 | if err != nil { 183 | Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err) 184 | errMsg := wski18n.T("Unable to add route options '{{.options}}'", 185 | map[string]interface{}{"options": options}) 186 | whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, 187 | NO_DISPLAY_USAGE) 188 | return nil, nil, whiskErr 189 | } 190 | Debug(DbgError, "Action list route with options: %s\n", route) 191 | 192 | req, err := s.client.NewRequestUrl("GET", routeUrl, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) 193 | if err != nil { 194 | Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) error: '%s'\n", routeUrl, err) 195 | errMsg := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}", 196 | map[string]interface{}{"route": routeUrl, "err": err}) 197 | whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXIT_CODE_ERR_NETWORK, DISPLAY_MSG, 198 | NO_DISPLAY_USAGE) 199 | return nil, nil, whiskErr 200 | } 201 | 202 | resp, err := s.client.Do(req, &actions, ExitWithSuccessOnTimeout) 203 | if err != nil { 204 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err) 205 | return nil, resp, err 206 | } 207 | 208 | return actions, resp, err 209 | } 210 | 211 | func (s *ActionService) Insert(action *Action, overwrite bool) (*Action, *http.Response, error) { 212 | // Encode resource name as a path (with no query params) before inserting it into the URI 213 | // This way any '?' chars in the name won't be treated as the beginning of the query params 214 | actionName := (&url.URL{Path: action.Name}).String() 215 | route := fmt.Sprintf("actions/%s?overwrite=%t", actionName, overwrite) 216 | Debug(DbgInfo, "Action insert route: %s\n", route) 217 | 218 | req, err := s.client.NewRequest("PUT", route, action, IncludeNamespaceInUrl) 219 | if err != nil { 220 | Debug(DbgError, "http.NewRequest(PUT, %s, %#v) error: '%s'\n", route, action, err) 221 | errMsg := wski18n.T("Unable to create HTTP request for PUT '{{.route}}': {{.err}}", 222 | map[string]interface{}{"route": route, "err": err}) 223 | whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXIT_CODE_ERR_NETWORK, DISPLAY_MSG, 224 | NO_DISPLAY_USAGE) 225 | return nil, nil, whiskErr 226 | } 227 | 228 | a := new(Action) 229 | resp, err := s.client.Do(req, &a, ExitWithSuccessOnTimeout) 230 | if err != nil { 231 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err) 232 | return nil, resp, err 233 | } 234 | 235 | return a, resp, nil 236 | } 237 | 238 | func (s *ActionService) Get(actionName string, fetchCode bool) (*Action, *http.Response, error) { 239 | // Encode resource name as a path (with no query params) before inserting it into the URI 240 | // This way any '?' chars in the name won't be treated as the beginning of the query params 241 | actionName = (&url.URL{Path: actionName}).String() 242 | route := fmt.Sprintf("actions/%s?code=%t", actionName, fetchCode) 243 | 244 | req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl) 245 | if err != nil { 246 | Debug(DbgError, "http.NewRequest(GET, %s, nil) error: '%s'\n", route, err) 247 | errMsg := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}", 248 | map[string]interface{}{"route": route, "err": err}) 249 | whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXIT_CODE_ERR_NETWORK, DISPLAY_MSG, 250 | NO_DISPLAY_USAGE) 251 | return nil, nil, whiskErr 252 | } 253 | 254 | a := new(Action) 255 | resp, err := s.client.Do(req, &a, ExitWithSuccessOnTimeout) 256 | if err != nil { 257 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err) 258 | return nil, resp, err 259 | } 260 | 261 | return a, resp, nil 262 | } 263 | 264 | func (s *ActionService) Delete(actionName string) (*http.Response, error) { 265 | // Encode resource name as a path (with no query params) before inserting it into the URI 266 | // This way any '?' chars in the name won't be treated as the beginning of the query params 267 | actionName = (&url.URL{Path: actionName}).String() 268 | route := fmt.Sprintf("actions/%s", actionName) 269 | Debug(DbgInfo, "HTTP route: %s\n", route) 270 | 271 | req, err := s.client.NewRequest("DELETE", route, nil, IncludeNamespaceInUrl) 272 | if err != nil { 273 | Debug(DbgError, "http.NewRequest(DELETE, %s, nil) error: '%s'\n", route, err) 274 | errMsg := wski18n.T("Unable to create HTTP request for DELETE '{{.route}}': {{.err}}", 275 | map[string]interface{}{"route": route, "err": err}) 276 | whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXIT_CODE_ERR_NETWORK, DISPLAY_MSG, 277 | NO_DISPLAY_USAGE) 278 | return nil, whiskErr 279 | } 280 | 281 | a := new(Action) 282 | resp, err := s.client.Do(req, a, ExitWithSuccessOnTimeout) 283 | if err != nil { 284 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err) 285 | return resp, err 286 | } 287 | 288 | return resp, nil 289 | } 290 | 291 | func (s *ActionService) Invoke(actionName string, payload interface{}, blocking bool, result bool) (interface{}, *http.Response, error) { 292 | var res interface{} 293 | 294 | // Encode resource name as a path (with no query params) before inserting it into the URI 295 | // This way any '?' chars in the name won't be treated as the beginning of the query params 296 | actionName = (&url.URL{Path: actionName}).String() 297 | route := fmt.Sprintf("actions/%s?blocking=%t&result=%t", actionName, blocking, result) 298 | Debug(DbgInfo, "HTTP route: %s\n", route) 299 | 300 | req, err := s.client.NewRequest("POST", route, payload, IncludeNamespaceInUrl) 301 | if err != nil { 302 | Debug(DbgError, "http.NewRequest(POST, %s, %#v) error: '%s'\n", route, payload, err) 303 | errMsg := wski18n.T("Unable to create HTTP request for POST '{{.route}}': {{.err}}", 304 | map[string]interface{}{"route": route, "err": err}) 305 | whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXIT_CODE_ERR_NETWORK, DISPLAY_MSG, 306 | NO_DISPLAY_USAGE) 307 | return nil, nil, whiskErr 308 | } 309 | 310 | resp, err := s.client.Do(req, &res, blocking) 311 | 312 | if err != nil { 313 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err) 314 | return res, resp, err 315 | } 316 | 317 | return res, resp, nil 318 | } 319 | -------------------------------------------------------------------------------- /whisk/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 whisk 21 | 22 | import ( 23 | "encoding/json" 24 | "github.com/stretchr/testify/assert" 25 | "io/ioutil" 26 | "net/http" 27 | "net/url" 28 | "strings" 29 | "testing" 30 | ) 31 | 32 | const ( 33 | NODE_ACTION_NO_CODE = `{ 34 | "name": "test", 35 | "publish": false, 36 | "annotations": [ 37 | { 38 | "key": "exec", 39 | "value": "nodejs:6" 40 | } 41 | ], 42 | "version": "0.0.1", 43 | "exec": { 44 | "kind": "nodejs:6", 45 | "binary": false 46 | }, 47 | "parameters": [], 48 | "limits": { 49 | "timeout": 60000, 50 | "memory": 256, 51 | "logs": 10 52 | }, 53 | "namespace": "test@openwhisk" 54 | }` 55 | 56 | NODE_ACTION = `{ 57 | "name": "test", 58 | "publish": false, 59 | "annotations": [ 60 | { 61 | "key": "exec", 62 | "value": "nodejs:6" 63 | } 64 | ], 65 | "version": "0.0.1", 66 | "exec": { 67 | "kind": "nodejs:6", 68 | "code": "...", 69 | "binary": false 70 | }, 71 | "parameters": [], 72 | "limits": { 73 | "timeout": 60000, 74 | "memory": 256, 75 | "logs": 10 76 | }, 77 | "namespace": "test@openwhisk" 78 | }` 79 | ) 80 | 81 | type ActionResponse struct { 82 | Body string 83 | } 84 | 85 | type ActionRequest struct { 86 | Method string 87 | URL string 88 | } 89 | 90 | var actionResponse = &ActionResponse{} 91 | var actionRequest = &ActionRequest{} 92 | 93 | type MockClient struct{} 94 | 95 | func (c *MockClient) NewRequestUrl(method string, urlRelResource *url.URL, body interface{}, includeNamespaceInUrl bool, appendOpenWhiskPath bool, encodeBodyAs string, useAuthentication bool) (*http.Request, error) { 96 | return &http.Request{}, nil 97 | } 98 | 99 | func (c *MockClient) NewRequest(method, urlStr string, body interface{}, includeNamespaceInUrl bool) (*http.Request, error) { 100 | actionRequest.Method = method 101 | actionRequest.URL = urlStr 102 | 103 | request, err := http.NewRequest(method, urlStr, nil) 104 | if err != nil { 105 | return &http.Request{}, err 106 | } 107 | 108 | return request, nil 109 | } 110 | 111 | func (c *MockClient) Do(req *http.Request, v interface{}, ExitWithErrorOnTimeout bool, secretToObfuscate ...ObfuscateSet) (*http.Response, error) { 112 | var reader = strings.NewReader(actionResponse.Body) 113 | 114 | dc := json.NewDecoder(reader) 115 | dc.UseNumber() 116 | err := dc.Decode(v) 117 | 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | resp := &http.Response{ 123 | StatusCode: 200, 124 | Body: ioutil.NopCloser(reader), 125 | } 126 | 127 | return resp, nil 128 | } 129 | 130 | func TestActionGet(t *testing.T) { 131 | assert := assert.New(t) 132 | mockClient := &MockClient{} 133 | actionService := &ActionService{client: mockClient} 134 | 135 | actionResponse.Body = NODE_ACTION_NO_CODE 136 | action, _, _ := actionService.Get("test", false) 137 | 138 | var exec Exec 139 | exec = *action.Exec 140 | var nilStr *string 141 | 142 | assert.Equal("GET", actionRequest.Method) 143 | assert.Equal("actions/test?code=false", actionRequest.URL) 144 | assert.Equal(nilStr, exec.Code) 145 | 146 | actionResponse.Body = NODE_ACTION 147 | action, _, _ = actionService.Get("test", true) 148 | assert.Equal("GET", actionRequest.Method) 149 | assert.Equal("actions/test?code=true", actionRequest.URL) 150 | assert.Equal("...", *action.Exec.Code) 151 | } 152 | -------------------------------------------------------------------------------- /whisk/activation.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 whisk 19 | 20 | import ( 21 | "errors" 22 | "fmt" 23 | "net/http" 24 | "net/url" 25 | "strconv" 26 | "time" 27 | 28 | "github.com/apache/openwhisk-client-go/wski18n" 29 | ) 30 | 31 | type ActivationService struct { 32 | client *Client 33 | } 34 | 35 | type Activation struct { 36 | Namespace string `json:"namespace"` 37 | Name string `json:"name"` 38 | Version string `json:"version"` 39 | Subject string `json:"subject"` 40 | ActivationID string `json:"activationId"` 41 | Cause string `json:"cause,omitempty"` 42 | Start int64 `json:"start"` // When action started (in milliseconds since January 1, 1970 UTC) 43 | End int64 `json:"end"` // Since a 0 is a valid value from server, don't omit 44 | Duration int64 `json:"duration"` // Only available for actions 45 | StatusCode int `json:"statusCode"` 46 | Response `json:"response"` 47 | Logs []string `json:"logs"` 48 | Annotations KeyValueArr `json:"annotations"` 49 | Publish *bool `json:"publish,omitempty"` 50 | } 51 | 52 | type ActivationCount struct { 53 | Activations int64 `json:"activations"` 54 | } 55 | 56 | type ActivationFilteredRow struct { 57 | Row Activation 58 | HeaderFmt string 59 | RowFmt string 60 | } 61 | 62 | type Response struct { 63 | Status string `json:"status"` 64 | StatusCode int `json:"statusCode"` 65 | Success bool `json:"success"` 66 | Result Result `json:"result,omitempty"` 67 | } 68 | 69 | type Result interface{} 70 | 71 | type ActivationListOptions struct { 72 | Name string `url:"name,omitempty"` 73 | Limit int `url:"limit"` 74 | Skip int `url:"skip"` 75 | Since int64 `url:"since,omitempty"` 76 | Upto int64 `url:"upto,omitempty"` 77 | Docs bool `url:"docs,omitempty"` 78 | } 79 | 80 | type ActivationCountOptions struct { 81 | Name string `url:"name,omitempty"` 82 | Skip int `url:"skip"` 83 | Since int64 `url:"since,omitempty"` 84 | Upto int64 `url:"upto,omitempty"` 85 | Count bool `url:"count,omitempty"` 86 | } 87 | 88 | // MWD - This structure may no longer be needed as the log format is now a string and not JSON 89 | type Log struct { 90 | Log string `json:"log,omitempty"` 91 | Stream string `json:"stream,omitempty"` 92 | Time string `json:"time,omitempty"` 93 | } 94 | 95 | // Status codes to descriptions 96 | var StatusCodes = []string{"success", "application error", "developer error", "internal error"} 97 | 98 | // Compare(sortable) compares activation to sortable for the purpose of sorting. 99 | // REQUIRED: sortable must also be of type Activation. 100 | // ***Method of type Sortable*** 101 | // ***Currently, no method of sorting defined*** 102 | func (activation Activation) Compare(sortable Sortable) bool { 103 | return true 104 | } 105 | 106 | // Compare(sortable) compares activation to sortable for the purpose of sorting. 107 | // REQUIRED: sortable must also be of type Activation. 108 | // ***Method of type Sortable*** 109 | // ***Currently, no method of sorting defined*** 110 | func (activation ActivationFilteredRow) Compare(sortable Sortable) bool { 111 | return true 112 | } 113 | 114 | // ToHeaderString() returns the header for a list of activations 115 | func (activation ActivationFilteredRow) ToHeaderString() string { 116 | return fmt.Sprintf(activation.HeaderFmt, "Datetime", "Activation ID", "Kind", "Start", "Duration", "Status", "Entity") 117 | } 118 | 119 | // TruncateStr() returns the string, truncated with ...in the middle if it exceeds the specified length 120 | func TruncateStr(str string, maxlen int) string { 121 | if len(str) <= maxlen { 122 | return str 123 | } else { 124 | mid := maxlen / 2 125 | upp := len(str) - mid + 3 126 | if maxlen%2 != 0 { 127 | mid++ 128 | } 129 | return str[0:mid] + "..." + str[upp:] 130 | } 131 | } 132 | 133 | // ToSummaryRowString() returns a compound string of required parameters for printing 134 | // 135 | // from CLI command `wsk activation list`. 136 | // 137 | // ***Method of type Sortable*** 138 | func (activation ActivationFilteredRow) ToSummaryRowString() string { 139 | s := time.Unix(0, activation.Row.Start*1000000) 140 | e := time.Unix(0, activation.Row.End*1000000) 141 | 142 | var duration = e.Sub(s) 143 | var kind interface{} = activation.Row.Annotations.GetValue("kind") 144 | var initTime interface{} = activation.Row.Annotations.GetValue("initTime") 145 | var status = StatusCodes[0] // assume success 146 | var start = "warm" // assume warm 147 | var fqn = TruncateStr(activation.Row.Namespace, 20) + "/" + TruncateStr(activation.Row.Name, 30) + ":" + TruncateStr(activation.Row.Version, 20) 148 | 149 | if activation.Row.Duration == 0 { 150 | duration = s.Sub(s) 151 | } 152 | if kind == nil { 153 | kind = "unknown" 154 | } 155 | if activation.Row.StatusCode > 0 && activation.Row.StatusCode < len(StatusCodes) { 156 | status = StatusCodes[activation.Row.StatusCode] 157 | } 158 | if initTime != nil { 159 | start = "cold" 160 | } 161 | 162 | return fmt.Sprintf( 163 | activation.RowFmt+strconv.Itoa(len(fqn))+"s\n", 164 | s.Year(), s.Month(), s.Day(), s.Hour(), s.Minute(), s.Second(), 165 | activation.Row.ActivationID, 166 | kind.(string), 167 | start, 168 | duration, 169 | status, 170 | fqn) 171 | } 172 | 173 | func (s *ActivationService) List(options *ActivationListOptions) ([]Activation, *http.Response, error) { 174 | // TODO :: for some reason /activations only works with "_" as namespace 175 | s.client.Namespace = "_" 176 | route := "activations" 177 | routeUrl, err := addRouteOptions(route, options) 178 | if err != nil { 179 | Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err) 180 | errStr := wski18n.T("Unable to append options '{{.options}}' to URL route '{{.route}}': {{.err}}", 181 | map[string]interface{}{"options": fmt.Sprintf("%#v", options), "route": route, "err": err}) 182 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 183 | return nil, nil, werr 184 | } 185 | 186 | req, err := s.client.NewRequestUrl("GET", routeUrl, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) 187 | if err != nil { 188 | Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) error: '%s'\n", route, err) 189 | errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}", 190 | map[string]interface{}{"route": route, "err": err}) 191 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 192 | return nil, nil, werr 193 | } 194 | 195 | Debug(DbgInfo, "Sending HTTP request - URL '%s'; req %#v\n", req.URL.String(), req) 196 | 197 | var activations []Activation 198 | resp, err := s.client.Do(req, &activations, ExitWithSuccessOnTimeout) 199 | if err != nil { 200 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err) 201 | return nil, resp, err 202 | } 203 | 204 | return activations, resp, nil 205 | } 206 | 207 | func (s *ActivationService) Count(options *ActivationCountOptions) (*ActivationCount, *http.Response, error) { 208 | // TODO :: for some reason /activations only works with "_" as namespace 209 | s.client.Namespace = "_" 210 | route := "activations" 211 | 212 | options.Count = true 213 | routeUrl, err := addRouteOptions(route, options) 214 | 215 | if err != nil { 216 | Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err) 217 | errStr := wski18n.T("Unable to append options '{{.options}}' to URL route '{{.route}}': {{.err}}", 218 | map[string]interface{}{"options": fmt.Sprintf("%#v", options), "route": route, "err": err}) 219 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 220 | return nil, nil, werr 221 | } 222 | 223 | req, err := s.client.NewRequestUrl("GET", routeUrl, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) 224 | if err != nil { 225 | Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) error: '%s'\n", route, err) 226 | errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}", 227 | map[string]interface{}{"route": route, "err": err}) 228 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 229 | return nil, nil, werr 230 | } 231 | 232 | Debug(DbgInfo, "Sending HTTP request - URL '%s'; req %#v\n", req.URL.String(), req) 233 | 234 | count := new(ActivationCount) 235 | resp, err := s.client.Do(req, &count, ExitWithSuccessOnTimeout) 236 | 237 | if err != nil { 238 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err) 239 | return nil, resp, err 240 | } 241 | 242 | return count, resp, nil 243 | } 244 | 245 | func (s *ActivationService) Get(activationID string) (*Activation, *http.Response, error) { 246 | // TODO :: for some reason /activations/:id only works with "_" as namespace 247 | s.client.Namespace = "_" 248 | 249 | // Encode resource name as a path (with no query params) before inserting it into the URI 250 | // This way any '?' chars in the name won't be treated as the beginning of the query params 251 | activationID = (&url.URL{Path: activationID}).String() 252 | route := fmt.Sprintf("activations/%s", activationID) 253 | 254 | req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl) 255 | if err != nil { 256 | Debug(DbgError, "http.NewRequest(GET, %s) error: '%s'\n", route, err) 257 | errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}", 258 | map[string]interface{}{"route": route, "err": err}) 259 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 260 | return nil, nil, werr 261 | } 262 | 263 | Debug(DbgInfo, "Sending HTTP request - URL '%s'; req %#v\n", req.URL.String(), req) 264 | 265 | a := new(Activation) 266 | resp, err := s.client.Do(req, &a, ExitWithSuccessOnTimeout) 267 | if err != nil { 268 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err) 269 | return nil, resp, err 270 | } 271 | 272 | a.StatusCode = GetStatusCodeForMessage(a.Status) 273 | 274 | return a, resp, nil 275 | } 276 | 277 | func GetStatusCodeForMessage(msg string) int { 278 | var code int 279 | 280 | switch msg { 281 | case "application error": 282 | code = 1 283 | case "action developer error": 284 | code = 2 285 | case "whisk internal error": 286 | code = 3 287 | default: 288 | code = 0 289 | } 290 | 291 | return code 292 | } 293 | 294 | func (s *ActivationService) Logs(activationID string) (*Activation, *http.Response, error) { 295 | // TODO :: for some reason /activations/:id/logs only works with "_" as namespace 296 | s.client.Namespace = "_" 297 | // Encode resource name as a path (with no query params) before inserting it into the URI 298 | // This way any '?' chars in the name won't be treated as the beginning of the query params 299 | activationID = (&url.URL{Path: activationID}).String() 300 | route := fmt.Sprintf("activations/%s/logs", activationID) 301 | 302 | req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl) 303 | if err != nil { 304 | Debug(DbgError, "http.NewRequest(GET, %s) error: '%s'\n", route, err) 305 | errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}", 306 | map[string]interface{}{"route": route, "err": err}) 307 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 308 | return nil, nil, werr 309 | } 310 | 311 | Debug(DbgInfo, "Sending HTTP request - URL '%s'; req %#v\n", req.URL.String(), req) 312 | 313 | activation := new(Activation) 314 | resp, err := s.client.Do(req, &activation, ExitWithSuccessOnTimeout) 315 | if err != nil { 316 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err) 317 | return nil, resp, err 318 | } 319 | 320 | return activation, resp, nil 321 | } 322 | 323 | func (s *ActivationService) Result(activationID string) (*Response, *http.Response, error) { 324 | // TODO :: for some reason /activations only works with "_" as namespace 325 | s.client.Namespace = "_" 326 | // Encode resource name as a path (with no query params) before inserting it into the URI 327 | // This way any '?' chars in the name won't be treated as the beginning of the query params 328 | activationID = (&url.URL{Path: activationID}).String() 329 | route := fmt.Sprintf("activations/%s/result", activationID) 330 | 331 | req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl) 332 | if err != nil { 333 | Debug(DbgError, "http.NewRequest(GET, %s) error: '%s'\n", route, err) 334 | errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}", 335 | map[string]interface{}{"route": route, "err": err}) 336 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 337 | return nil, nil, werr 338 | } 339 | 340 | Debug(DbgInfo, "Sending HTTP request - URL '%s'; req %#v\n", req.URL.String(), req) 341 | 342 | r := new(Response) 343 | resp, err := s.client.Do(req, &r, ExitWithSuccessOnTimeout) 344 | if err != nil { 345 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err) 346 | return nil, resp, err 347 | } 348 | 349 | return r, resp, nil 350 | } 351 | -------------------------------------------------------------------------------- /whisk/api.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 whisk 19 | 20 | import ( 21 | "errors" 22 | "fmt" 23 | "github.com/apache/openwhisk-client-go/wski18n" 24 | "net/http" 25 | "strings" 26 | ) 27 | 28 | type ApiService struct { 29 | client *Client 30 | } 31 | 32 | // wsk api create : Request, Response 33 | type ApiCreateRequest struct { 34 | ApiDoc *Api `json:"apidoc,omitempty"` 35 | } 36 | type ApiCreateRequestOptions ApiOptions 37 | type ApiCreateResponse RetApi 38 | 39 | // wsk api list : Request, Response 40 | type ApiListRequest struct { 41 | } 42 | type ApiListRequestOptions struct { 43 | ApiOptions 44 | Limit int `url:"limit"` 45 | Skip int `url:"skip"` 46 | Docs bool `url:"docs,omitempty"` 47 | } 48 | type ApiListResponse RetApiArray 49 | 50 | // wsk api get : Request, Response 51 | type ApiGetRequest struct { 52 | Api 53 | } 54 | type ApiGetRequestOptions ApiOptions 55 | type ApiGetResponse RetApiArray 56 | 57 | // wsk api delete : Request, Response 58 | type ApiDeleteRequest struct { 59 | Api 60 | } 61 | type ApiDeleteRequestOptions ApiOptions 62 | type ApiDeleteResponse struct{} 63 | 64 | type Api struct { 65 | Namespace string `json:"namespace,omitempty"` 66 | ApiName string `json:"apiName,omitempty"` 67 | GatewayBasePath string `json:"gatewayBasePath,omitempty"` 68 | GatewayRelPath string `json:"gatewayPath,omitempty"` 69 | GatewayMethod string `json:"gatewayMethod,omitempty"` 70 | Id string `json:"id,omitempty"` 71 | GatewayFullPath string `json:"gatewayFullPath,omitempty"` 72 | Swagger string `json:"swagger,omitempty"` 73 | Action *ApiAction `json:"action,omitempty"` 74 | PathParameters []ApiParameter `json:"pathParameters,omitempty"` 75 | } 76 | 77 | type ApiParameter struct { 78 | Name string `json:"name"` 79 | In string `json:"in"` 80 | Description string `json:"description,omitempty"` 81 | Required bool `json:"required,omitempty"` 82 | Type string `json:"type,omitempty"` 83 | Format string `json:"format,omitempty"` 84 | AllowEmptyValue bool `json:"allowEmptyValue,omitempty"` 85 | Items map[string]interface{} `json:"items,omitempty"` 86 | CollectionFormat string `json:"collectionFormat,omitempty"` 87 | Default interface{} `json:"default,omitempty"` 88 | Maximum int `json:"maximum,omitempty"` 89 | ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"` 90 | Minimum int `json:"minimum,omitempty"` 91 | ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"` 92 | MaxLength int `json:"maxLength,omitempty"` 93 | MinLength int `json:"minLength,omitempty"` 94 | Pattern string `json:"pattern,omitempty"` 95 | MaxItems int `json:"maxItems,omitempty"` 96 | MinItems int `json:"minItems,omitempty"` 97 | UniqueItems bool `json:"uniqueItems,omitempty"` 98 | MultipleOf int `json:"multipleOf,omitempty"` 99 | Enum interface{} `json:"enum,omitempty"` 100 | Ref string `json:"$ref,omitempty"` 101 | } 102 | 103 | type ApiAction struct { 104 | Name string `json:"name,omitempty"` 105 | Namespace string `json:"namespace,omitempty"` 106 | BackendMethod string `json:"backendMethod,omitempty"` 107 | BackendUrl string `json:"backendUrl,omitempty"` 108 | Auth string `json:"authkey,omitempty"` 109 | SecureKey interface{} `json:"secureKey,omitempty"` 110 | } 111 | 112 | type ApiOptions struct { 113 | ActionName string `url:"action,omitempty"` 114 | ApiBasePath string `url:"basepath,omitempty"` 115 | ApiRelPath string `url:"relpath,omitempty"` 116 | ApiVerb string `url:"operation,omitempty"` 117 | ApiName string `url:"apiname,omitempty"` 118 | SpaceGuid string `url:"spaceguid,omitempty"` 119 | AccessToken string `url:"accesstoken,omitempty"` 120 | ResponseType string `url:"responsetype,omitempty"` 121 | } 122 | 123 | type ApiUserAuth struct { 124 | SpaceGuid string `json:"spaceguid,omitempty"` 125 | AccessToken string `json:"accesstoken,omitempty"` 126 | } 127 | 128 | type RetApiArray struct { 129 | Apis []ApiItem `json:"apis,omitempty"` 130 | } 131 | 132 | type ApiItem struct { 133 | ApiId string `json:"id,omitempty"` 134 | QueryKey string `json:"key,omitempty"` 135 | ApiValue *RetApi `json:"value,omitempty"` 136 | } 137 | 138 | type RetApi struct { 139 | Namespace string `json:"namespace"` 140 | BaseUrl string `json:"gwApiUrl"` 141 | Activated bool `json:"gwApiActivated"` 142 | TenantId string `json:"tenantId"` 143 | Swagger *ApiSwagger `json:"apidoc,omitempty"` 144 | } 145 | 146 | type ApiSwagger struct { 147 | SwaggerName string `json:"swagger,omitempty"` 148 | BasePath string `json:"basePath,omitempty"` 149 | Info *ApiSwaggerInfo `json:"info,omitempty"` 150 | Paths map[string]*ApiSwaggerPath `json:"paths,omitempty"` 151 | SecurityDef interface{} `json:"securityDefinitions,omitempty"` 152 | Security interface{} `json:"security,omitempty"` 153 | XConfig interface{} `json:"x-ibm-configuration,omitempty"` 154 | XRateLimit interface{} `json:"x-ibm-rate-limit,omitempty"` 155 | } 156 | 157 | type ApiSwaggerPath struct { 158 | Get *ApiSwaggerOperation `json:"get,omitempty"` 159 | Put *ApiSwaggerOperation `json:"put,omitempty"` 160 | Post *ApiSwaggerOperation `json:"post,omitempty"` 161 | Delete *ApiSwaggerOperation `json:"delete,omitempty"` 162 | Options *ApiSwaggerOperation `json:"options,omitempty"` 163 | Head *ApiSwaggerOperation `json:"head,omitempty"` 164 | Patch *ApiSwaggerOperation `json:"patch,omitempty"` 165 | Parameters []ApiParameter `json:"parameters,omitempty"` 166 | } 167 | 168 | func (asp *ApiSwaggerPath) MakeOperationMap() map[string]*ApiSwaggerOperation { 169 | var opMap map[string]*ApiSwaggerOperation = make(map[string]*ApiSwaggerOperation) 170 | if asp.Get != nil { 171 | opMap["get"] = asp.Get 172 | } 173 | if asp.Put != nil { 174 | opMap["put"] = asp.Put 175 | } 176 | if asp.Post != nil { 177 | opMap["post"] = asp.Post 178 | } 179 | if asp.Delete != nil { 180 | opMap["delete"] = asp.Delete 181 | } 182 | if asp.Options != nil { 183 | opMap["options"] = asp.Options 184 | } 185 | if asp.Head != nil { 186 | opMap["head"] = asp.Head 187 | } 188 | if asp.Patch != nil { 189 | opMap["patch"] = asp.Patch 190 | } 191 | return opMap 192 | } 193 | 194 | type ApiSwaggerInfo struct { 195 | Title string `json:"title,omitempty"` 196 | Version string `json:"version,omitempty"` 197 | } 198 | 199 | type ApiSwaggerOperation struct { 200 | OperationId string `json:"operationId"` 201 | Parameters []ApiParameter `json:"parameters,omitempty"` 202 | Responses interface{} `json:"responses"` 203 | XOpenWhisk *ApiSwaggerOpXOpenWhisk `json:"x-openwhisk,omitempty"` 204 | } 205 | 206 | type ApiSwaggerOpXOpenWhisk struct { 207 | ActionName string `json:"action"` 208 | Namespace string `json:"namespace"` 209 | Package string `json:"package"` 210 | ApiUrl string `json:"url"` 211 | } 212 | 213 | // Used for printing individual APIs in non-truncated form 214 | type ApiFilteredList struct { 215 | ActionName string 216 | ApiName string 217 | BasePath string 218 | RelPath string 219 | Verb string 220 | Url string 221 | } 222 | 223 | // Used for printing individual APIs in truncated form 224 | type ApiFilteredRow struct { 225 | ActionName string 226 | ApiName string 227 | BasePath string 228 | RelPath string 229 | Verb string 230 | Url string 231 | FmtString string 232 | } 233 | 234 | var ApiVerbs map[string]bool = map[string]bool{ 235 | "GET": true, 236 | "PUT": true, 237 | "POST": true, 238 | "DELETE": true, 239 | "PATCH": true, 240 | "HEAD": true, 241 | "OPTIONS": true, 242 | } 243 | 244 | const ( 245 | Overwrite = true 246 | DoNotOverwrite = false 247 | ) 248 | 249 | ///////////////// 250 | // Api Methods // 251 | ///////////////// 252 | 253 | // Compare(sortable) compares api to sortable for the purpose of sorting. 254 | // REQUIRED: sortable must also be of type ApiFilteredList. 255 | // ***Method of type Sortable*** 256 | func (api ApiFilteredList) Compare(sortable Sortable) bool { 257 | // Sorts alphabetically by [BASE_PATH | API_NAME] -> REL_PATH -> API_VERB 258 | apiToCompare := sortable.(ApiFilteredList) 259 | var apiString string 260 | var compareString string 261 | 262 | apiString = strings.ToLower(fmt.Sprintf("%s%s%s", api.BasePath, api.RelPath, 263 | api.Verb)) 264 | compareString = strings.ToLower(fmt.Sprintf("%s%s%s", apiToCompare.BasePath, 265 | apiToCompare.RelPath, apiToCompare.Verb)) 266 | 267 | return apiString < compareString 268 | } 269 | 270 | // ToHeaderString() returns the header for a list of apis 271 | func (api ApiFilteredList) ToHeaderString() string { 272 | return "" 273 | } 274 | 275 | // ToSummaryRowString() returns a compound string of required parameters for printing 276 | // from CLI command `wsk api list` or `wsk api-experimental list`. 277 | // ***Method of type Sortable*** 278 | func (api ApiFilteredList) ToSummaryRowString() string { 279 | return fmt.Sprintf("%s %s %s %s %s %s", 280 | fmt.Sprintf("%s: %s\n", wski18n.T("Action"), api.ActionName), 281 | fmt.Sprintf(" %s: %s\n", wski18n.T("API Name"), api.ApiName), 282 | fmt.Sprintf(" %s: %s\n", wski18n.T("Base path"), api.BasePath), 283 | fmt.Sprintf(" %s: %s\n", wski18n.T("Path"), api.RelPath), 284 | fmt.Sprintf(" %s: %s\n", wski18n.T("Verb"), api.Verb), 285 | fmt.Sprintf(" %s: %s\n", wski18n.T("URL"), api.Url)) 286 | } 287 | 288 | // Compare(sortable) compares api to sortable for the purpose of sorting. 289 | // REQUIRED: sortable must also be of type ApiFilteredRow. 290 | // ***Method of type Sortable*** 291 | func (api ApiFilteredRow) Compare(sortable Sortable) bool { 292 | // Sorts alphabetically by [BASE_PATH | API_NAME] -> REL_PATH -> API_VERB 293 | var apiString string 294 | var compareString string 295 | apiToCompare := sortable.(ApiFilteredRow) 296 | 297 | apiString = strings.ToLower(fmt.Sprintf("%s%s%s", api.BasePath, api.RelPath, 298 | api.Verb)) 299 | compareString = strings.ToLower(fmt.Sprintf("%s%s%s", apiToCompare.BasePath, 300 | apiToCompare.RelPath, apiToCompare.Verb)) 301 | 302 | return apiString < compareString 303 | } 304 | 305 | // ToHeaderString() returns the header for a list of apis 306 | func (api ApiFilteredRow) ToHeaderString() string { 307 | return fmt.Sprintf("%s", fmt.Sprintf(api.FmtString, "Action", "Verb", "API Name", "URL")) 308 | } 309 | 310 | // ToSummaryRowString() returns a compound string of required parameters for printing 311 | // from CLI command `wsk api list -f` or `wsk api-experimental list -f`. 312 | // ***Method of type Sortable*** 313 | func (api ApiFilteredRow) ToSummaryRowString() string { 314 | return fmt.Sprintf(api.FmtString, api.ActionName, api.Verb, api.ApiName, api.Url) 315 | } 316 | 317 | func (s *ApiService) List(apiListOptions *ApiListRequestOptions) (*ApiListResponse, *http.Response, error) { 318 | route := "web/whisk.system/apimgmt/getApi.http" 319 | 320 | routeUrl, err := addRouteOptions(route, apiListOptions) 321 | if err != nil { 322 | Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, apiListOptions, err) 323 | errMsg := wski18n.T("Unable to add route options '{{.options}}'", 324 | map[string]interface{}{"options": apiListOptions}) 325 | whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, 326 | NO_DISPLAY_USAGE) 327 | return nil, nil, whiskErr 328 | } 329 | Debug(DbgInfo, "Api GET/list route with api options: %s\n", routeUrl) 330 | 331 | req, err := s.client.NewRequestUrl("GET", routeUrl, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) 332 | if err != nil { 333 | Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson) error: '%s'\n", routeUrl, err) 334 | errMsg := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}", 335 | map[string]interface{}{"route": routeUrl, "err": err}) 336 | whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXIT_CODE_ERR_NETWORK, DISPLAY_MSG, 337 | NO_DISPLAY_USAGE) 338 | return nil, nil, whiskErr 339 | } 340 | 341 | apiArray := new(ApiListResponse) 342 | resp, err := s.client.Do(req, &apiArray, ExitWithErrorOnTimeout) 343 | if err != nil { 344 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err) 345 | return nil, resp, err 346 | } 347 | 348 | err = validateApiListResponse(apiArray) 349 | if err != nil { 350 | Debug(DbgError, "Not a valid ApiListResponse object\n") 351 | return nil, resp, err 352 | } 353 | 354 | return apiArray, resp, err 355 | } 356 | 357 | func (s *ApiService) Insert(api *ApiCreateRequest, options *ApiCreateRequestOptions, overwrite bool) (*ApiCreateResponse, *http.Response, error) { 358 | route := "web/whisk.system/apimgmt/createApi.http" 359 | Debug(DbgInfo, "Api PUT route: %s\n", route) 360 | 361 | routeUrl, err := addRouteOptions(route, options) 362 | if err != nil { 363 | Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err) 364 | errMsg := wski18n.T("Unable to add route options '{{.options}}'", 365 | map[string]interface{}{"options": options}) 366 | whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, 367 | NO_DISPLAY_USAGE) 368 | return nil, nil, whiskErr 369 | } 370 | Debug(DbgError, "Api create route with options: %s\n", routeUrl) 371 | 372 | req, err := s.client.NewRequestUrl("POST", routeUrl, api, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) 373 | if err != nil { 374 | Debug(DbgError, "http.NewRequestUrl(POST, %s, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson) error: '%s'\n", route, err) 375 | errMsg := wski18n.T("Unable to create HTTP request for POST '{{.route}}': {{.err}}", 376 | map[string]interface{}{"route": route, "err": err}) 377 | whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXIT_CODE_ERR_NETWORK, DISPLAY_MSG, 378 | NO_DISPLAY_USAGE) 379 | return nil, nil, whiskErr 380 | } 381 | 382 | retApi := new(ApiCreateResponse) 383 | resp, err := s.client.Do(req, &retApi, ExitWithErrorOnTimeout) 384 | if err != nil { 385 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err) 386 | return nil, resp, err 387 | } 388 | 389 | err = validateApiSwaggerResponse(retApi.Swagger) 390 | if err != nil { 391 | Debug(DbgError, "Not a valid API creation response\n") 392 | return nil, resp, err 393 | } 394 | 395 | return retApi, resp, nil 396 | } 397 | 398 | func (s *ApiService) Get(api *ApiGetRequest, options *ApiGetRequestOptions) (*ApiGetResponse, *http.Response, error) { 399 | route := "web/whisk.system/apimgmt/getApi.http" 400 | Debug(DbgInfo, "Api GET route: %s\n", route) 401 | 402 | routeUrl, err := addRouteOptions(route, options) 403 | if err != nil { 404 | Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err) 405 | errMsg := wski18n.T("Unable to add route options '{{.options}}'", 406 | map[string]interface{}{"options": options}) 407 | whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, 408 | NO_DISPLAY_USAGE) 409 | return nil, nil, whiskErr 410 | } 411 | Debug(DbgError, "Api get route with options: %s\n", routeUrl) 412 | 413 | req, err := s.client.NewRequestUrl("GET", routeUrl, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) 414 | if err != nil { 415 | Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson) error: '%s'\n", route, err) 416 | errMsg := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}", 417 | map[string]interface{}{"route": route, "err": err}) 418 | whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXIT_CODE_ERR_NETWORK, DISPLAY_MSG, 419 | NO_DISPLAY_USAGE) 420 | return nil, nil, whiskErr 421 | } 422 | 423 | retApi := new(ApiGetResponse) 424 | resp, err := s.client.Do(req, &retApi, ExitWithErrorOnTimeout) 425 | if err != nil { 426 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err) 427 | return nil, resp, err 428 | } 429 | 430 | return retApi, resp, nil 431 | } 432 | 433 | func (s *ApiService) Delete(api *ApiDeleteRequest, options *ApiDeleteRequestOptions) (*http.Response, error) { 434 | route := "web/whisk.system/apimgmt/deleteApi.http" 435 | Debug(DbgInfo, "Api DELETE route: %s\n", route) 436 | 437 | routeUrl, err := addRouteOptions(route, options) 438 | if err != nil { 439 | Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err) 440 | errMsg := wski18n.T("Unable to add route options '{{.options}}'", 441 | map[string]interface{}{"options": options}) 442 | whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, 443 | NO_DISPLAY_USAGE) 444 | return nil, whiskErr 445 | } 446 | Debug(DbgError, "Api DELETE route with options: %s\n", routeUrl) 447 | 448 | req, err := s.client.NewRequestUrl("DELETE", routeUrl, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) 449 | if err != nil { 450 | Debug(DbgError, "http.NewRequestUrl(DELETE, %s, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson) error: '%s'\n", route, err) 451 | errMsg := wski18n.T("Unable to create HTTP request for DELETE '{{.route}}': {{.err}}", 452 | map[string]interface{}{"route": route, "err": err}) 453 | whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXIT_CODE_ERR_NETWORK, DISPLAY_MSG, 454 | NO_DISPLAY_USAGE) 455 | return nil, whiskErr 456 | } 457 | 458 | retApi := new(ApiDeleteResponse) 459 | resp, err := s.client.Do(req, &retApi, ExitWithErrorOnTimeout) 460 | if err != nil { 461 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err) 462 | return resp, err 463 | } 464 | 465 | return nil, nil 466 | } 467 | 468 | func validateApiListResponse(apiList *ApiListResponse) error { 469 | for i := 0; i < len(apiList.Apis); i++ { 470 | if apiList.Apis[i].ApiValue == nil { 471 | Debug(DbgError, "validateApiResponse: No value stanza in api %v\n", apiList.Apis[i]) 472 | errMsg := wski18n.T("Internal error. Missing value stanza in API configuration response") 473 | whiskErr := MakeWskError(errors.New(errMsg), EXIT_CODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE) 474 | return whiskErr 475 | } 476 | err := validateApiSwaggerResponse(apiList.Apis[i].ApiValue.Swagger) 477 | if err != nil { 478 | Debug(DbgError, "validateApiListResponse: Invalid Api: %v\n", apiList.Apis[i]) 479 | return err 480 | } 481 | } 482 | return nil 483 | } 484 | 485 | func validateApiSwaggerResponse(swagger *ApiSwagger) error { 486 | if swagger == nil { 487 | Debug(DbgError, "validateApiSwaggerResponse: No apidoc stanza in api\n") 488 | errMsg := wski18n.T("Internal error. Missing apidoc stanza in API configuration") 489 | whiskErr := MakeWskError(errors.New(errMsg), EXIT_CODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE) 490 | return whiskErr 491 | } 492 | for path := range swagger.Paths { 493 | err := validateApiPath(swagger.Paths[path]) 494 | if err != nil { 495 | Debug(DbgError, "validateApiResponse: Invalid Api Path object: %v\n", swagger.Paths[path]) 496 | return err 497 | } 498 | } 499 | 500 | return nil 501 | } 502 | 503 | func validateApiPath(path *ApiSwaggerPath) error { 504 | for op, opv := range path.MakeOperationMap() { 505 | err := validateApiOperation(op, opv) 506 | if err != nil { 507 | Debug(DbgError, "validateApiPath: Invalid Api operation object: %v\n", opv) 508 | return err 509 | } 510 | } 511 | return nil 512 | } 513 | 514 | func validateApiOperation(opName string, op *ApiSwaggerOperation) error { 515 | if op.XOpenWhisk != nil && len(op.OperationId) == 0 { 516 | Debug(DbgError, "validateApiOperation: No operationId field in operation %v\n", op) 517 | errMsg := wski18n.T("Missing operationId field in API configuration for operation {{.op}}", 518 | map[string]interface{}{"op": opName}) 519 | whiskErr := MakeWskError(errors.New(errMsg), EXIT_CODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE) 520 | return whiskErr 521 | } 522 | 523 | if op.XOpenWhisk != nil && len(op.XOpenWhisk.Namespace) == 0 { 524 | Debug(DbgError, "validateApiOperation: no x-openwhisk.namespace stanza in operation %v\n", op) 525 | errMsg := wski18n.T("Missing x-openwhisk.namespace field in API configuration for operation {{.op}}", 526 | map[string]interface{}{"op": opName}) 527 | whiskErr := MakeWskError(errors.New(errMsg), EXIT_CODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE) 528 | return whiskErr 529 | } 530 | 531 | // Note: The op.XOpenWhisk.Package field can have a value of "", so don't enforce a value 532 | 533 | if op.XOpenWhisk != nil && len(op.XOpenWhisk.ActionName) == 0 { 534 | Debug(DbgError, "validateApiOperation: no x-openwhisk.action stanza in operation %v\n", op) 535 | errMsg := wski18n.T("Missing x-openwhisk.action field in API configuration for operation {{.op}}", 536 | map[string]interface{}{"op": opName}) 537 | whiskErr := MakeWskError(errors.New(errMsg), EXIT_CODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE) 538 | return whiskErr 539 | } 540 | if op.XOpenWhisk != nil && len(op.XOpenWhisk.ApiUrl) == 0 { 541 | Debug(DbgError, "validateApiOperation: no x-openwhisk.url stanza in operation %v\n", op) 542 | errMsg := wski18n.T("Missing x-openwhisk.url field in API configuration for operation {{.op}}", 543 | map[string]interface{}{"op": opName}) 544 | whiskErr := MakeWskError(errors.New(errMsg), EXIT_CODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE) 545 | return whiskErr 546 | } 547 | return nil 548 | } 549 | -------------------------------------------------------------------------------- /whisk/client_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 whisk 21 | 22 | import ( 23 | "fmt" 24 | "github.com/stretchr/testify/assert" 25 | "net/http" 26 | "net/url" 27 | "os" 28 | "testing" 29 | ) 30 | 31 | const ( 32 | FakeHost = "myUrl.com" 33 | FakeHostDiff = "myUrlTest.com" 34 | FakeBaseURL = "https://" + FakeHost + "/api" 35 | FakeBaseURLDiff = "https://" + FakeHostDiff + "/api" 36 | FakeNamespace = "my_namespace" 37 | FakeAuthKey = "dhajfhshfs:hjhfsjfdjfjsgfjs" 38 | ) 39 | 40 | func GetValidConfigTest() *Config { 41 | var config Config 42 | config.Host = FakeHost 43 | config.Namespace = FakeNamespace 44 | config.AuthToken = FakeAuthKey 45 | return &config 46 | } 47 | 48 | func GetInvalidConfigMissingApiHostTest() *Config { 49 | var config Config 50 | config.Namespace = FakeNamespace 51 | config.AuthToken = FakeAuthKey 52 | return &config 53 | } 54 | 55 | func GetInvalidConfigMissingApiHostWithBaseURLTest() *Config { 56 | var config Config 57 | urlBase := fmt.Sprintf("https://%s/api", FakeHostDiff) 58 | config.BaseURL, _ = url.Parse(urlBase) 59 | config.Namespace = FakeNamespace 60 | config.AuthToken = FakeAuthKey 61 | return &config 62 | } 63 | 64 | func GetValidConfigDiffApiHostAndBaseURLTest() *Config { 65 | var config Config 66 | urlBase := fmt.Sprintf("https://%s/api", FakeHostDiff) 67 | config.BaseURL, _ = url.Parse(urlBase) 68 | config.Host = FakeHost 69 | config.Namespace = FakeNamespace 70 | config.AuthToken = FakeAuthKey 71 | return &config 72 | } 73 | 74 | func TestNewClient(t *testing.T) { 75 | // Test the use case to pass a valid config. 76 | config := GetValidConfigTest() 77 | client, err := NewClient(http.DefaultClient, config) 78 | assert.Nil(t, err) 79 | assert.NotNil(t, client) 80 | assert.Equal(t, FakeNamespace, client.Config.Namespace) 81 | assert.Equal(t, FakeHost, client.Config.Host) 82 | assert.Equal(t, FakeBaseURL, client.Config.BaseURL.String()) 83 | assert.Equal(t, FakeAuthKey, client.Config.AuthToken) 84 | 85 | // Test the use case to pass an invalid config with a missing api host. 86 | config = GetInvalidConfigMissingApiHostTest() 87 | client, err = NewClient(http.DefaultClient, config) 88 | assert.NotNil(t, err) 89 | assert.Contains(t, err.Error(), "Unable to create request URL, because OpenWhisk API host is missing") 90 | assert.Nil(t, client) 91 | 92 | // Test the use case to pass a valid config with the base url but without api host. 93 | config = GetInvalidConfigMissingApiHostWithBaseURLTest() 94 | client, err = NewClient(http.DefaultClient, config) 95 | assert.NotNil(t, err) 96 | assert.Contains(t, err.Error(), "Unable to create request URL, because OpenWhisk API host is missing") 97 | assert.Nil(t, client) 98 | 99 | // Test the use case to pass a valid config with both the base and api host of different values. 100 | config = GetValidConfigDiffApiHostAndBaseURLTest() 101 | client, err = NewClient(http.DefaultClient, config) 102 | assert.Nil(t, err) 103 | assert.NotNil(t, client) 104 | assert.Equal(t, FakeNamespace, client.Config.Namespace) 105 | assert.Equal(t, FakeHost, client.Config.Host) 106 | assert.Equal(t, FakeBaseURLDiff, client.Config.BaseURL.String()) 107 | assert.Equal(t, FakeAuthKey, client.Config.AuthToken) 108 | } 109 | 110 | func TestProxyHost(t *testing.T) { 111 | var proxyhost = "one.bad.url.going.nowhere.org" 112 | var proxyurl = "https://" + proxyhost 113 | 114 | config := GetValidConfigTest() 115 | client, err := NewClient(nil, config) 116 | assert.NotNil(t, client) 117 | 118 | // This will update the transport 119 | err = client.LoadX509KeyPair() 120 | assert.Nil(t, err, "LoadX509KeyPair() failed") 121 | 122 | req, err := client.NewRequest("GET", config.BaseURL.String(), nil, false) 123 | assert.Nil(t, err, "NewRequest for proxy test failed.") 124 | if err != nil { 125 | fmt.Printf("NewRequest() error: %s\n", err.Error()) 126 | } 127 | 128 | // Proxy is enabled by setting env 129 | if priorProxyEnv, priorProxyEnvSet := os.LookupEnv("HTTPS_PROXY"); priorProxyEnvSet { 130 | err = os.Unsetenv("HTTPS_PROXY") 131 | assert.Nil(t, err, "Unsetenv(HTTPS_PROXY) failed: "+err.Error()) 132 | defer os.Setenv("HTTPS_PROXY", priorProxyEnv) 133 | } 134 | os.Setenv("HTTPS_PROXY", proxyurl) 135 | 136 | // Issue request that should fail due to a bad proxy host 137 | _, err = client.Do(req, nil, true) 138 | assert.NotNil(t, err, "Do() did not fail with invalid proxy URL.") 139 | if err != nil { 140 | assert.Contains(t, err.Error(), proxyhost, "Setting HTTPS_PROXY to '"+proxyhost+"' did not cause the CLI to use that proxy URL.") 141 | } 142 | } 143 | 144 | func TestAdditionalHeaders(t *testing.T) { 145 | config := GetValidConfigTest() 146 | config.AdditionalHeaders = make(map[string][]string) 147 | config.AdditionalHeaders.Add("Key1", "Value1") 148 | config.AdditionalHeaders.Add("Key2", "Value2") 149 | 150 | client, _ := NewClient(nil, config) 151 | assert.NotNil(t, client) 152 | 153 | newRequest, newRequestErr := client.NewRequest("GET", config.BaseURL.String(), nil, false) 154 | assert.Nil(t, newRequestErr, "NewRequest for proxy test failed.") 155 | if newRequestErr != nil { 156 | fmt.Printf("NewRequest() error: %s\n", newRequestErr.Error()) 157 | } 158 | 159 | assert.Equal(t, "Value1", newRequest.Header.Get("Key1")) 160 | assert.Equal(t, "Value2", newRequest.Header.Get("Key2")) 161 | 162 | newRequestUrl, newRequestUrlErr := client.NewRequestUrl("GET", config.BaseURL, nil, false, false, "", false) 163 | assert.Nil(t, newRequestUrlErr, "NewRequest for proxy test failed.") 164 | if newRequestUrlErr != nil { 165 | fmt.Printf("NewRequest() error: %s\n", newRequestUrlErr.Error()) 166 | } 167 | 168 | assert.Equal(t, "Value1", newRequestUrl.Header.Get("Key1")) 169 | assert.Equal(t, "Value2", newRequestUrl.Header.Get("Key2")) 170 | } 171 | 172 | func TestParseApplicationError(t *testing.T) { 173 | appErr1 := map[string]interface{}{ 174 | "error": map[string]interface{}{ 175 | "error": "An error string", 176 | "message": "An error message", 177 | }, 178 | } 179 | 180 | appErr2 := map[string]interface{}{ 181 | "error": "Another error string", 182 | } 183 | 184 | // Note: since the client implementation uses a Go map, 185 | // the entry order of strings concatenated is not preserved 186 | errStr := getApplicationErrorMessage(appErr1) 187 | assert.Contains(t, errStr, "An error string") 188 | assert.Contains(t, errStr, "An error message") 189 | 190 | errStr = getApplicationErrorMessage(appErr2) 191 | assert.Equal(t, "Another error string", errStr) 192 | } 193 | -------------------------------------------------------------------------------- /whisk/info.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 whisk 19 | 20 | import ( 21 | "errors" 22 | "fmt" 23 | "github.com/apache/openwhisk-client-go/wski18n" 24 | "net/http" 25 | "net/url" 26 | ) 27 | 28 | type Info struct { 29 | Whisk string `json:"whisk,omitempty"` 30 | Version string `json:"version,omitempty"` 31 | Build string `json:"build,omitempty"` 32 | BuildNo string `json:"buildno,omitempty"` 33 | } 34 | 35 | type InfoService struct { 36 | client *Client 37 | } 38 | 39 | func (s *InfoService) Get() (*Info, *http.Response, error) { 40 | // make a request to c.BaseURL / v1 41 | urlStr := fmt.Sprintf("%s/%s", s.client.BaseURL.String(), s.client.Config.Version) 42 | u, err := url.Parse(urlStr) 43 | if err != nil { 44 | Debug(DbgError, "url.Parse(%s) error: %s\n", urlStr, err) 45 | errStr := wski18n.T("Unable to URL parse '{{.version}}': {{.err}}", 46 | map[string]interface{}{"version": urlStr, "err": err}) 47 | werr := MakeWskError(errors.New(errStr), EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 48 | return nil, nil, werr 49 | } 50 | 51 | req, err := http.NewRequest("GET", u.String(), nil) 52 | if err != nil { 53 | Debug(DbgError, "http.NewRequest(GET, %s) error: %s\n", u.String(), err) 54 | errStr := wski18n.T("Unable to create HTTP request for GET '{{.url}}': {{.err}}", 55 | map[string]interface{}{"url": u.String(), "err": err}) 56 | werr := MakeWskError(errors.New(errStr), EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 57 | return nil, nil, werr 58 | } 59 | 60 | Debug(DbgInfo, "Sending HTTP URL '%s'; req %#v\n", req.URL.String(), req) 61 | info := new(Info) 62 | resp, err := s.client.Do(req, &info, ExitWithSuccessOnTimeout) 63 | if err != nil { 64 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err) 65 | return nil, nil, err 66 | } 67 | 68 | return info, resp, nil 69 | } 70 | -------------------------------------------------------------------------------- /whisk/keyvaluearr_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 whisk 21 | 22 | import ( 23 | "github.com/stretchr/testify/assert" 24 | "testing" 25 | ) 26 | 27 | func TestKeyValueArrReplaceOrAdd(t *testing.T) { 28 | kvArr := make(KeyValueArr, 3) 29 | kvArr[0] = KeyValue{"key0", "value0"} 30 | kvArr[1] = KeyValue{"key1", "value1"} 31 | kvArr[2] = KeyValue{"key2", "value2"} 32 | 33 | kvNew := &KeyValue{"keyAdd", "valueAdd"} 34 | kvArrNew := kvArr.AddOrReplace(kvNew) 35 | assert.Equal(t, len(kvArrNew), 4) 36 | assert.Equal(t, kvArrNew.GetValue(kvNew.Key), kvNew.Value) 37 | 38 | kvAdd := &KeyValue{"key3", "valueReplace"} 39 | kvArrNew = kvArr.AddOrReplace(kvAdd) 40 | assert.Equal(t, len(kvArrNew), 4) 41 | assert.Equal(t, kvArrNew.GetValue(kvAdd.Key), kvAdd.Value) 42 | } 43 | -------------------------------------------------------------------------------- /whisk/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 whisk 19 | 20 | import ( 21 | "errors" 22 | "fmt" 23 | "github.com/apache/openwhisk-client-go/wski18n" 24 | "net/http" 25 | "strings" 26 | ) 27 | 28 | type Namespace struct { 29 | Name string `json:"name"` 30 | } 31 | 32 | type NamespaceService struct { 33 | client *Client 34 | } 35 | 36 | // Compare(sortable) compares namespace to sortable for the purpose of sorting. 37 | // REQUIRED: sortable must also be of type Namespace. 38 | // ***Method of type Sortable*** 39 | func (namespace Namespace) Compare(sortable Sortable) bool { 40 | // Sorts alphabetically 41 | namespaceToCompare := sortable.(Namespace) 42 | var namespaceString string 43 | var compareString string 44 | 45 | namespaceString = strings.ToLower(namespace.Name) 46 | compareString = strings.ToLower(namespaceToCompare.Name) 47 | 48 | return namespaceString < compareString 49 | } 50 | 51 | // ToHeaderString() returns the header for a list of namespaces 52 | func (namespace Namespace) ToHeaderString() string { 53 | return fmt.Sprintf("%s\n", "namespaces") 54 | } 55 | 56 | // ToSummaryRowString() returns a compound string of required parameters for printing 57 | // from CLI command `wsk namespace list`. 58 | // ***Method of type Sortable*** 59 | func (namespace Namespace) ToSummaryRowString() string { 60 | return fmt.Sprintf("%s\n", namespace.Name) 61 | } 62 | 63 | // get a list of available namespaces 64 | func (s *NamespaceService) List() ([]Namespace, *http.Response, error) { 65 | // make a request to c.BaseURL / namespaces 66 | 67 | // Create the request against the namespaces resource 68 | s.client.Config.Namespace = "" 69 | route := "" 70 | req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl) 71 | if err != nil { 72 | Debug(DbgError, "s.client.NewRequest(GET) error: %s\n", err) 73 | errStr := wski18n.T("Unable to create HTTP request for GET: {{.err}}", 74 | map[string]interface{}{"err": err}) 75 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 76 | return nil, nil, werr 77 | } 78 | 79 | var namespaceNames []string 80 | resp, err := s.client.Do(req, &namespaceNames, ExitWithSuccessOnTimeout) 81 | if err != nil { 82 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err) 83 | return nil, resp, err 84 | } 85 | 86 | var namespaces []Namespace 87 | for _, nsName := range namespaceNames { 88 | ns := Namespace{ 89 | Name: nsName, 90 | } 91 | namespaces = append(namespaces, ns) 92 | } 93 | 94 | Debug(DbgInfo, "Returning []namespaces: %#v\n", namespaces) 95 | return namespaces, resp, nil 96 | } 97 | -------------------------------------------------------------------------------- /whisk/package.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 whisk 19 | 20 | import ( 21 | "errors" 22 | "fmt" 23 | "github.com/apache/openwhisk-client-go/wski18n" 24 | "net/http" 25 | "net/url" 26 | "strings" 27 | ) 28 | 29 | type PackageService struct { 30 | client *Client 31 | } 32 | 33 | type PackageInterface interface { 34 | GetName() string 35 | } 36 | 37 | // Use this struct to represent the package/binding sent from the Whisk server 38 | // Binding is a bool ???MWD20160602 now seeing Binding as a struct??? 39 | type Package struct { 40 | Namespace string `json:"namespace,omitempty"` 41 | Name string `json:"name,omitempty"` 42 | Version string `json:"version,omitempty"` 43 | Publish *bool `json:"publish,omitempty"` 44 | Annotations KeyValueArr `json:"annotations,omitempty"` 45 | Parameters KeyValueArr `json:"parameters,omitempty"` 46 | Binding *Binding `json:"binding,omitempty"` 47 | Actions []Action `json:"actions,omitempty"` 48 | Feeds []Action `json:"feeds,omitempty"` 49 | Updated int64 `json:"updated,omitempty"` 50 | } 51 | 52 | func (p *Package) GetName() string { 53 | return p.Name 54 | } 55 | 56 | // Use this struct when creating a binding 57 | // Publish is NOT optional; Binding is a namespace/name object, not a bool 58 | type BindingPackage struct { 59 | Namespace string `json:"-"` 60 | Name string `json:"-"` 61 | Version string `json:"version,omitempty"` 62 | Publish *bool `json:"publish,omitempty"` 63 | Annotations KeyValueArr `json:"annotations,omitempty"` 64 | Parameters KeyValueArr `json:"parameters,omitempty"` 65 | Binding `json:"binding"` 66 | } 67 | 68 | func (p *BindingPackage) GetName() string { 69 | return p.Name 70 | } 71 | 72 | type Binding struct { 73 | Namespace string `json:"namespace,omitempty"` 74 | Name string `json:"name,omitempty"` 75 | } 76 | 77 | type BindingUpdates struct { 78 | Added []string `json:"added,omitempty"` 79 | Updated []string `json:"updated,omitempty"` 80 | Deleted []string `json:"deleted,omitempty"` 81 | } 82 | 83 | type PackageListOptions struct { 84 | Public bool `url:"public,omitempty"` 85 | Limit int `url:"limit"` 86 | Skip int `url:"skip"` 87 | Since int `url:"since,omitempty"` 88 | Docs bool `url:"docs,omitempty"` 89 | } 90 | 91 | // Compare(sortable) compares xPackage to sortable for the purpose of sorting. 92 | // REQUIRED: sortable must also be of type Package. 93 | // ***Method of type Sortable*** 94 | func (xPackage Package) Compare(sortable Sortable) bool { 95 | // Sorts alphabetically by NAMESPACE -> PACKAGE_NAME 96 | packageToCompare := sortable.(Package) 97 | 98 | var packageString string 99 | var compareString string 100 | 101 | packageString = strings.ToLower(fmt.Sprintf("%s%s", xPackage.Namespace, 102 | xPackage.Name)) 103 | compareString = strings.ToLower(fmt.Sprintf("%s%s", packageToCompare.Namespace, 104 | packageToCompare.Name)) 105 | 106 | return packageString < compareString 107 | } 108 | 109 | // ToHeaderString() returns the header for a list of actions 110 | func (pkg Package) ToHeaderString() string { 111 | return fmt.Sprintf("%s\n", "packages") 112 | } 113 | 114 | // ToSummaryRowString() returns a compound string of required parameters for printing 115 | // from CLI command `wsk package list`. 116 | // ***Method of type Sortable*** 117 | func (xPackage Package) ToSummaryRowString() string { 118 | publishState := wski18n.T("private") 119 | 120 | if xPackage.Publish != nil && *xPackage.Publish { 121 | publishState = wski18n.T("shared") 122 | } 123 | 124 | return fmt.Sprintf("%-70s %s\n", fmt.Sprintf("/%s/%s", xPackage.Namespace, 125 | xPackage.Name), publishState) 126 | } 127 | 128 | func (s *PackageService) List(options *PackageListOptions) ([]Package, *http.Response, error) { 129 | route := fmt.Sprintf("packages") 130 | routeUrl, err := addRouteOptions(route, options) 131 | if err != nil { 132 | Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err) 133 | errStr := wski18n.T("Unable to build request URL: {{.err}}", map[string]interface{}{"err": err}) 134 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 135 | return nil, nil, werr 136 | } 137 | 138 | req, err := s.client.NewRequestUrl("GET", routeUrl, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) 139 | if err != nil { 140 | Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired); error: '%s'\n", route, err) 141 | errStr := wski18n.T("Unable to create GET HTTP request for '{{.route}}': {{.err}}", 142 | map[string]interface{}{"route": route, "err": err}) 143 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 144 | return nil, nil, werr 145 | } 146 | 147 | var packages []Package 148 | resp, err := s.client.Do(req, &packages, ExitWithSuccessOnTimeout) 149 | if err != nil { 150 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err) 151 | return nil, resp, err 152 | } 153 | 154 | return packages, resp, err 155 | 156 | } 157 | 158 | func (s *PackageService) Get(packageName string) (*Package, *http.Response, error) { 159 | // Encode resource name as a path (with no query params) before inserting it into the URI 160 | // This way any '?' chars in the name won't be treated as the beginning of the query params 161 | packageName = (&url.URL{Path: packageName}).String() 162 | route := fmt.Sprintf("packages/%s", packageName) 163 | 164 | req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl) 165 | if err != nil { 166 | Debug(DbgError, "http.NewRequest(GET, %s); error: '%s'\n", route, err) 167 | errStr := wski18n.T("Unable to create GET HTTP request for '{{.route}}': {{.err}}", 168 | map[string]interface{}{"route": route, "err": err}) 169 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 170 | return nil, nil, werr 171 | } 172 | 173 | p := new(Package) 174 | resp, err := s.client.Do(req, &p, ExitWithSuccessOnTimeout) 175 | if err != nil { 176 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err) 177 | return nil, resp, err 178 | } 179 | 180 | return p, resp, nil 181 | 182 | } 183 | 184 | func (s *PackageService) Insert(x_package PackageInterface, overwrite bool) (*Package, *http.Response, error) { 185 | // Encode resource name as a path (with no query params) before inserting it into the URI 186 | // This way any '?' chars in the name won't be treated as the beginning of the query params 187 | packageName := (&url.URL{Path: x_package.GetName()}).String() 188 | route := fmt.Sprintf("packages/%s?overwrite=%t", packageName, overwrite) 189 | 190 | req, err := s.client.NewRequest("PUT", route, x_package, IncludeNamespaceInUrl) 191 | if err != nil { 192 | Debug(DbgError, "http.NewRequest(PUT, %s); error: '%s'\n", route, err) 193 | errStr := wski18n.T("Unable to create PUT HTTP request for '{{.route}}': {{.err}}", 194 | map[string]interface{}{"route": route, "err": err}) 195 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 196 | return nil, nil, werr 197 | } 198 | 199 | p := new(Package) 200 | resp, err := s.client.Do(req, &p, ExitWithSuccessOnTimeout) 201 | if err != nil { 202 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err) 203 | return nil, resp, err 204 | } 205 | 206 | return p, resp, nil 207 | } 208 | 209 | func (s *PackageService) Delete(packageName string) (*http.Response, error) { 210 | // Encode resource name as a path (with no query params) before inserting it into the URI 211 | // This way any '?' chars in the name won't be treated as the beginning of the query params 212 | packageName = (&url.URL{Path: packageName}).String() 213 | route := fmt.Sprintf("packages/%s", packageName) 214 | 215 | req, err := s.client.NewRequest("DELETE", route, nil, IncludeNamespaceInUrl) 216 | if err != nil { 217 | Debug(DbgError, "http.NewRequest(DELETE, %s); error: '%s'\n", route, err) 218 | errStr := wski18n.T("Unable to create DELETE HTTP request for '{{.route}}': {{.err}}", 219 | map[string]interface{}{"route": route, "err": err}) 220 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 221 | return nil, werr 222 | } 223 | 224 | resp, err := s.client.Do(req, nil, ExitWithSuccessOnTimeout) 225 | if err != nil { 226 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err) 227 | return resp, err 228 | } 229 | 230 | return resp, nil 231 | } 232 | 233 | func (s *PackageService) Refresh() (*BindingUpdates, *http.Response, error) { 234 | route := "packages/refresh" 235 | 236 | req, err := s.client.NewRequest("POST", route, nil, IncludeNamespaceInUrl) 237 | if err != nil { 238 | Debug(DbgError, "http.NewRequest(POST, %s); error: '%s'\n", route, err) 239 | errStr := wski18n.T("Unable to create POST HTTP request for '{{.route}}': {{.err}}", 240 | map[string]interface{}{"route": route, "err": err}) 241 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 242 | return nil, nil, werr 243 | } 244 | 245 | updates := &BindingUpdates{} 246 | resp, err := s.client.Do(req, updates, ExitWithSuccessOnTimeout) 247 | if err != nil { 248 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err) 249 | return nil, resp, err 250 | } 251 | 252 | return updates, resp, nil 253 | } 254 | -------------------------------------------------------------------------------- /whisk/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 whisk 19 | 20 | import ( 21 | "errors" 22 | "fmt" 23 | "github.com/apache/openwhisk-client-go/wski18n" 24 | "net/http" 25 | "net/url" 26 | "strings" 27 | ) 28 | 29 | type RuleService struct { 30 | client *Client 31 | } 32 | 33 | type Rule struct { 34 | Namespace string `json:"namespace,omitempty"` 35 | Name string `json:"name,omitempty"` 36 | Version string `json:"version,omitempty"` 37 | Annotations KeyValueArr `json:"annotations,omitempty"` 38 | Status string `json:"status"` 39 | Trigger interface{} `json:"trigger"` 40 | Action interface{} `json:"action"` 41 | Publish *bool `json:"publish,omitempty"` 42 | Updated int64 `json:"updated,omitempty"` 43 | } 44 | 45 | type RuleListOptions struct { 46 | Limit int `url:"limit"` 47 | Skip int `url:"skip"` 48 | Docs bool `url:"docs,omitempty"` 49 | } 50 | 51 | // Compare(sortable) compares rule to sortable for the purpose of sorting. 52 | // REQUIRED: sortable must also be of type Rule. 53 | // ***Method of type Sortable*** 54 | func (rule Rule) Compare(sortable Sortable) bool { 55 | // Sorts alphabetically by NAMESPACE -> PACKAGE_NAME 56 | ruleToCompare := sortable.(Rule) 57 | var ruleString string 58 | var compareString string 59 | 60 | ruleString = strings.ToLower(fmt.Sprintf("%s%s", rule.Namespace, rule.Name)) 61 | compareString = strings.ToLower(fmt.Sprintf("%s%s", ruleToCompare.Namespace, 62 | ruleToCompare.Name)) 63 | 64 | return ruleString < compareString 65 | } 66 | 67 | // ToHeaderString() returns the header for a list of rules 68 | func (rule Rule) ToHeaderString() string { 69 | return fmt.Sprintf("%s\n", "rules") 70 | } 71 | 72 | // ToSummaryRowString() returns a compound string of required parameters for printing 73 | // from CLI command `wsk rule list`. 74 | // ***Method of type Sortable*** 75 | func (rule Rule) ToSummaryRowString() string { 76 | publishState := wski18n.T("private") 77 | 78 | return fmt.Sprintf("%-70s %-20s %s\n", fmt.Sprintf("/%s/%s", rule.Namespace, 79 | rule.Name), publishState, rule.Status) 80 | } 81 | 82 | func (s *RuleService) List(options *RuleListOptions) ([]Rule, *http.Response, error) { 83 | route := "rules" 84 | routeUrl, err := addRouteOptions(route, options) 85 | if err != nil { 86 | Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err) 87 | errStr := wski18n.T("Unable to append options '{{.options}}' to URL route '{{.route}}': {{.err}}", 88 | map[string]interface{}{"options": fmt.Sprintf("%#v", options), "route": route, "err": err}) 89 | werr := MakeWskError(errors.New(errStr), EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 90 | return nil, nil, werr 91 | } 92 | 93 | req, err := s.client.NewRequestUrl("GET", routeUrl, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) 94 | if err != nil { 95 | Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired); error: '%s'\n", route, err) 96 | errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}", 97 | map[string]interface{}{"route": route, "err": err}) 98 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 99 | return nil, nil, werr 100 | } 101 | 102 | var rules []Rule 103 | resp, err := s.client.Do(req, &rules, ExitWithSuccessOnTimeout) 104 | if err != nil { 105 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err) 106 | return nil, resp, err 107 | } 108 | 109 | return rules, resp, err 110 | } 111 | 112 | func (s *RuleService) Insert(rule *Rule, overwrite bool) (*Rule, *http.Response, error) { 113 | // Encode resource name as a path (with no query params) before inserting it into the URI 114 | // This way any '?' chars in the name won't be treated as the beginning of the query params 115 | ruleName := (&url.URL{Path: rule.Name}).String() 116 | route := fmt.Sprintf("rules/%s?overwrite=%t", ruleName, overwrite) 117 | 118 | routeUrl, err := url.Parse(route) 119 | if err != nil { 120 | Debug(DbgError, "url.Parse(%s) error: %s\n", route, err) 121 | errStr := wski18n.T("Invalid request URL '{{.url}}': {{.err}}", 122 | map[string]interface{}{"url": route, "err": err}) 123 | werr := MakeWskError(errors.New(errStr), EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 124 | return nil, nil, werr 125 | } 126 | 127 | req, err := s.client.NewRequestUrl("PUT", routeUrl, rule, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) 128 | if err != nil { 129 | Debug(DbgError, "http.NewRequestUrl(PUT, %s, %+v, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired); error: '%s'\n", routeUrl, rule, err) 130 | errStr := wski18n.T("Unable to create HTTP request for PUT '{{.route}}': {{.err}}", 131 | map[string]interface{}{"route": routeUrl, "err": err}) 132 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 133 | return nil, nil, werr 134 | } 135 | 136 | r := new(Rule) 137 | resp, err := s.client.Do(req, &r, ExitWithSuccessOnTimeout) 138 | if err != nil { 139 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err) 140 | return nil, resp, err 141 | } 142 | 143 | return r, resp, nil 144 | } 145 | 146 | func (s *RuleService) Get(ruleName string) (*Rule, *http.Response, error) { 147 | // Encode resource name as a path (with no query params) before inserting it into the URI 148 | // This way any '?' chars in the name won't be treated as the beginning of the query params 149 | ruleName = (&url.URL{Path: ruleName}).String() 150 | route := fmt.Sprintf("rules/%s", ruleName) 151 | 152 | req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl) 153 | if err != nil { 154 | Debug(DbgError, "http.NewRequest(GET, %s); error: '%s'\n", route, err) 155 | errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}", 156 | map[string]interface{}{"route": route, "err": err}) 157 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 158 | return nil, nil, werr 159 | } 160 | 161 | r := new(Rule) 162 | resp, err := s.client.Do(req, &r, ExitWithSuccessOnTimeout) 163 | if err != nil { 164 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err) 165 | return nil, resp, err 166 | } 167 | 168 | return r, resp, nil 169 | } 170 | 171 | func (s *RuleService) Delete(ruleName string) (*http.Response, error) { 172 | // Encode resource name as a path (with no query params) before inserting it into the URI 173 | // This way any '?' chars in the name won't be treated as the beginning of the query params 174 | ruleName = (&url.URL{Path: ruleName}).String() 175 | route := fmt.Sprintf("rules/%s", ruleName) 176 | 177 | req, err := s.client.NewRequest("DELETE", route, nil, IncludeNamespaceInUrl) 178 | if err != nil { 179 | Debug(DbgError, "http.NewRequest(DELETE, %s); error: '%s'\n", route, err) 180 | errStr := wski18n.T("Unable to create HTTP request for DELETE '{{.route}}': {{.err}}", 181 | map[string]interface{}{"route": route, "err": err}) 182 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 183 | return nil, werr 184 | } 185 | 186 | resp, err := s.client.Do(req, nil, ExitWithSuccessOnTimeout) 187 | if err != nil { 188 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err) 189 | return resp, err 190 | } 191 | 192 | return resp, nil 193 | } 194 | 195 | func (s *RuleService) SetState(ruleName string, state string) (*Rule, *http.Response, error) { 196 | state = strings.ToLower(state) 197 | if state != "active" && state != "inactive" { 198 | errStr := wski18n.T("Internal error. Invalid state option '{{.state}}'. Valid options are \"active\" and \"inactive\".", 199 | map[string]interface{}{"state": state}) 200 | werr := MakeWskError(errors.New(errStr), EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, DISPLAY_USAGE) 201 | return nil, nil, werr 202 | } 203 | 204 | // Encode resource name as a path (with no query params) before inserting it into the URI 205 | // This way any '?' chars in the name won't be treated as the beginning of the query params 206 | ruleName = (&url.URL{Path: ruleName}).String() 207 | route := fmt.Sprintf("rules/%s", ruleName) 208 | 209 | ruleState := &Rule{Status: state} 210 | 211 | req, err := s.client.NewRequest("POST", route, ruleState, IncludeNamespaceInUrl) 212 | if err != nil { 213 | Debug(DbgError, "http.NewRequest(POST, %s); error: '%s'\n", route, err) 214 | errStr := wski18n.T("Unable to create HTTP request for POST '{{.route}}': {{.err}}", 215 | map[string]interface{}{"route": route, "err": err}) 216 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 217 | return nil, nil, werr 218 | } 219 | 220 | r := new(Rule) 221 | resp, err := s.client.Do(req, &r, ExitWithSuccessOnTimeout) 222 | if err != nil { 223 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err) 224 | return nil, resp, err 225 | } 226 | 227 | return r, resp, nil 228 | } 229 | -------------------------------------------------------------------------------- /whisk/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 whisk 19 | 20 | import ( 21 | "errors" 22 | "fmt" 23 | "github.com/apache/openwhisk-client-go/wski18n" 24 | "net/http" 25 | ) 26 | 27 | type SdkService struct { 28 | client *Client 29 | } 30 | 31 | // Structure for SDK request responses 32 | type Sdk struct { 33 | // TODO :: Add SDK fields 34 | } 35 | 36 | type SdkRequest struct { 37 | // TODO :: Add SDK 38 | } 39 | 40 | // Install artifact {component = docker || swift || iOS} 41 | func (s *SdkService) Install(relFileUrl string) (*http.Response, error) { 42 | baseURL := s.client.Config.BaseURL 43 | // Remove everything but the scheme, host, and port 44 | baseURL.Path, baseURL.RawQuery, baseURL.Fragment = "", "", "" 45 | 46 | urlStr := fmt.Sprintf("%s/%s", baseURL, relFileUrl) 47 | 48 | req, err := http.NewRequest("GET", urlStr, nil) 49 | if err != nil { 50 | Debug(DbgError, "http.NewRequest(GET, %s, nil) error: %s\n", urlStr, err) 51 | errStr := wski18n.T("Unable to create HTTP request for GET '{{.url}}': {{.err}}", 52 | map[string]interface{}{"url": urlStr, "err": err}) 53 | werr := MakeWskError(errors.New(errStr), EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 54 | return nil, werr 55 | } 56 | 57 | if IsVerbose() { 58 | fmt.Println("REQUEST:") 59 | fmt.Printf("[%s]\t%s\n", req.Method, req.URL) 60 | if len(req.Header) > 0 { 61 | fmt.Println("Req Headers") 62 | PrintJSON(req.Header) 63 | } 64 | if req.Body != nil { 65 | fmt.Println("Req Body") 66 | fmt.Println(req.Body) 67 | } 68 | } 69 | 70 | // Directly use the HTTP client, not the Whisk CLI client, so that the response body is left alone 71 | resp, err := s.client.client.Do(req) 72 | if err != nil { 73 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err) 74 | return resp, err 75 | } 76 | 77 | return resp, nil 78 | } 79 | -------------------------------------------------------------------------------- /whisk/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 whisk 19 | 20 | import ( 21 | "encoding/json" 22 | "strings" 23 | ) 24 | 25 | type KeyValue struct { 26 | Key string `json:"key"` 27 | Value interface{} `json:"value"` 28 | } 29 | 30 | type KeyValueArr []KeyValue 31 | 32 | /* 33 | Retrieves a value associated with a given key from a KeyValueArr. A key of type string must be passed to the method. 34 | An interface will be returned containing the found value. If a key could not be found, a nil value will be returned. 35 | */ 36 | func (keyValueArr KeyValueArr) GetValue(key string) (res interface{}) { 37 | for i := 0; i < len(keyValueArr); i++ { 38 | if keyValueArr[i].Key == key { 39 | res = keyValueArr[i].Value 40 | break 41 | } 42 | } 43 | 44 | Debug(DbgInfo, "Got value '%v' for key '%s' from '%v'\n", res, key, keyValueArr) 45 | 46 | return res 47 | } 48 | 49 | func (keyValueArr KeyValueArr) FindKeyValue(key string) int { 50 | for i := 0; i < len(keyValueArr); i++ { 51 | if strings.ToLower(keyValueArr[i].Key) == strings.ToLower(key) { 52 | return i 53 | } 54 | } 55 | 56 | return -1 57 | } 58 | 59 | /* 60 | * Adds the specified KeyValue to the key value array. If the KeyValue's key 61 | * is already in the array, that entry is updated with the KeyValue's value. 62 | * 63 | * Returns a new key value array with the update 64 | */ 65 | func (keyValueArr KeyValueArr) AddOrReplace(kv *KeyValue) KeyValueArr { 66 | var replaced = false 67 | for i := 0; i < len(keyValueArr); i++ { 68 | if strings.ToLower(keyValueArr[i].Key) == strings.ToLower(kv.Key) { 69 | keyValueArr[i].Value = kv.Value 70 | replaced = true 71 | } 72 | } 73 | if !replaced { 74 | return append(keyValueArr, *kv) 75 | } 76 | return keyValueArr 77 | } 78 | 79 | /* 80 | Appends items from appKeyValueArr to keyValueArr if the appKeyValueArr item does not exist in keyValueArr. 81 | */ 82 | func (keyValueArr KeyValueArr) AppendKeyValueArr(appKeyValueArr KeyValueArr) KeyValueArr { 83 | for i := 0; i < len(appKeyValueArr); i++ { 84 | if KeyValueArr.FindKeyValue(keyValueArr, appKeyValueArr[i].Key) == -1 { 85 | keyValueArr = append(keyValueArr, appKeyValueArr[i]) 86 | } 87 | } 88 | 89 | return keyValueArr 90 | } 91 | 92 | type Annotations []map[string]interface{} 93 | 94 | type Parameters *json.RawMessage 95 | 96 | type Limits struct { 97 | Timeout *int `json:"timeout,omitempty"` 98 | Memory *int `json:"memory,omitempty"` 99 | Logsize *int `json:"logs,omitempty"` 100 | Concurrency *int `json:"concurrency,omitempty"` 101 | } 102 | -------------------------------------------------------------------------------- /whisk/start.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 whisk 19 | 20 | import ( 21 | "bufio" 22 | "encoding/json" 23 | "fmt" 24 | "io" 25 | "log" 26 | "os" 27 | ) 28 | 29 | // ActionFunction is the signature of an action in OpenWhisk 30 | type ActionFunction func(event json.RawMessage) (json.RawMessage, error) 31 | 32 | // actual implementation of a read-eval-print-loop 33 | func repl(fn ActionFunction, in io.Reader, out io.Writer) { 34 | // read loop 35 | reader := bufio.NewReader(in) 36 | for { 37 | event, err := reader.ReadBytes('\n') 38 | if err != nil { 39 | break 40 | } 41 | result, err := fn(event) 42 | if err != nil { 43 | fmt.Fprintf(out, "{ error: %q}\n", err.Error()) 44 | continue 45 | } 46 | fmt.Fprintln(out, string(result)) 47 | } 48 | } 49 | 50 | // Start will start a loop reading in stdin and outputting in fd3 51 | // This is expected to be uses for implementing Go actions 52 | func Start(fn ActionFunction) { 53 | out := os.NewFile(3, "pipe") 54 | defer out.Close() 55 | repl(fn, os.Stdin, out) 56 | } 57 | 58 | // StartWithArgs will execute the function for each arg 59 | // If there are no args it will start a read-write loop on the function 60 | // Expected to be used as starting point for implementing Go Actions 61 | // as whisk.StartWithArgs(function, os.Args[:1]) 62 | // if args are 2 (command and one parameter) it will invoke the function once 63 | // otherwise it will stat the function in a read-write loop 64 | func StartWithArgs(action ActionFunction, args []string) { 65 | // handle command line argument 66 | if len(args) > 0 { 67 | for _, arg := range args { 68 | log.Println(arg) 69 | result, err := action([]byte(arg)) 70 | if err == nil { 71 | fmt.Println(string(result)) 72 | } else { 73 | log.Println(err) 74 | } 75 | } 76 | return 77 | } 78 | Start(action) 79 | } 80 | -------------------------------------------------------------------------------- /whisk/start_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 | package whisk 18 | 19 | import ( 20 | "bytes" 21 | "encoding/json" 22 | "fmt" 23 | "os" 24 | ) 25 | 26 | func hello(event json.RawMessage) (json.RawMessage, error) { 27 | var obj map[string]interface{} 28 | json.Unmarshal(event, &obj) 29 | name, ok := obj["name"].(string) 30 | if !ok { 31 | name = "Stranger" 32 | } 33 | fmt.Printf("name=%s\n", name) 34 | msg := map[string]string{"message": ("Hello, " + name + "!")} 35 | return json.Marshal(msg) 36 | } 37 | 38 | func Example_repl() { 39 | in := bytes.NewBufferString("{\"name\":\"Mike\"}\nerr\n") 40 | repl(hello, in, os.Stdout) 41 | // Output: 42 | // name=Mike 43 | // {"message":"Hello, Mike!"} 44 | // name=Stranger 45 | // {"message":"Hello, Stranger!"} 46 | } 47 | 48 | func ExampleStart() { 49 | StartWithArgs(hello, []string{"{\"name\":\"Mike\"}", "err"}) 50 | // Output: 51 | // name=Mike 52 | // {"message":"Hello, Mike!"} 53 | // name=Stranger 54 | // {"message":"Hello, Stranger!"} 55 | } 56 | -------------------------------------------------------------------------------- /whisk/trace.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 whisk 19 | 20 | import ( 21 | "fmt" 22 | "os" 23 | "runtime" 24 | "strings" 25 | ) 26 | 27 | type DebugLevel string 28 | 29 | const ( 30 | DbgInfo DebugLevel = "Inf" 31 | DbgWarn DebugLevel = "Wrn" 32 | DbgError DebugLevel = "Err" 33 | DbgFatal DebugLevel = "Ftl" 34 | ) 35 | 36 | const MaxNameLen int = 25 37 | 38 | var isVerbose bool 39 | var isDebug bool 40 | 41 | func init() { 42 | if len(os.Getenv("WSK_CLI_DEBUG")) > 0 { // Useful for tracing init() code, before parms are parsed 43 | SetDebug(true) 44 | } 45 | } 46 | 47 | func SetDebug(b bool) { 48 | isDebug = b 49 | } 50 | 51 | func SetVerbose(b bool) { 52 | isVerbose = b 53 | } 54 | 55 | func IsVerbose() bool { 56 | return isVerbose || isDebug 57 | } 58 | func IsDebug() bool { 59 | return isDebug 60 | } 61 | 62 | /* Function for tracing debug level messages to stdout 63 | Output format: 64 | [file-or-function-name]:line-#:[DebugLevel] The formatted message without any appended \n 65 | */ 66 | func Debug(dl DebugLevel, msgFormat string, args ...interface{}) { 67 | if isDebug { 68 | pc, file, line, _ := runtime.Caller(1) 69 | fcn := runtime.FuncForPC(pc) 70 | msg := fmt.Sprintf(msgFormat, args...) 71 | fcnName := fcn.Name() 72 | 73 | // Cobra command Run/RunE functions are anonymous, so the function name is unfriendly; 74 | // use the file name instead 75 | if strings.Contains(fcnName, "commands.glob.") || strings.Contains(fcnName, "whisk.glob.") { 76 | fcnName = file 77 | } 78 | 79 | // Only interested in the the trailing function/file name characters 80 | if len(fcnName) > MaxNameLen { 81 | fcnName = fcnName[len(fcnName)-MaxNameLen:] 82 | } 83 | fmt.Printf("[%-25s]:%03d:[%3s] %v", fcnName, line, dl, msg) 84 | } 85 | } 86 | 87 | /* Function for tracing debug level messages to stdout 88 | Output format: 89 | [file-or-function-name]:line-#:[DebugLevel] The formatted message without any appended newline characters 90 | */ 91 | func Verbose(msgFormat string, args ...interface{}) { 92 | if IsVerbose() { 93 | msg := fmt.Sprintf(msgFormat, args...) 94 | fmt.Printf("%v", msg) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /whisk/trigger.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 whisk 19 | 20 | import ( 21 | "errors" 22 | "fmt" 23 | "github.com/apache/openwhisk-client-go/wski18n" 24 | "net/http" 25 | "net/url" 26 | "strings" 27 | ) 28 | 29 | type TriggerService struct { 30 | client ClientInterface 31 | } 32 | 33 | type Trigger struct { 34 | Namespace string `json:"namespace,omitempty"` 35 | Name string `json:"name,omityempty"` 36 | Version string `json:"version,omitempty"` 37 | ActivationId string `json:"activationId,omitempty"` 38 | Annotations KeyValueArr `json:"annotations,omitempty"` 39 | Parameters KeyValueArr `json:"parameters,omitempty"` 40 | Limits *Limits `json:"limits,omitempty"` 41 | Publish *bool `json:"publish,omitempty"` 42 | Rules map[string]interface{} `json:"rules,omitempty"` 43 | Updated int64 `json:"updated,omitempty"` 44 | } 45 | 46 | type TriggerListOptions struct { 47 | Limit int `url:"limit"` 48 | Skip int `url:"skip"` 49 | Docs bool `url:"docs,omitempty"` 50 | } 51 | 52 | // Compare(sortable) compares trigger to sortable for the purpose of sorting. 53 | // REQUIRED: sortable must also be of type Trigger. 54 | // ***Method of type Sortable*** 55 | func (trigger Trigger) Compare(sortable Sortable) bool { 56 | // Sorts alphabetically by NAMESPACE -> TRIGGER_NAME 57 | triggerToCompare := sortable.(Trigger) 58 | var triggerString string 59 | var compareString string 60 | 61 | triggerString = strings.ToLower(fmt.Sprintf("%s%s", trigger.Namespace, 62 | trigger.Name)) 63 | compareString = strings.ToLower(fmt.Sprintf("%s%s", triggerToCompare.Namespace, 64 | triggerToCompare.Name)) 65 | 66 | return triggerString < compareString 67 | } 68 | 69 | // ToHeaderString() returns the header for a list of triggers 70 | func (trigger Trigger) ToHeaderString() string { 71 | return fmt.Sprintf("%s\n", "triggers") 72 | } 73 | 74 | // ToSummaryRowString() returns a compound string of required parameters for printing 75 | // from CLI command `wsk trigger list`. 76 | // ***Method of type Sortable*** 77 | func (trigger Trigger) ToSummaryRowString() string { 78 | publishState := wski18n.T("private") 79 | 80 | return fmt.Sprintf("%-70s %s\n", fmt.Sprintf("/%s/%s", trigger.Namespace, 81 | trigger.Name), publishState) 82 | } 83 | 84 | func (s *TriggerService) List(options *TriggerListOptions) ([]Trigger, *http.Response, error) { 85 | route := "triggers" 86 | routeUrl, err := addRouteOptions(route, options) 87 | if err != nil { 88 | Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err) 89 | errStr := wski18n.T("Unable to append options '{{.options}}' to URL route '{{.route}}': {{.err}}", 90 | map[string]interface{}{"options": fmt.Sprintf("%#v", options), "route": route, "err": err}) 91 | werr := MakeWskError(errors.New(errStr), EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 92 | return nil, nil, werr 93 | } 94 | 95 | req, err := s.client.NewRequestUrl("GET", routeUrl, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) 96 | if err != nil { 97 | Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired); error: '%s'\n", route, err) 98 | errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}", 99 | map[string]interface{}{"route": route, "err": err}) 100 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 101 | return nil, nil, werr 102 | } 103 | 104 | var triggers []Trigger 105 | resp, err := s.client.Do(req, &triggers, ExitWithSuccessOnTimeout) 106 | if err != nil { 107 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err) 108 | return nil, resp, err 109 | } 110 | 111 | return triggers, resp, nil 112 | } 113 | 114 | func (s *TriggerService) Insert(trigger *Trigger, overwrite bool) (*Trigger, *http.Response, error) { 115 | // Encode resource name as a path (with no query params) before inserting it into the URI 116 | // This way any '?' chars in the name won't be treated as the beginning of the query params 117 | triggerName := (&url.URL{Path: trigger.Name}).String() 118 | route := fmt.Sprintf("triggers/%s?overwrite=%t", triggerName, overwrite) 119 | 120 | routeUrl, err := url.Parse(route) 121 | if err != nil { 122 | Debug(DbgError, "url.Parse(%s) error: %s\n", route, err) 123 | errStr := wski18n.T("Invalid request URL '{{.url}}': {{.err}}", 124 | map[string]interface{}{"url": route, "err": err}) 125 | werr := MakeWskError(errors.New(errStr), EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 126 | return nil, nil, werr 127 | } 128 | 129 | req, err := s.client.NewRequestUrl("PUT", routeUrl, trigger, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) 130 | if err != nil { 131 | Debug(DbgError, "http.NewRequestUrl(PUT, %s, %+v, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired); error: '%s'\n", routeUrl, trigger, err) 132 | errStr := wski18n.T("Unable to create HTTP request for PUT '{{.route}}': {{.err}}", 133 | map[string]interface{}{"route": routeUrl, "err": err}) 134 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 135 | return nil, nil, werr 136 | } 137 | 138 | t := new(Trigger) 139 | resp, err := s.client.Do(req, &t, ExitWithSuccessOnTimeout) 140 | if err != nil { 141 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err) 142 | return nil, resp, err 143 | } 144 | 145 | return t, resp, nil 146 | 147 | } 148 | 149 | func (s *TriggerService) Get(triggerName string) (*Trigger, *http.Response, error) { 150 | // Encode resource name as a path (with no query params) before inserting it into the URI 151 | // This way any '?' chars in the name won't be treated as the beginning of the query params 152 | triggerName = (&url.URL{Path: triggerName}).String() 153 | route := fmt.Sprintf("triggers/%s", triggerName) 154 | 155 | req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl) 156 | if err != nil { 157 | Debug(DbgError, "http.NewRequest(GET, %s); error: '%s'\n", route, err) 158 | errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}", 159 | map[string]interface{}{"route": route, "err": err}) 160 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 161 | return nil, nil, werr 162 | } 163 | 164 | t := new(Trigger) 165 | resp, err := s.client.Do(req, &t, ExitWithSuccessOnTimeout) 166 | if err != nil { 167 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err) 168 | return nil, resp, err 169 | } 170 | 171 | return t, resp, nil 172 | 173 | } 174 | 175 | func (s *TriggerService) Delete(triggerName string) (*Trigger, *http.Response, error) { 176 | // Encode resource name as a path (with no query params) before inserting it into the URI 177 | // This way any '?' chars in the name won't be treated as the beginning of the query params 178 | triggerName = (&url.URL{Path: triggerName}).String() 179 | route := fmt.Sprintf("triggers/%s", triggerName) 180 | 181 | req, err := s.client.NewRequest("DELETE", route, nil, IncludeNamespaceInUrl) 182 | if err != nil { 183 | Debug(DbgError, "http.NewRequest(DELETE, %s); error: '%s'\n", route, err) 184 | errStr := wski18n.T("Unable to create HTTP request for DELETE '{{.route}}': {{.err}}", 185 | map[string]interface{}{"route": route, "err": err}) 186 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 187 | return nil, nil, werr 188 | } 189 | 190 | t := new(Trigger) 191 | resp, err := s.client.Do(req, &t, ExitWithSuccessOnTimeout) 192 | if err != nil { 193 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err) 194 | return nil, resp, err 195 | } 196 | 197 | return t, resp, nil 198 | } 199 | 200 | func (s *TriggerService) Fire(triggerName string, payload interface{}) (*Trigger, *http.Response, error) { 201 | // Encode resource name as a path (with no query params) before inserting it into the URI 202 | // This way any '?' chars in the name won't be treated as the beginning of the query params 203 | triggerName = (&url.URL{Path: triggerName}).String() 204 | route := fmt.Sprintf("triggers/%s", triggerName) 205 | 206 | req, err := s.client.NewRequest("POST", route, payload, IncludeNamespaceInUrl) 207 | if err != nil { 208 | Debug(DbgError, " http.NewRequest(POST, %s); error: '%s'\n", route, err) 209 | errStr := wski18n.T("Unable to create HTTP request for POST '{{.route}}': {{.err}}", 210 | map[string]interface{}{"route": route, "err": err}) 211 | werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 212 | return nil, nil, werr 213 | } 214 | 215 | t := new(Trigger) 216 | resp, err := s.client.Do(req, &t, ExitWithSuccessOnTimeout) 217 | if err != nil { 218 | Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err) 219 | return nil, resp, err 220 | } 221 | 222 | return t, resp, nil 223 | } 224 | -------------------------------------------------------------------------------- /whisk/trigger_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 whisk 21 | 22 | import ( 23 | "encoding/json" 24 | "fmt" 25 | "github.com/stretchr/testify/assert" 26 | "io/ioutil" 27 | "net/http" 28 | "net/url" 29 | "strings" 30 | "testing" 31 | ) 32 | 33 | const ( 34 | TRIGGER_GET_NO_RULES = `{ 35 | "namespace": "test@openwhisk_dev", 36 | "name": "testTrigger", 37 | "publish": false, 38 | "version": "0.0.1", 39 | "limits": {} 40 | }` 41 | 42 | TRIGGER_GET_WITH_RULES = `{ 43 | "namespace": "test@openwhisk_dev", 44 | "name": "testTrigger", 45 | "publish": false, 46 | "version": "0.0.1", 47 | "limits": {}, 48 | "rules": { 49 | "guest/inactiverule": { 50 | "action": { 51 | "name": "web-echo-env", 52 | "path": "guest" 53 | }, 54 | "status": "inactive" 55 | } 56 | } 57 | }` 58 | ) 59 | 60 | type TriggerResponse struct { 61 | Body string 62 | } 63 | 64 | type TriggerRequest struct { 65 | Method string 66 | URL string 67 | } 68 | 69 | var triggerResponse = &TriggerResponse{} 70 | var triggerRequest = &TriggerRequest{} 71 | 72 | type MockTriggerClient struct{} 73 | 74 | func (c *MockTriggerClient) NewRequestUrl(method string, urlRelResource *url.URL, body interface{}, includeNamespaceInUrl bool, appendOpenWhiskPath bool, encodeBodyAs string, useAuthentication bool) (*http.Request, error) { 75 | return &http.Request{}, nil 76 | } 77 | 78 | func (c *MockTriggerClient) NewRequest(method, urlStr string, body interface{}, includeNamespaceInUrl bool) (*http.Request, error) { 79 | triggerRequest.Method = method 80 | triggerRequest.URL = urlStr 81 | 82 | request, err := http.NewRequest(method, urlStr, nil) 83 | if err != nil { 84 | fmt.Printf("http.NewRequest() failure: %s\n", err) 85 | return &http.Request{}, err 86 | } 87 | 88 | return request, nil 89 | } 90 | 91 | func (c *MockTriggerClient) Do(req *http.Request, v interface{}, ExitWithErrorOnTimeout bool, secretToObfuscate ...ObfuscateSet) (*http.Response, error) { 92 | var reader = strings.NewReader(triggerResponse.Body) 93 | 94 | dc := json.NewDecoder(reader) 95 | dc.UseNumber() 96 | err := dc.Decode(v) 97 | 98 | if err != nil { 99 | fmt.Printf("json decode failure: %s\n", err) 100 | return nil, err 101 | } 102 | 103 | resp := &http.Response{ 104 | StatusCode: 200, 105 | Body: ioutil.NopCloser(reader), 106 | } 107 | 108 | return resp, nil 109 | } 110 | 111 | func TestTriggerGet(t *testing.T) { 112 | assert := assert.New(t) 113 | mockClient := &MockTriggerClient{} 114 | triggerService := &TriggerService{client: mockClient} 115 | var nilMap map[string]interface{} 116 | 117 | triggerResponse.Body = TRIGGER_GET_NO_RULES 118 | trigger, _, _ := triggerService.Get("testTrigger") 119 | assert.Equal("GET", triggerRequest.Method) 120 | assert.Equal("triggers/testTrigger", triggerRequest.URL) 121 | assert.Equal(nilMap, trigger.Rules) 122 | 123 | triggerResponse.Body = TRIGGER_GET_WITH_RULES 124 | var expectedTrigger map[string]interface{} 125 | json.Unmarshal([]byte(triggerResponse.Body), &expectedTrigger) 126 | expectedRules, _ := expectedTrigger["rules"] 127 | trigger, _, _ = triggerService.Get("testTrigger") 128 | assert.Equal("GET", triggerRequest.Method) 129 | assert.Equal("triggers/testTrigger", triggerRequest.URL) 130 | assert.Equal(expectedRules, trigger.Rules) 131 | } 132 | -------------------------------------------------------------------------------- /whisk/util.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 whisk 19 | 20 | import ( 21 | "errors" 22 | "fmt" 23 | "net/url" 24 | "reflect" 25 | "strings" 26 | 27 | "github.com/apache/openwhisk-client-go/wski18n" 28 | "github.com/fatih/color" 29 | "github.com/google/go-querystring/query" 30 | "github.com/hokaccha/go-prettyjson" 31 | ) 32 | 33 | // Sortable items are anything that needs to be sorted for listing purposes. 34 | type Sortable interface { 35 | // Compare(sortable) compares an two sortables and returns true 36 | // if the item calling the Compare method is less than toBeCompared. 37 | // Sorts alphabetically by default, can have other parameters to sort by 38 | // passed by sortByName. 39 | Compare(toBeCompared Sortable) bool 40 | } 41 | 42 | // Printable items are anything that need to be printed for listing purposes. 43 | type Printable interface { 44 | ToHeaderString() string // Prints header information of a Printable 45 | ToSummaryRowString() string // Prints summary info of one Printable 46 | } 47 | 48 | // addOptions adds the parameters in opt as URL query parameters to s. opt 49 | // must be a struct whose fields may contain "url" tags. 50 | func addRouteOptions(route string, options interface{}) (*url.URL, error) { 51 | Debug(DbgInfo, "Adding options %+v to route '%s'\n", options, route) 52 | u, err := url.Parse(route) 53 | if err != nil { 54 | Debug(DbgError, "url.Parse(%s) error: %s\n", route, err) 55 | errStr := wski18n.T("Unable to parse URL '{{.route}}': {{.err}}", 56 | map[string]interface{}{"route": route, "err": err}) 57 | werr := MakeWskError(errors.New(errStr), EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 58 | return nil, werr 59 | } 60 | 61 | v := reflect.ValueOf(options) 62 | if v.Kind() == reflect.Ptr && v.IsNil() { 63 | return u, nil 64 | } 65 | 66 | qs, err := query.Values(options) 67 | if err != nil { 68 | Debug(DbgError, "query.Values(%#v) error: %s\n", options, err) 69 | errStr := wski18n.T("Unable to process URL query options '{{.options}}': {{.err}}", 70 | map[string]interface{}{"options": fmt.Sprintf("%#v", options), "err": err}) 71 | werr := MakeWskError(errors.New(errStr), EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE) 72 | return nil, werr 73 | } 74 | 75 | u.RawQuery = qs.Encode() 76 | Debug(DbgInfo, "Returning route options '%s' from input struct %+v\n", u.String(), options) 77 | return u, nil 78 | } 79 | 80 | func PrintJSON(v interface{}) { 81 | output, _ := prettyjson.Marshal(v) 82 | fmt.Fprintln(color.Output, string(output)) 83 | } 84 | 85 | func GetURLBase(host string, path string) (*url.URL, error) { 86 | if len(host) == 0 { 87 | errMsg := wski18n.T("An API host must be provided.\n") 88 | whiskErr := MakeWskError(errors.New(errMsg), EXIT_CODE_ERR_GENERAL, 89 | DISPLAY_MSG, DISPLAY_USAGE) 90 | return nil, whiskErr 91 | } 92 | 93 | if !strings.HasPrefix(host, "http") { 94 | host = "https://" + host 95 | } 96 | 97 | urlBase := fmt.Sprintf("%s%s", host, path) 98 | url, err := url.Parse(urlBase) 99 | 100 | if len(url.Scheme) == 0 || len(url.Host) == 0 { 101 | urlBase = fmt.Sprintf("https://%s%s", host, path) 102 | url, err = url.Parse(urlBase) 103 | } 104 | 105 | return url, err 106 | } 107 | -------------------------------------------------------------------------------- /whisk/wskerror.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 whisk 19 | 20 | const EXIT_CODE_ERR_GENERAL int = 1 21 | const EXIT_CODE_ERR_USAGE int = 2 22 | const EXIT_CODE_ERR_NETWORK int = 3 23 | const EXIT_CODE_ERR_HTTP_RESP int = 4 24 | const NOT_ALLOWED int = 149 25 | const EXIT_CODE_TIMED_OUT int = 202 26 | const EXIT_CODE_NOT_FOUND int = 148 27 | 28 | const DISPLAY_MSG bool = true 29 | const NO_DISPLAY_MSG bool = false 30 | const DISPLAY_USAGE bool = true 31 | const NO_DISPLAY_USAGE bool = false 32 | const NO_MSG_DISPLAYED bool = false 33 | const DISPLAY_PREFIX bool = true 34 | const NO_DISPLAY_PREFIX bool = false 35 | const APPLICATION_ERR bool = true 36 | const NO_APPLICATION_ERR bool = false 37 | const TIMED_OUT bool = true 38 | 39 | type WskError struct { 40 | RootErr error // Parent error 41 | ExitCode int // Error code to be returned to the OS 42 | DisplayMsg bool // When true, the error message should be displayed to console 43 | MsgDisplayed bool // When true, the error message has already been displayed, don't display it again 44 | DisplayUsage bool // When true, the CLI usage should be displayed before exiting 45 | DisplayPrefix bool // When true, the CLI will prefix an error message with "error: " 46 | ApplicationError bool // When true, the error is a result of an application failure 47 | TimedOut bool // When True, the error is a result of a timeout 48 | } 49 | 50 | /* 51 | Prints the error message contained inside an WskError. An error prefix may, or may not be displayed depending on the 52 | WskError's setting for DisplayPrefix. 53 | 54 | Parameters: 55 | err - WskError object used to display an error message from 56 | */ 57 | func (whiskError WskError) Error() string { 58 | return whiskError.RootErr.Error() 59 | } 60 | 61 | /* 62 | Instantiate a WskError structure 63 | Parameters: 64 | error - RootErr. object implementing the error interface 65 | int - ExitCode. Used if error object does not have an exit code OR if ExitCodeOverride is true 66 | bool - DisplayMsg. If true, the error message should be displayed on the console 67 | bool - DisplayUsage. If true, the command usage syntax/help should be displayed on the console 68 | bool - MsgDisplayed. If true, the error message has been displayed on the console 69 | bool - DisplayPreview. If true, the error message will be prefixed with "error: " 70 | bool - TimedOut. If true, the error is a result of a timeout 71 | */ 72 | func MakeWskError(err error, exitCode int, flags ...bool) (resWhiskError *WskError) { 73 | resWhiskError = &WskError{ 74 | RootErr: err, 75 | ExitCode: exitCode, 76 | DisplayMsg: false, 77 | DisplayUsage: false, 78 | MsgDisplayed: false, 79 | DisplayPrefix: true, 80 | ApplicationError: false, 81 | TimedOut: false, 82 | } 83 | 84 | if len(flags) > 0 { 85 | resWhiskError.DisplayMsg = flags[0] 86 | } 87 | if len(flags) > 1 { 88 | resWhiskError.DisplayUsage = flags[1] 89 | } 90 | if len(flags) > 2 { 91 | resWhiskError.MsgDisplayed = flags[2] 92 | } 93 | if len(flags) > 3 { 94 | resWhiskError.DisplayPrefix = flags[3] 95 | } 96 | if len(flags) > 4 { 97 | resWhiskError.ApplicationError = flags[4] 98 | } 99 | if len(flags) > 5 { 100 | resWhiskError.TimedOut = flags[5] 101 | } 102 | 103 | return resWhiskError 104 | } 105 | 106 | /* 107 | Instantiate a WskError structure 108 | Parameters: 109 | error - RootErr. object implementing the error interface 110 | WskError - WskError being wrappered. It's exitcode will be used as this WskError's exitcode. Ignored if nil 111 | int - ExitCode. Used if error object is nil or if the error object is not a WskError 112 | bool - DisplayMsg. If true, the error message should be displayed on the console 113 | bool - DisplayUsage. If true, the command usage syntax/help should be displayed on the console 114 | bool - MsgDisplayed. If true, the error message has been displayed on the console 115 | bool - ApplicationError. If true, the error is a result of an application error 116 | bool - TimedOut. If true, the error resulted from a timeout 117 | */ 118 | func MakeWskErrorFromWskError(baseError error, whiskError error, exitCode int, flags ...bool) (resWhiskError *WskError) { 119 | 120 | // Get the exit code, and flags from the existing Whisk error 121 | if whiskError != nil { 122 | 123 | // Ensure the Whisk error is a pointer 124 | switch errorType := whiskError.(type) { 125 | case *WskError: 126 | resWhiskError = errorType 127 | case WskError: 128 | resWhiskError = &errorType 129 | } 130 | 131 | if resWhiskError != nil { 132 | exitCode, flags = getWhiskErrorProperties(resWhiskError, flags...) 133 | } 134 | } 135 | 136 | return MakeWskError(baseError, exitCode, flags...) 137 | } 138 | 139 | /* 140 | Returns the settings from a WskError. Values returned will include ExitCode, DisplayMsg, DisplayUsage, MsgDisplayed, 141 | DisplayPrefix, TimedOut. 142 | 143 | Parameters: 144 | whiskError - WskError to examine. 145 | flags - Boolean values that may override the WskError object's values for DisplayMsg, DisplayUsage, 146 | MsgDisplayed, ApplicationError, TimedOut. 147 | */ 148 | func getWhiskErrorProperties(whiskError *WskError, flags ...bool) (int, []bool) { 149 | if len(flags) > 0 { 150 | flags[0] = whiskError.DisplayMsg 151 | } else { 152 | flags = append(flags, whiskError.DisplayMsg) 153 | } 154 | 155 | if len(flags) > 1 { 156 | flags[1] = whiskError.DisplayUsage || flags[1] 157 | } else { 158 | flags = append(flags, whiskError.DisplayUsage) 159 | } 160 | 161 | if len(flags) > 2 { 162 | flags[2] = whiskError.MsgDisplayed || flags[2] 163 | } else { 164 | flags = append(flags, whiskError.MsgDisplayed) 165 | } 166 | 167 | if len(flags) > 3 { 168 | flags[3] = whiskError.DisplayPrefix || flags[3] 169 | } else { 170 | flags = append(flags, whiskError.DisplayPrefix) 171 | } 172 | 173 | if len(flags) > 4 { 174 | flags[4] = whiskError.ApplicationError || flags[4] 175 | } else { 176 | flags = append(flags, whiskError.ApplicationError) 177 | } 178 | 179 | if len(flags) > 5 { 180 | flags[5] = whiskError.TimedOut || flags[5] 181 | } else { 182 | flags = append(flags, whiskError.TimedOut) 183 | } 184 | 185 | return whiskError.ExitCode, flags 186 | } 187 | -------------------------------------------------------------------------------- /whisk/wskprops.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 whisk 19 | 20 | import ( 21 | "bufio" 22 | "errors" 23 | "fmt" 24 | "github.com/apache/openwhisk-client-go/wski18n" 25 | "io/ioutil" 26 | "net/url" 27 | "os" 28 | "strings" 29 | ) 30 | 31 | const ( 32 | OPENWHISK_HOME = "OPENWHISK_HOME" 33 | HOMEPATH = "HOME" 34 | DEFAULT_LOCAL_CONFIG = ".wskprops" 35 | OPENWHISK_PROPERTIES = "whisk.properties" 36 | TEST_AUTH_FILE = "testing.auth" 37 | OPENWHISK_PRO = "whisk.api.host.proto" 38 | OPENWHISK_PORT = "whisk.api.host.port" 39 | OPENWHISK_HOST = "whisk.api.host.name" 40 | DEFAULT_VERSION = "v1" 41 | DEFAULT_NAMESPACE = "_" 42 | 43 | APIGW_ACCESS_TOKEN = "APIGW_ACCESS_TOKEN" 44 | APIGW_TENANT_ID = "APIGW_TENANT_ID" 45 | APIHOST = "APIHOST" 46 | APIVERSION = "APIVERSION" 47 | AUTH = "AUTH" 48 | CERT = "CERT" 49 | KEY = "KEY" 50 | NAMESPACE = "NAMESPACE" 51 | 52 | DEFAULT_SOURCE = "wsk props" 53 | WSKPROP = "wsk props" 54 | WHISK_PROPERTY = "whisk.properties" 55 | ) 56 | 57 | type Wskprops struct { 58 | APIGWSpaceSuid string 59 | APIGWTenantId string 60 | APIHost string 61 | Apiversion string 62 | AuthAPIGWKey string 63 | AuthKey string 64 | Cert string 65 | Key string 66 | Namespace string 67 | Source string 68 | } 69 | 70 | func GetUrlBase(host string) (*url.URL, error) { 71 | urlBase := fmt.Sprintf("%s/api", host) 72 | url, err := url.Parse(urlBase) 73 | 74 | if err != nil || len(url.Scheme) == 0 || len(url.Host) == 0 { 75 | urlBase = fmt.Sprintf("https://%s/api", host) 76 | url, err = url.Parse(urlBase) 77 | } 78 | 79 | return url, err 80 | } 81 | 82 | func convertWskpropsToConfig(dep *Wskprops) *Config { 83 | var config Config 84 | config.Host = dep.APIHost 85 | if len(config.Host) != 0 { 86 | v, err := GetUrlBase(config.Host) 87 | if err == nil { 88 | config.BaseURL = v 89 | } 90 | } 91 | config.Namespace = dep.Namespace 92 | config.Cert = dep.Cert 93 | config.Key = dep.Key 94 | config.AuthToken = dep.AuthKey 95 | 96 | config.Version = dep.Apiversion 97 | config.Verbose = false 98 | config.Debug = false 99 | config.Insecure = true 100 | 101 | return &config 102 | } 103 | 104 | func GetDefaultConfigFromProperties(pi Properties) (*Config, error) { 105 | var config *Config 106 | dep, e := GetDefaultWskProp(pi) 107 | config = convertWskpropsToConfig(dep) 108 | return config, e 109 | } 110 | 111 | func GetConfigFromWhiskProperties(pi Properties) (*Config, error) { 112 | var config *Config 113 | dep, e := GetWskPropFromWhiskProperty(pi) 114 | config = convertWskpropsToConfig(dep) 115 | return config, e 116 | } 117 | 118 | func GetConfigFromWskprops(pi Properties, path string) (*Config, error) { 119 | var config *Config 120 | dep, e := GetWskPropFromWskprops(pi, path) 121 | config = convertWskpropsToConfig(dep) 122 | return config, e 123 | } 124 | 125 | var GetDefaultWskProp = func(pi Properties) (*Wskprops, error) { 126 | var dep *Wskprops 127 | dep = pi.GetPropsFromWskprops("") 128 | error := ValidateWskprops(dep) 129 | if error != nil { 130 | dep_whisk := pi.GetPropsFromWhiskProperties() 131 | error_whisk := ValidateWskprops(dep_whisk) 132 | if error_whisk != nil { 133 | return dep, error 134 | } else { 135 | return dep_whisk, error_whisk 136 | } 137 | } 138 | return dep, error 139 | } 140 | 141 | var GetWskPropFromWskprops = func(pi Properties, path string) (*Wskprops, error) { 142 | var dep *Wskprops 143 | dep = pi.GetPropsFromWskprops(path) 144 | error := ValidateWskprops(dep) 145 | return dep, error 146 | } 147 | 148 | var GetWskPropFromWhiskProperty = func(pi Properties) (*Wskprops, error) { 149 | var dep *Wskprops 150 | dep = pi.GetPropsFromWhiskProperties() 151 | error := ValidateWskprops(dep) 152 | return dep, error 153 | } 154 | 155 | type Properties interface { 156 | GetPropsFromWskprops(string) *Wskprops 157 | GetPropsFromWhiskProperties() *Wskprops 158 | } 159 | 160 | type PropertiesImp struct { 161 | OsPackage OSPackage 162 | } 163 | 164 | func (pi PropertiesImp) GetPropsFromWskprops(path string) *Wskprops { 165 | dep := GetDefaultWskprops(WSKPROP) 166 | 167 | var wskpropsPath string 168 | if path != "" { 169 | wskpropsPath = path 170 | } else { 171 | wskpropsPath = pi.OsPackage.Getenv(HOMEPATH, "") + "/" + DEFAULT_LOCAL_CONFIG 172 | } 173 | results, err := ReadProps(wskpropsPath) 174 | 175 | if err == nil { 176 | 177 | dep.APIHost = GetValue(results, APIHOST, dep.APIHost) 178 | 179 | dep.AuthKey = GetValue(results, AUTH, dep.AuthKey) 180 | dep.Namespace = GetValue(results, NAMESPACE, dep.Namespace) 181 | dep.AuthAPIGWKey = GetValue(results, APIGW_ACCESS_TOKEN, dep.AuthAPIGWKey) 182 | dep.APIGWTenantId = GetValue(results, APIGW_TENANT_ID, dep.APIGWTenantId) 183 | if len(dep.AuthKey) > 0 { 184 | dep.APIGWSpaceSuid = strings.Split(dep.AuthKey, ":")[0] 185 | } 186 | dep.Apiversion = GetValue(results, APIVERSION, dep.Apiversion) 187 | dep.Key = GetValue(results, KEY, dep.Key) 188 | dep.Cert = GetValue(results, CERT, dep.Cert) 189 | } 190 | 191 | return dep 192 | } 193 | 194 | func (pi PropertiesImp) GetPropsFromWhiskProperties() *Wskprops { 195 | dep := GetDefaultWskprops(WHISK_PROPERTY) 196 | path := pi.OsPackage.Getenv(OPENWHISK_HOME, "") + "/" + OPENWHISK_PROPERTIES 197 | results, err := ReadProps(path) 198 | 199 | if err == nil { 200 | // TODO Determine why we have a hardcoed "test.auth" file here, is this only for unit tests? documented? 201 | authPath := GetValue(results, TEST_AUTH_FILE, "") 202 | b, err := ioutil.ReadFile(authPath) 203 | if err == nil { 204 | dep.AuthKey = strings.TrimSpace(string(b)) 205 | } 206 | dep.APIHost = GetValue(results, OPENWHISK_HOST, "") 207 | dep.Namespace = DEFAULT_NAMESPACE 208 | if len(dep.AuthKey) > 0 { 209 | dep.APIGWSpaceSuid = strings.Split(dep.AuthKey, ":")[0] 210 | } 211 | } 212 | return dep 213 | } 214 | 215 | var ValidateWskprops = func(wskprops *Wskprops) error { 216 | // There are at least two fields: WHISKAPIURL and AuthKey, mandatory for a valid Wskprops. 217 | errStr := "" 218 | if len(wskprops.APIHost) == 0 { 219 | if wskprops.Source == WHISK_PROPERTY { 220 | errStr = wski18n.T("OpenWhisk API host is missing (Please configure WHISK_APIHOST in .wskprops under the system HOME directory.)") 221 | } else { 222 | errStr = wski18n.T("OpenWhisk API host is missing (Please configure whisk.api.host.proto, whisk.api.host.name and whisk.api.host.port in whisk.properties under the OPENWHISK_HOME directory.)") 223 | } 224 | return MakeWskError(errors.New(errStr), EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, DISPLAY_USAGE) 225 | } else { 226 | if len(wskprops.AuthKey) == 0 { 227 | if wskprops.Source == WHISK_PROPERTY { 228 | errStr = wski18n.T("Authentication key is missing (Please configure AUTH in .wskprops under the system HOME directory.)") 229 | } else { 230 | errStr = wski18n.T("Authentication key is missing (Please configure testing.auth as the path of the authentication key file in whisk.properties under the OPENWHISK_HOME directory.)") 231 | } 232 | return MakeWskError(errors.New(errStr), EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, DISPLAY_USAGE) 233 | } else { 234 | return nil 235 | } 236 | } 237 | } 238 | 239 | type OSPackage interface { 240 | Getenv(key string, defaultValue string) string 241 | } 242 | 243 | type OSPackageImp struct{} 244 | 245 | func (osPackage OSPackageImp) Getenv(key string, defaultValue string) string { 246 | value := os.Getenv(key) 247 | if len(value) == 0 { 248 | return defaultValue 249 | } 250 | return value 251 | } 252 | 253 | func GetDefaultConfig() (*Config, error) { 254 | pi := PropertiesImp{ 255 | OsPackage: OSPackageImp{}, 256 | } 257 | return GetDefaultConfigFromProperties(pi) 258 | } 259 | 260 | func GetWhiskPropertiesConfig() (*Config, error) { 261 | pi := PropertiesImp{ 262 | OsPackage: OSPackageImp{}, 263 | } 264 | return GetConfigFromWhiskProperties(pi) 265 | } 266 | 267 | func GetProperties() Properties { 268 | return PropertiesImp{ 269 | OsPackage: OSPackageImp{}, 270 | } 271 | } 272 | 273 | func GetWskpropsConfig(path string) (*Config, error) { 274 | pi := GetProperties() 275 | return GetConfigFromWskprops(pi, path) 276 | } 277 | 278 | func GetDefaultWskprops(source string) *Wskprops { 279 | if len(source) == 0 { 280 | source = DEFAULT_SOURCE 281 | } 282 | 283 | dep := Wskprops{ 284 | APIHost: "", 285 | AuthKey: "", 286 | Namespace: DEFAULT_NAMESPACE, 287 | AuthAPIGWKey: "", 288 | APIGWTenantId: "", 289 | APIGWSpaceSuid: "", 290 | Apiversion: DEFAULT_VERSION, 291 | Key: "", 292 | Cert: "", 293 | Source: source, 294 | } 295 | return &dep 296 | } 297 | 298 | func GetValue(StoredValues map[string]string, key string, defaultvalue string) string { 299 | if val, ok := StoredValues[key]; ok { 300 | return val 301 | } else { 302 | return defaultvalue 303 | } 304 | } 305 | 306 | func ReadProps(path string) (map[string]string, error) { 307 | 308 | props := map[string]string{} 309 | 310 | file, err := os.Open(path) 311 | if err != nil { 312 | return props, err 313 | } 314 | defer file.Close() 315 | 316 | lines := []string{} 317 | scanner := bufio.NewScanner(file) 318 | for scanner.Scan() { 319 | lines = append(lines, scanner.Text()) 320 | } 321 | 322 | props = map[string]string{} 323 | for _, line := range lines { 324 | kv := strings.Split(line, "=") 325 | if len(kv) != 2 { 326 | continue 327 | } 328 | key := strings.TrimSpace(kv[0]) 329 | value := strings.TrimSpace(kv[1]) 330 | props[key] = value 331 | } 332 | 333 | return props, nil 334 | 335 | } 336 | -------------------------------------------------------------------------------- /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/i18n_resources.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 | // Code generated by go-bindata. 19 | // sources: 20 | // wski18n/resources/de_DE.all.json 21 | // wski18n/resources/en_US.all.json 22 | // wski18n/resources/es_ES.all.json 23 | // wski18n/resources/fr_FR.all.json 24 | // wski18n/resources/it_IT.all.json 25 | // wski18n/resources/ja_JA.all.json 26 | // wski18n/resources/ko_KR.all.json 27 | // wski18n/resources/pt_BR.all.json 28 | // wski18n/resources/zh_Hans.all.json 29 | // wski18n/resources/zh_Hant.all.json 30 | // DO NOT EDIT! 31 | 32 | package wski18n 33 | 34 | import ( 35 | "bytes" 36 | "compress/gzip" 37 | "fmt" 38 | "io" 39 | "io/ioutil" 40 | "os" 41 | "path/filepath" 42 | "strings" 43 | "time" 44 | ) 45 | 46 | func bindataRead(data []byte, name string) ([]byte, error) { 47 | gz, err := gzip.NewReader(bytes.NewBuffer(data)) 48 | if err != nil { 49 | return nil, fmt.Errorf("Read %q: %v", name, err) 50 | } 51 | 52 | var buf bytes.Buffer 53 | _, err = io.Copy(&buf, gz) 54 | clErr := gz.Close() 55 | 56 | if err != nil { 57 | return nil, fmt.Errorf("Read %q: %v", name, err) 58 | } 59 | if clErr != nil { 60 | return nil, clErr 61 | } 62 | 63 | return buf.Bytes(), nil 64 | } 65 | 66 | type asset struct { 67 | bytes []byte 68 | info os.FileInfo 69 | } 70 | 71 | type bindataFileInfo struct { 72 | name string 73 | size int64 74 | mode os.FileMode 75 | modTime time.Time 76 | } 77 | 78 | func (fi bindataFileInfo) Name() string { 79 | return fi.name 80 | } 81 | func (fi bindataFileInfo) Size() int64 { 82 | return fi.size 83 | } 84 | func (fi bindataFileInfo) Mode() os.FileMode { 85 | return fi.mode 86 | } 87 | func (fi bindataFileInfo) ModTime() time.Time { 88 | return fi.modTime 89 | } 90 | func (fi bindataFileInfo) IsDir() bool { 91 | return false 92 | } 93 | func (fi bindataFileInfo) Sys() interface{} { 94 | return nil 95 | } 96 | 97 | var _wski18nResourcesDe_deAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00") 98 | 99 | func wski18nResourcesDe_deAllJsonBytes() ([]byte, error) { 100 | return bindataRead( 101 | _wski18nResourcesDe_deAllJson, 102 | "wski18n/resources/de_DE.all.json", 103 | ) 104 | } 105 | 106 | func wski18nResourcesDe_deAllJson() (*asset, error) { 107 | bytes, err := wski18nResourcesDe_deAllJsonBytes() 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | info := bindataFileInfo{name: "wski18n/resources/de_DE.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510603210, 0)} 113 | a := &asset{bytes: bytes, info: info} 114 | return a, nil 115 | } 116 | 117 | var _wski18nResourcesEn_usAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x58\x5f\x6f\xdb\x36\x10\x7f\xf7\xa7\x38\xf8\xc5\x19\xe0\x0a\x7b\xd9\xc3\xba\xa7\xa0\x33\xe6\xa0\x5d\x63\xac\xce\x3a\x60\x19\x06\x46\x3c\x5b\x87\xc8\xa4\x7a\xa4\x9c\xb9\x86\xbe\xfb\x40\xca\x72\xd2\xc4\xb4\xfe\x58\x49\xf3\x64\x81\xe6\xfd\xee\xc7\xbb\xe3\xfd\xe1\xdf\x03\x80\xed\x00\x00\x60\x48\x72\xf8\x16\x86\x57\x4a\xdc\xa4\x08\x56\x83\x90\x12\x58\xe7\x16\x41\x67\x96\xb4\x32\x30\xda\x6e\xa3\xdd\x77\x51\x8c\x86\xe3\x52\xce\xb2\x50\x26\x15\x6e\xb9\x06\xe0\x2d\x3c\x04\x18\x0e\x00\x8a\x71\x58\x7f\xcc\x28\x2c\xc2\x74\x3e\x9f\x01\xe3\x97\x1c\x8d\x85\x85\x66\x98\x5d\xcd\x3d\x13\x0f\x5d\x14\x23\x8f\x8a\xcc\x45\x51\xcb\xa8\x03\x64\x47\x92\xbf\x4d\x7a\x27\x79\x04\xb2\x23\xc9\x5f\x27\x1f\x26\xf3\x49\xdf\x3c\x8f\xa3\x76\x75\xfa\xe5\xa7\xfe\xbd\x7e\x04\xb3\x86\xa6\xc8\x32\x54\x32\x70\x31\xdc\x86\xab\x3f\x3e\xec\x62\xbf\x23\xe9\xd3\x35\x34\xb3\x74\x65\x10\x07\xe7\x80\x72\x4e\x3b\x59\xb7\x16\xe7\x20\x9d\x0b\xb5\x16\x29\xc9\xae\x2c\x1a\x8b\x1f\x54\x3e\x61\xd6\x0c\xa8\x62\x2d\x49\x2d\xf7\x20\x37\x5a\x6e\x6a\x35\x37\x93\x3d\xa2\x96\x14\x59\x12\x29\x7d\x7d\x20\xde\x50\x6b\x8d\x68\x5d\xe8\x4a\x09\x36\xd9\x5d\x07\x91\xdb\x04\x95\xa5\xd8\xab\x80\x04\x85\x44\x6e\x13\xa5\x6d\xc0\x0e\x12\x3b\xcf\x6d\xa2\x99\xbe\x96\x32\xb7\xb8\x01\x32\xa0\xb4\x85\x58\xab\x05\x2d\x73\x46\x09\x67\x6f\xde\x38\x6c\xf7\x8f\x3b\x2e\x31\xca\x1f\x02\xd4\x3a\xc3\x1d\x26\xa7\xe0\x7c\x76\x01\x89\x36\x16\x56\xb9\xf3\x2f\x42\xc6\x7a\x4d\x12\x65\x74\xad\x42\x1c\x6a\xa4\x1a\x38\xe8\xe5\xeb\xee\x3b\xbd\x5a\x09\x25\x61\x21\x28\x45\x09\x32\x2f\xa1\x54\xe9\x5a\xb7\x9a\x33\x06\x54\x37\x93\x3d\xa8\xf6\xa3\x06\x52\x16\x79\x21\xe2\x7b\x23\xfd\x02\x4a\x57\xe9\xda\x64\x5a\x19\xf4\x17\x0b\xf0\xbf\x0c\x63\x8b\x32\x40\xa3\x1b\x56\x3b\x6b\x78\x05\x4a\xa4\x5d\x2d\xf2\x44\xfe\xa0\xfa\x79\x82\xb0\xd0\x69\xaa\xef\xdc\x15\x17\x59\x96\x56\x97\x0a\x7d\x06\xb8\x13\x2e\x74\x63\xa4\x35\xca\xda\xdb\xda\x11\xec\xf5\xe5\xeb\x57\x9b\xc3\xee\xb1\xdc\x81\x32\xc1\xa6\x2c\xca\x6b\x64\x43\x5a\xb5\xab\xa7\x0d\x20\x4e\xec\x49\xbb\x96\xf8\xe6\x80\xdd\x09\xf6\xc3\xaa\x39\x95\x9b\x9c\xd2\x6f\xe2\xb1\x05\x81\x63\xb2\xcd\x2c\xe0\x0c\xf8\x84\xff\x69\x5d\x6e\x1b\xc8\x66\x24\xdd\x70\xd4\x33\xc9\x36\x90\xcd\x48\xee\x86\x8e\x9e\x79\xb6\x44\x6d\x68\x4f\x37\x76\xf4\x6d\xd0\x36\x98\x81\xcc\xbe\x2b\x4c\xbe\x28\x44\x50\xa5\x6a\x63\xc5\xbe\x91\xf0\x80\x7e\xa1\x28\x46\x11\xfc\xe9\x37\x54\x4d\x8a\x60\x84\xeb\xa1\x88\x2d\xad\xf1\x7a\x08\xae\xfa\x5d\x0f\x49\x55\x0b\x51\xb0\x24\x3c\xb7\xde\x1a\xaf\x94\xb9\xb6\xaa\x44\x1d\x5c\x50\x0b\x50\x47\x80\x75\x8c\xc6\x78\x84\x2f\x39\xf2\x26\xd0\xf7\xb5\xa1\xd4\x1e\xf2\x20\xc9\xed\x36\x5a\x99\x65\x51\xc0\x59\xac\x25\xba\xcd\xee\xb7\x28\x42\xdd\x77\x78\x7f\xb0\xcd\x89\xb5\x52\x18\x7b\x2f\x97\xcd\xd2\x18\x34\x83\xa5\x15\x4a\xd0\xb9\x8d\xe0\xcc\x87\xb5\xf3\x7e\x6e\xa0\x19\x8d\xd3\x71\x5b\xcf\xce\x63\xb8\xc1\x58\xe4\x06\xe1\x32\x43\xf5\x39\x21\x73\x7b\x3f\x04\x90\x81\x15\x19\x43\x6a\xd9\x61\x9a\x6e\x8a\x7c\x02\x65\xd7\xfd\x88\x8c\x4a\x48\x17\x21\xee\xa3\x28\x46\x0e\x9e\xca\xfb\x78\xd2\x83\x40\x57\x4d\x07\x8f\x74\xd4\x0a\x70\x36\x4b\x51\x18\xbc\x1f\xf7\xe0\xf3\xf4\xe2\xd3\xfb\x7f\xcf\x67\x17\x53\x97\x23\x49\x41\x74\x67\x6e\x33\xd6\x99\x81\x5c\x49\x64\xcf\xc9\x6c\x8c\xc5\x15\x4c\x2f\x7f\x9f\x80\x24\xc6\xd8\x6a\xde\x44\xa1\xf8\x7a\x51\x0a\xbd\x18\xe1\xce\xed\x8d\x44\x46\xde\xe0\x51\xc6\xda\xea\xf1\xe3\x55\x25\x56\xe8\xf3\xe7\xe3\xdd\x9a\xad\x63\x5d\x2e\x3b\xde\xc8\x96\xf0\x21\xf9\xcb\xd9\xe4\x63\x79\xca\x67\x32\xe1\x77\x3c\x40\xf0\xd9\xe2\xc1\x9c\xb0\x7b\x68\x08\xf3\x3f\xbf\x9a\x4f\xfb\x09\xbe\x97\xd0\xdc\xcb\x91\x2d\x1a\x4b\x6a\x19\xf9\xb7\x16\x61\xbc\xc2\x4c\xd8\x04\xf4\xa2\xcc\x03\x4f\xf1\x16\x94\x62\xdf\x91\xf6\xea\x69\xd7\x24\xee\x54\x8b\x72\x3e\xfd\xeb\xa7\x1f\x7f\xf6\xea\x32\x41\x5c\x3d\x29\xd8\x6f\x86\x7b\x46\x61\xb4\x6a\x91\xab\x4f\x02\x0f\xd6\xf4\x77\xc8\x76\x67\x94\xc7\x8f\x6f\x11\x3c\x35\x78\x82\x7b\x77\xec\x25\xc3\xaf\x6b\x3d\x2a\x08\x1e\xe0\xfd\xde\xa9\x2d\xe1\x2b\xc1\xe3\xf4\x7b\x81\x77\xe4\x07\xff\x0c\xfe\x0f\x00\x00\xff\xff\xab\x99\xa5\xc1\xc1\x1b\x00\x00") 118 | 119 | func wski18nResourcesEn_usAllJsonBytes() ([]byte, error) { 120 | return bindataRead( 121 | _wski18nResourcesEn_usAllJson, 122 | "wski18n/resources/en_US.all.json", 123 | ) 124 | } 125 | 126 | func wski18nResourcesEn_usAllJson() (*asset, error) { 127 | bytes, err := wski18nResourcesEn_usAllJsonBytes() 128 | if err != nil { 129 | return nil, err 130 | } 131 | 132 | info := bindataFileInfo{name: "wski18n/resources/en_US.all.json", size: 7105, mode: os.FileMode(420), modTime: time.Unix(1510603813, 0)} 133 | a := &asset{bytes: bytes, info: info} 134 | return a, nil 135 | } 136 | 137 | var _wski18nResourcesEs_esAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00") 138 | 139 | func wski18nResourcesEs_esAllJsonBytes() ([]byte, error) { 140 | return bindataRead( 141 | _wski18nResourcesEs_esAllJson, 142 | "wski18n/resources/es_ES.all.json", 143 | ) 144 | } 145 | 146 | func wski18nResourcesEs_esAllJson() (*asset, error) { 147 | bytes, err := wski18nResourcesEs_esAllJsonBytes() 148 | if err != nil { 149 | return nil, err 150 | } 151 | 152 | info := bindataFileInfo{name: "wski18n/resources/es_ES.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510603210, 0)} 153 | a := &asset{bytes: bytes, info: info} 154 | return a, nil 155 | } 156 | 157 | var _wski18nResourcesFr_frAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00") 158 | 159 | func wski18nResourcesFr_frAllJsonBytes() ([]byte, error) { 160 | return bindataRead( 161 | _wski18nResourcesFr_frAllJson, 162 | "wski18n/resources/fr_FR.all.json", 163 | ) 164 | } 165 | 166 | func wski18nResourcesFr_frAllJson() (*asset, error) { 167 | bytes, err := wski18nResourcesFr_frAllJsonBytes() 168 | if err != nil { 169 | return nil, err 170 | } 171 | 172 | info := bindataFileInfo{name: "wski18n/resources/fr_FR.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510603210, 0)} 173 | a := &asset{bytes: bytes, info: info} 174 | return a, nil 175 | } 176 | 177 | var _wski18nResourcesIt_itAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00") 178 | 179 | func wski18nResourcesIt_itAllJsonBytes() ([]byte, error) { 180 | return bindataRead( 181 | _wski18nResourcesIt_itAllJson, 182 | "wski18n/resources/it_IT.all.json", 183 | ) 184 | } 185 | 186 | func wski18nResourcesIt_itAllJson() (*asset, error) { 187 | bytes, err := wski18nResourcesIt_itAllJsonBytes() 188 | if err != nil { 189 | return nil, err 190 | } 191 | 192 | info := bindataFileInfo{name: "wski18n/resources/it_IT.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510603210, 0)} 193 | a := &asset{bytes: bytes, info: info} 194 | return a, nil 195 | } 196 | 197 | var _wski18nResourcesJa_jaAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00") 198 | 199 | func wski18nResourcesJa_jaAllJsonBytes() ([]byte, error) { 200 | return bindataRead( 201 | _wski18nResourcesJa_jaAllJson, 202 | "wski18n/resources/ja_JA.all.json", 203 | ) 204 | } 205 | 206 | func wski18nResourcesJa_jaAllJson() (*asset, error) { 207 | bytes, err := wski18nResourcesJa_jaAllJsonBytes() 208 | if err != nil { 209 | return nil, err 210 | } 211 | 212 | info := bindataFileInfo{name: "wski18n/resources/ja_JA.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510603210, 0)} 213 | a := &asset{bytes: bytes, info: info} 214 | return a, nil 215 | } 216 | 217 | var _wski18nResourcesKo_krAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00") 218 | 219 | func wski18nResourcesKo_krAllJsonBytes() ([]byte, error) { 220 | return bindataRead( 221 | _wski18nResourcesKo_krAllJson, 222 | "wski18n/resources/ko_KR.all.json", 223 | ) 224 | } 225 | 226 | func wski18nResourcesKo_krAllJson() (*asset, error) { 227 | bytes, err := wski18nResourcesKo_krAllJsonBytes() 228 | if err != nil { 229 | return nil, err 230 | } 231 | 232 | info := bindataFileInfo{name: "wski18n/resources/ko_KR.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510603210, 0)} 233 | a := &asset{bytes: bytes, info: info} 234 | return a, nil 235 | } 236 | 237 | var _wski18nResourcesPt_brAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00") 238 | 239 | func wski18nResourcesPt_brAllJsonBytes() ([]byte, error) { 240 | return bindataRead( 241 | _wski18nResourcesPt_brAllJson, 242 | "wski18n/resources/pt_BR.all.json", 243 | ) 244 | } 245 | 246 | func wski18nResourcesPt_brAllJson() (*asset, error) { 247 | bytes, err := wski18nResourcesPt_brAllJsonBytes() 248 | if err != nil { 249 | return nil, err 250 | } 251 | 252 | info := bindataFileInfo{name: "wski18n/resources/pt_BR.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510603210, 0)} 253 | a := &asset{bytes: bytes, info: info} 254 | return a, nil 255 | } 256 | 257 | var _wski18nResourcesZh_hansAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00") 258 | 259 | func wski18nResourcesZh_hansAllJsonBytes() ([]byte, error) { 260 | return bindataRead( 261 | _wski18nResourcesZh_hansAllJson, 262 | "wski18n/resources/zh_Hans.all.json", 263 | ) 264 | } 265 | 266 | func wski18nResourcesZh_hansAllJson() (*asset, error) { 267 | bytes, err := wski18nResourcesZh_hansAllJsonBytes() 268 | if err != nil { 269 | return nil, err 270 | } 271 | 272 | info := bindataFileInfo{name: "wski18n/resources/zh_Hans.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510603210, 0)} 273 | a := &asset{bytes: bytes, info: info} 274 | return a, nil 275 | } 276 | 277 | var _wski18nResourcesZh_hantAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00") 278 | 279 | func wski18nResourcesZh_hantAllJsonBytes() ([]byte, error) { 280 | return bindataRead( 281 | _wski18nResourcesZh_hantAllJson, 282 | "wski18n/resources/zh_Hant.all.json", 283 | ) 284 | } 285 | 286 | func wski18nResourcesZh_hantAllJson() (*asset, error) { 287 | bytes, err := wski18nResourcesZh_hantAllJsonBytes() 288 | if err != nil { 289 | return nil, err 290 | } 291 | 292 | info := bindataFileInfo{name: "wski18n/resources/zh_Hant.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510603210, 0)} 293 | a := &asset{bytes: bytes, info: info} 294 | return a, nil 295 | } 296 | 297 | // Asset loads and returns the asset for the given name. 298 | // It returns an error if the asset could not be found or 299 | // could not be loaded. 300 | func Asset(name string) ([]byte, error) { 301 | canonicalName := strings.Replace(name, "\\", "/", -1) 302 | if f, ok := _bindata[canonicalName]; ok { 303 | a, err := f() 304 | if err != nil { 305 | return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) 306 | } 307 | return a.bytes, nil 308 | } 309 | return nil, fmt.Errorf("Asset %s not found", name) 310 | } 311 | 312 | // MustAsset is like Asset but panics when Asset would return an error. 313 | // It simplifies safe initialization of global variables. 314 | func MustAsset(name string) []byte { 315 | a, err := Asset(name) 316 | if err != nil { 317 | panic("asset: Asset(" + name + "): " + err.Error()) 318 | } 319 | 320 | return a 321 | } 322 | 323 | // AssetInfo loads and returns the asset info for the given name. 324 | // It returns an error if the asset could not be found or 325 | // could not be loaded. 326 | func AssetInfo(name string) (os.FileInfo, error) { 327 | canonicalName := strings.Replace(name, "\\", "/", -1) 328 | if f, ok := _bindata[canonicalName]; ok { 329 | a, err := f() 330 | if err != nil { 331 | return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) 332 | } 333 | return a.info, nil 334 | } 335 | return nil, fmt.Errorf("AssetInfo %s not found", name) 336 | } 337 | 338 | // AssetNames returns the names of the assets. 339 | func AssetNames() []string { 340 | names := make([]string, 0, len(_bindata)) 341 | for name := range _bindata { 342 | names = append(names, name) 343 | } 344 | return names 345 | } 346 | 347 | // _bindata is a table, holding each asset generator, mapped to its name. 348 | var _bindata = map[string]func() (*asset, error){ 349 | "wski18n/resources/de_DE.all.json": wski18nResourcesDe_deAllJson, 350 | "wski18n/resources/en_US.all.json": wski18nResourcesEn_usAllJson, 351 | "wski18n/resources/es_ES.all.json": wski18nResourcesEs_esAllJson, 352 | "wski18n/resources/fr_FR.all.json": wski18nResourcesFr_frAllJson, 353 | "wski18n/resources/it_IT.all.json": wski18nResourcesIt_itAllJson, 354 | "wski18n/resources/ja_JA.all.json": wski18nResourcesJa_jaAllJson, 355 | "wski18n/resources/ko_KR.all.json": wski18nResourcesKo_krAllJson, 356 | "wski18n/resources/pt_BR.all.json": wski18nResourcesPt_brAllJson, 357 | "wski18n/resources/zh_Hans.all.json": wski18nResourcesZh_hansAllJson, 358 | "wski18n/resources/zh_Hant.all.json": wski18nResourcesZh_hantAllJson, 359 | } 360 | 361 | // AssetDir returns the file names below a certain 362 | // directory embedded in the file by go-bindata. 363 | // For example if you run go-bindata on data/... and data contains the 364 | // following hierarchy: 365 | // data/ 366 | // foo.txt 367 | // img/ 368 | // a.png 369 | // b.png 370 | // then AssetDir("data") would return []string{"foo.txt", "img"} 371 | // AssetDir("data/img") would return []string{"a.png", "b.png"} 372 | // AssetDir("foo.txt") and AssetDir("notexist") would return an error 373 | // AssetDir("") will return []string{"data"}. 374 | func AssetDir(name string) ([]string, error) { 375 | node := _bintree 376 | if len(name) != 0 { 377 | canonicalName := strings.Replace(name, "\\", "/", -1) 378 | pathList := strings.Split(canonicalName, "/") 379 | for _, p := range pathList { 380 | node = node.Children[p] 381 | if node == nil { 382 | return nil, fmt.Errorf("Asset %s not found", name) 383 | } 384 | } 385 | } 386 | if node.Func != nil { 387 | return nil, fmt.Errorf("Asset %s not found", name) 388 | } 389 | rv := make([]string, 0, len(node.Children)) 390 | for childName := range node.Children { 391 | rv = append(rv, childName) 392 | } 393 | return rv, nil 394 | } 395 | 396 | type bintree struct { 397 | Func func() (*asset, error) 398 | Children map[string]*bintree 399 | } 400 | 401 | var _bintree = &bintree{nil, map[string]*bintree{ 402 | "wski18n": {nil, map[string]*bintree{ 403 | "resources": {nil, map[string]*bintree{ 404 | "de_DE.all.json": {wski18nResourcesDe_deAllJson, map[string]*bintree{}}, 405 | "en_US.all.json": {wski18nResourcesEn_usAllJson, map[string]*bintree{}}, 406 | "es_ES.all.json": {wski18nResourcesEs_esAllJson, map[string]*bintree{}}, 407 | "fr_FR.all.json": {wski18nResourcesFr_frAllJson, map[string]*bintree{}}, 408 | "it_IT.all.json": {wski18nResourcesIt_itAllJson, map[string]*bintree{}}, 409 | "ja_JA.all.json": {wski18nResourcesJa_jaAllJson, map[string]*bintree{}}, 410 | "ko_KR.all.json": {wski18nResourcesKo_krAllJson, map[string]*bintree{}}, 411 | "pt_BR.all.json": {wski18nResourcesPt_brAllJson, map[string]*bintree{}}, 412 | "zh_Hans.all.json": {wski18nResourcesZh_hansAllJson, map[string]*bintree{}}, 413 | "zh_Hant.all.json": {wski18nResourcesZh_hantAllJson, map[string]*bintree{}}, 414 | }}, 415 | }}, 416 | }} 417 | 418 | // RestoreAsset restores an asset under the given directory 419 | func RestoreAsset(dir, name string) error { 420 | data, err := Asset(name) 421 | if err != nil { 422 | return err 423 | } 424 | info, err := AssetInfo(name) 425 | if err != nil { 426 | return err 427 | } 428 | err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) 429 | if err != nil { 430 | return err 431 | } 432 | err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) 433 | if err != nil { 434 | return err 435 | } 436 | err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) 437 | if err != nil { 438 | return err 439 | } 440 | return nil 441 | } 442 | 443 | // RestoreAssets restores an asset under the given directory recursively 444 | func RestoreAssets(dir, name string) error { 445 | children, err := AssetDir(name) 446 | // File 447 | if err != nil { 448 | return RestoreAsset(dir, name) 449 | } 450 | // Dir 451 | for _, child := range children { 452 | err = RestoreAssets(dir, filepath.Join(name, child)) 453 | if err != nil { 454 | return err 455 | } 456 | } 457 | return nil 458 | } 459 | 460 | func _filePath(dir, name string) string { 461 | canonicalName := strings.Replace(name, "\\", "/", -1) 462 | return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...) 463 | } 464 | -------------------------------------------------------------------------------- /wski18n/resources/de_DE.all.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/openwhisk-client-go/fa7fa7e488631cfbbcd187aace0adb8b4ca316cd/wski18n/resources/de_DE.all.json -------------------------------------------------------------------------------- /wski18n/resources/en_US.all.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "Unable to add route options '{{.options}}'", 4 | "translation": "Unable to add route options: {{.options}}" 5 | }, 6 | { 7 | "id": "Unable to create HTTP request for PUT '{{.route}}': {{.err}}", 8 | "translation": "Unable to create HTTP request for PUT '{{.route}}': {{.err}}" 9 | }, 10 | { 11 | "id": "Unable to create HTTP request for GET '{{.route}}': {{.err}}", 12 | "translation": "Unable to create HTTP request for GET '{{.route}}': {{.err}}" 13 | }, 14 | { 15 | "id": "Unable to create HTTP request for DELETE '{{.route}}': {{.err}}", 16 | "translation": "Unable to create HTTP request for DELETE '{{.route}}': {{.err}}" 17 | }, 18 | { 19 | "id": "Unable to create HTTP request for POST '{{.route}}': {{.err}}", 20 | "translation": "Unable to create HTTP request for POST '{{.route}}': {{.err}}" 21 | }, 22 | { 23 | "id": "Unable to append options '{{.options}}' to URL route '{{.route}}': {{.err}}", 24 | "translation": "Unable to append options '{{.options}}' to URL route '{{.route}}': {{.err}}" 25 | }, 26 | { 27 | "id": "Unable to create request URL '{{.url}}': {{.err}}", 28 | "translation": "Unable to create request URL '{{.url}}': {{.err}}" 29 | }, 30 | { 31 | "id": "Invalid request URL '{{.url}}': {{.err}}", 32 | "translation": "Invalid request URL '{{.url}}': {{.err}}" 33 | }, 34 | { 35 | "id": "Error encoding request body: {{.err}}", 36 | "translation": "Error encoding request body: {{.err}}" 37 | }, 38 | { 39 | "id": "Error initializing request: {{.err}}", 40 | "translation": "Error initializing request: {{.err}}" 41 | }, 42 | { 43 | "id": "Unable to add the HTTP authentication header: {{.err}}", 44 | "translation": "Unable to add the HTTP authentication header: {{.err}}" 45 | }, 46 | { 47 | "id": "Authorization key is not configured (--auth is required)", 48 | "translation": "Authorization key is not configured (--auth is required)" 49 | }, 50 | { 51 | "id": "An API host must be provided.\n", 52 | "translation": "An API host must be provided.\n" 53 | }, 54 | { 55 | "id": "Unable to add route options '{{.options}}'", 56 | "translation": "Unable to add route options: {{.options}}" 57 | }, 58 | { 59 | "id": "Command failed due to an HTTP failure", 60 | "translation": "Command failed due to an HTTP failure" 61 | }, 62 | { 63 | "id": "No interface provided; no HTTP response body expected", 64 | "translation": "No interface provided; no HTTP response body expected" 65 | }, 66 | { 67 | "id": "Command failed due to an internal failure", 68 | "translation": "Command failed due to an internal failure" 69 | }, 70 | { 71 | "id": "The following application error was received: {{.err}}", 72 | "translation": "The following application error was received: {{.err}}" 73 | }, 74 | { 75 | "id": "Invalid request URL '{{.url}}': {{.err}}", 76 | "translation": "Invalid request URL '{{.url}}': {{.err}}" 77 | }, 78 | { 79 | "id": "Unable to add the HTTP authentication header: {{.err}}", 80 | "translation": "Unable to add the HTTP authentication header: {{.err}}" 81 | }, 82 | { 83 | "id": "Unable to URL parse '{{.version}}': {{.err}}", 84 | "translation": "Unable to URL parse '{{.version}}': {{.err}}" 85 | }, 86 | { 87 | "id": "Unable to create HTTP request for GET '{{.url}}': {{.err}}", 88 | "translation": "Unable to create HTTP request for GET '{{.url}}': {{.err}}" 89 | }, 90 | { 91 | "id": "Unable to create HTTP request for GET: {{.err}}", 92 | "translation": "Unable to create HTTP request for GET: {{.err}}" 93 | }, 94 | { 95 | "id": "Unable to build request URL: {{.err}}", 96 | "translation": "Unable to build request URL: {{.err}}" 97 | }, 98 | { 99 | "id": "Unable to create GET HTTP request for '{{.route}}': {{.err}}", 100 | "translation": "Unable to create GET HTTP request for '{{.route}}': {{.err}}" 101 | }, 102 | { 103 | "id": "Unable to create PUT HTTP request for '{{.route}}': {{.err}}", 104 | "translation": "Unable to create PUT HTTP request for '{{.route}}': {{.err}}" 105 | }, 106 | { 107 | "id": "Unable to create DELETE HTTP request for '{{.route}}': {{.err}}", 108 | "translation": "Unable to create DELETE HTTP request for '{{.route}}': {{.err}}" 109 | }, 110 | { 111 | "id": "Unable to create POST HTTP request for '{{.route}}': {{.err}}", 112 | "translation": "Unable to create POST HTTP request for '{{.route}}': {{.err}}" 113 | }, 114 | { 115 | "id": "Internal error. Invalid state option '{{.state}}'. Valid options are \"active\" and \"inactive\".", 116 | "translation": "Internal error. Invalid state option '{{.state}}'. Valid options are \"active\" and \"inactive\"." 117 | }, 118 | { 119 | "id": "Unable to parse URL '{{.route}}': {{.err}}", 120 | "translation": "Unable to parse URL '{{.route}}': {{.err}}" 121 | }, 122 | { 123 | "id": "Unable to process URL query options '{{.options}}': {{.err}}", 124 | "translation": "Unable to process URL query options '{{.options}}': {{.err}}" 125 | }, 126 | { 127 | "id": "{{.msg}} (code {{.code}})", 128 | "translation": "{{.msg}} (code {{.code}})" 129 | }, 130 | { 131 | "id": "The connection failed, or timed out. (HTTP status code {{.code}})", 132 | "translation": "The connection failed, or timed out. (HTTP status code {{.code}})" 133 | }, 134 | { 135 | "id": "Unable to create request URL, because OpenWhisk API host is missing", 136 | "translation": "Unable to create request URL, because OpenWhisk API host is missing" 137 | }, 138 | { 139 | "id": "Unable to create request URL, because the api host '{{.host}}' is invalid: {{.err}}", 140 | "translation": "Unable to create request URL, because the api host '{{.host}}' is invalid: {{.err}}" 141 | }, 142 | { 143 | "id": "OpenWhisk API host is missing (Please configure WHISK_APIHOST in .wskprops under the system HOME directory.)", 144 | "translation": "OpenWhisk API host is missing (Please configure WHISK_APIHOST in .wskprops under the system HOME directory.)" 145 | }, 146 | { 147 | "id": "OpenWhisk API host is missing (Please configure whisk.api.host.proto, whisk.api.host.name and whisk.api.host.port in whisk.properties under the OPENWHISK_HOME directory.)", 148 | "translation": "OpenWhisk API host is missing (Please configure whisk.api.host.proto, whisk.api.host.name and whisk.api.host.port in whisk.properties under the OPENWHISK_HOME directory.)" 149 | }, 150 | { 151 | "id": "Authentication key is missing (Please configure AUTH in .wskprops under the system HOME directory.)", 152 | "translation": "Authentication key is missing (Please configure AUTH in .wskprops under the system HOME directory.)" 153 | }, 154 | { 155 | "id": "Authentication key is missing (Please configure testing.auth as the path of the authentication key file in whisk.properties under the OPENWHISK_HOME directory.)", 156 | "translation": "Authentication key is missing (Please configure testing.auth as the path of the authentication key file in whisk.properties under the OPENWHISK_HOME directory.)" 157 | }, 158 | { 159 | "id": "Unable to load the X509 key pair due to the following reason: {{.err}}", 160 | "translation": "Unable to load the X509 key pair due to the following reason: {{.err}}" 161 | }, 162 | { 163 | "id": "The Cert file is not configured. Please configure the missing Cert file.\n", 164 | "translation": "The Cert file is not configured. Please configure the missing Cert file.\n" 165 | }, 166 | { 167 | "id": "The Key file is not configured. Please configure the missing Key file.\n", 168 | "translation": "The Key file is not configured. Please configure the missing Key file.\n" 169 | } 170 | ] 171 | -------------------------------------------------------------------------------- /wski18n/resources/es_ES.all.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/openwhisk-client-go/fa7fa7e488631cfbbcd187aace0adb8b4ca316cd/wski18n/resources/es_ES.all.json -------------------------------------------------------------------------------- /wski18n/resources/fr_FR.all.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/openwhisk-client-go/fa7fa7e488631cfbbcd187aace0adb8b4ca316cd/wski18n/resources/fr_FR.all.json -------------------------------------------------------------------------------- /wski18n/resources/it_IT.all.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/openwhisk-client-go/fa7fa7e488631cfbbcd187aace0adb8b4ca316cd/wski18n/resources/it_IT.all.json -------------------------------------------------------------------------------- /wski18n/resources/ja_JA.all.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/openwhisk-client-go/fa7fa7e488631cfbbcd187aace0adb8b4ca316cd/wski18n/resources/ja_JA.all.json -------------------------------------------------------------------------------- /wski18n/resources/ko_KR.all.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/openwhisk-client-go/fa7fa7e488631cfbbcd187aace0adb8b4ca316cd/wski18n/resources/ko_KR.all.json -------------------------------------------------------------------------------- /wski18n/resources/pt_BR.all.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/openwhisk-client-go/fa7fa7e488631cfbbcd187aace0adb8b4ca316cd/wski18n/resources/pt_BR.all.json -------------------------------------------------------------------------------- /wski18n/resources/zh_Hans.all.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/openwhisk-client-go/fa7fa7e488631cfbbcd187aace0adb8b4ca316cd/wski18n/resources/zh_Hans.all.json -------------------------------------------------------------------------------- /wski18n/resources/zh_Hant.all.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/openwhisk-client-go/fa7fa7e488631cfbbcd187aace0adb8b4ca316cd/wski18n/resources/zh_Hant.all.json --------------------------------------------------------------------------------