Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Factory:zSystems
rancher-cli
cli-2.8.3.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File cli-2.8.3.obscpio of Package rancher-cli
07070100000000000081A400000000000000000000000165D8C5D30000002E000000000000000000000000000000000000001800000000cli-2.8.3/.dockerignore./bin ./build ./.dapper ./dist ./.trash-cache 07070100000001000081A400000000000000000000000165D8C5D300001593000000000000000000000000000000000000001500000000cli-2.8.3/.drone.yml--- kind: pipeline name: default platform: os: linux arch: amd64 steps: - name: build pull: default image: rancher/dapper:v0.6.0 commands: - dapper ci privileged: true volumes: - name: socket path: /var/run/docker.sock - name: build-all-binaries pull: default image: rancher/dapper:v0.6.0 commands: - dapper ci - ls -lR dist/artifacts environment: CROSS: 1 privileged: true volumes: - name: socket path: /var/run/docker.sock when: event: - tag ref: include: - "refs/heads/master" - "refs/heads/v1.6" - "refs/tags/v*" - name: stage-binaries pull: default image: rancher/dapper:v0.6.0 commands: - "cp -r ./bin/* ./package/" when: event: - push - tag ref: include: - "refs/heads/master" - "refs/heads/v1.6" - "refs/tags/v*" - name: docker-publish-20-release pull: default image: plugins/docker settings: password: from_secret: docker_password username: from_secret: docker_username build_args: - "VERSION=${DRONE_TAG}" context: package/ dockerfile: package/Dockerfile repo: rancher/cli2 tag: - latest when: event: - tag ref: exclude: - "refs/tags/*rc*" include: - "refs/heads/master" - "refs/tags/v*" - name: docker-publish-20-rc pull: default image: plugins/docker settings: password: from_secret: docker_password username: from_secret: docker_username build_args: - "VERSION=${DRONE_TAG}" context: package/ dockerfile: package/Dockerfile repo: rancher/cli2 tag: "${DRONE_TAG}" when: event: - tag ref: include: - "refs/heads/master" - "refs/tags/v*" - name: docker-publish-16-release pull: default image: plugins/docker settings: password: from_secret: docker_password username: from_secret: docker_username build_args: - "VERSION=${DRONE_TAG}" context: package/ dockerfile: package/Dockerfile repo: rancher/cli tag: - latest when: event: - tag ref: exclude: - "refs/tags/*rc*" include: - "refs/heads/v1.6" - "refs/tags/v1.6*" - name: docker-publish-16-rc pull: default image: plugins/docker settings: password: from_secret: docker_password username: from_secret: docker_username build_args: - "VERSION=${DRONE_TAG}" context: package/ dockerfile: package/Dockerfile repo: rancher/cli tag: - "${DRONE_TAG}" when: event: - tag ref: include: - "refs/heads/v1.6" - "refs/tags/v1.6*" - name: binary-publish-release-2 pull: default image: plugins/gcs settings: token: from_secret: google_auth_key acl: - allUsers:READER cache_control: "public,max-age=3600" source: "dist/artifacts/${DRONE_TAG}" target: "releases.rancher.com/cli2/${DRONE_TAG}" when: event: - tag ref: include: - "refs/heads/master" - "refs/tags/v*" - name: binary-publish-latest-2 pull: default image: plugins/gcs settings: token: from_secret: google_auth_key acl: - allUsers:READER cache_control: "public,max-age=3600" source: dist/artifacts/latest target: releases.rancher.com/cli2/latest when: event: - tag ref: exclude: - "refs/tags/*rc*" include: - "refs/heads/master" - "refs/tags/v*" - name: binary-publish-release-16 pull: default image: plugins/gcs settings: token: from_secret: google_auth_key acl: - allUsers:READER cache_control: "public,max-age=3600" source: "dist/artifacts/${DRONE_TAG}" target: "releases.rancher.com/cli/${DRONE_TAG}" when: event: - tag ref: include: - "refs/heads/v1.6" - "refs/tags/v1.6*" - name: binary-publish-latest-16 pull: default image: plugins/gcs settings: token: from_secret: google_auth_key acl: - allUsers:READER cache_control: "public,max-age=3600" source: dist/artifacts/latest target: releases.rancher.com/cli/latest when: event: - tag ref: exclude: - "refs/tags/*rc*" include: - "refs/heads/v1.6" - "refs/tags/v1.6*" - name: github-binary-publish-release pull: default image: plugins/github-release settings: api_key: from_secret: github_token checksum: - sha256 checksum_flatten: true files: - "dist/artifacts/${DRONE_TAG}/rancher*" when: event: - tag ref: exclude: - "refs/tags/*rc*" include: - "refs/heads/master" - "refs/heads/v1.6" - "refs/tags/v*" - name: github-binary-publish-prerelease pull: default image: plugins/github-release settings: api_key: from_secret: github_token checksum: - sha256 checksum_flatten: true files: - "dist/artifacts/${DRONE_TAG}/rancher*" prerelease: true when: event: - tag ref: - "refs/tags/*rc*" volumes: - name: socket host: path: /var/run/docker.sock --- kind: pipeline name: fossa steps: - name: fossa image: rancher/drone-fossa:latest failure: ignore settings: api_key: from_secret: FOSSA_API_KEY when: instance: - drone-publish.rancher.io ref: include: - "refs/heads/*" - "refs/tags/v*" - "refs/pull/*" event: - push - tag 07070100000002000081A400000000000000000000000165D8C5D300000046000000000000000000000000000000000000001500000000cli-2.8.3/.gitignore/.dapper /bin /build /dist *.swp /.trash-cache /.idea trash.lock /cli 07070100000003000081A400000000000000000000000165D8C5D3000000C5000000000000000000000000000000000000001900000000cli-2.8.3/.golangci.json{ "linters": { "disable-all": true, "enable": [ "govet", "golint", "goimports" ] }, "run": { "timeout": "10m" } }07070100000004000081A400000000000000000000000165D8C5D3000001E1000000000000000000000000000000000000001C00000000cli-2.8.3/Dockerfile.dapperFROM registry.suse.com/bci/golang:1.20 RUN zypper -n install docker rsync xz zip ENV GOLANGCI_LINT v1.53.3 RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin "$GOLANGCI_LINT" ENV DAPPER_SOURCE /go/src/github.com/rancher/cli ENV DAPPER_OUTPUT bin build/bin dist ENV DAPPER_DOCKER_SOCKET true ENV DAPPER_ENV TAG REPO GOOS CROSS DRONE_TAG WORKDIR ${DAPPER_SOURCE} ENTRYPOINT ["./scripts/entry"] CMD ["ci"] 07070100000005000081A400000000000000000000000165D8C5D3000027BE000000000000000000000000000000000000001200000000cli-2.8.3/LICENSE Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS 07070100000006000081A400000000000000000000000165D8C5D300000134000000000000000000000000000000000000001300000000cli-2.8.3/MakefileTARGETS := $(shell ls scripts) .dapper: @echo Downloading dapper @curl -sL https://releases.rancher.com/dapper/latest/dapper-`uname -s`-`uname -m` > .dapper.tmp @@chmod +x .dapper.tmp @./.dapper.tmp -v @mv .dapper.tmp .dapper $(TARGETS): .dapper ./.dapper $@ .DEFAULT_GOAL := ci .PHONY: $(TARGETS) 07070100000007000081A400000000000000000000000165D8C5D300000A2F000000000000000000000000000000000000001400000000cli-2.8.3/README.mdRancher CLI =========== The Rancher Command Line Interface (CLI) is a unified tool for interacting with your Rancher Server. For usage information see: https://rancher.com/docs/rancher/v2.x/en/cli/ > **Note:** This is for version 2.x.x of the cli, for info on 1.6.x see [here](https://github.com/rancher/cli/tree/v1.6) ## Installing Check the [releases page](https://github.com/rancher/cli/releases) for direct downloads of the binary. After you download it, you can add it to your `$PATH` or [build your own from source](#building-from-source). ## Setting up Rancher CLI with a Rancher Server The CLI requires your Rancher Server address, along with [credentials for authentication](https://rancher.com/docs/rancher/v2.x/en/user-settings/api-keys/). Rancher CLI pulls this information from a JSON file, `cli2.json`, which is created the first time you run `rancher login`. By default, the path of this file is `~/.rancher/cli2.json`. ``` $ rancher login https://<RANCHER_SERVER_URL> -t my-secret-token ``` > **Note:** When entering your `<RANCHER_SERVER_URL>`, include the port that was exposed while you installed Rancher Server. ## Usage Run `rancher --help` for a list of available commands. ## Building from Source The binaries will be located in `/bin`. ### Linux Binary Run `make`. ### Mac Binary Run `CROSS=1 make build`. ## Docker Image Run `docker run --rm -it -v <PATH_TO_CONFIG>:/root/.rancher/cli2.json rancher/cli2 [ARGS]`. Pass credentials by replacing `<PATH_TO_CONFIG>` with your config file for the server. To build `rancher/cli`, run `make`. To use a custom Docker repository, do `REPO=custom make`, which produces a `custom/cli` image. ## Contact For bugs, questions, comments, corrections, suggestions, etc., open an issue in [rancher/rancher](//github.com/rancher/rancher/issues) with a title prefix of `[cli] `. Or just [click here](//github.com/rancher/rancher/issues/new?title=%5Bcli%5D%20) to create a new issue. ## License Copyright (c) 2014-2019 [Rancher Labs, Inc.](http://rancher.com) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 07070100000008000041ED00000000000000000000000265D8C5D300000000000000000000000000000000000000000000001400000000cli-2.8.3/cliclient07070100000009000081A400000000000000000000000165D8C5D30000151C000000000000000000000000000000000000002100000000cli-2.8.3/cliclient/cliclient.gopackage cliclient import ( "errors" "fmt" "strings" errorsPkg "github.com/pkg/errors" "github.com/rancher/cli/config" "github.com/rancher/norman/clientbase" ntypes "github.com/rancher/norman/types" capiClient "github.com/rancher/rancher/pkg/client/generated/cluster/v1alpha4" clusterClient "github.com/rancher/rancher/pkg/client/generated/cluster/v3" managementClient "github.com/rancher/rancher/pkg/client/generated/management/v3" projectClient "github.com/rancher/rancher/pkg/client/generated/project/v3" "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" ) type MasterClient struct { ClusterClient *clusterClient.Client ManagementClient *managementClient.Client ProjectClient *projectClient.Client UserConfig *config.ServerConfig CAPIClient *capiClient.Client } // NewMasterClient returns a new MasterClient with Cluster, Management and Project // clients populated func NewMasterClient(config *config.ServerConfig) (*MasterClient, error) { mc := &MasterClient{ UserConfig: config, } clustProj := CheckProject(mc.UserConfig.Project) if clustProj == nil { logrus.Warn("No context set; some commands will not work. Run `rancher login` again.") } var g errgroup.Group g.Go(mc.newManagementClient) g.Go(mc.newClusterClient) g.Go(mc.newProjectClient) g.Go(mc.newCAPIClient) if err := g.Wait(); err != nil { return nil, err } return mc, nil } // NewManagementClient returns a new MasterClient with only the Management client func NewManagementClient(config *config.ServerConfig) (*MasterClient, error) { mc := &MasterClient{ UserConfig: config, } err := mc.newManagementClient() if err != nil { return nil, err } return mc, nil } // NewClusterClient returns a new MasterClient with only the Cluster client func NewClusterClient(config *config.ServerConfig) (*MasterClient, error) { clustProj := CheckProject(config.Project) if clustProj == nil { return nil, errors.New("no context set") } mc := &MasterClient{ UserConfig: config, } err := mc.newClusterClient() if err != nil { return nil, err } return mc, nil } // NewProjectClient returns a new MasterClient with only the Project client func NewProjectClient(config *config.ServerConfig) (*MasterClient, error) { clustProj := CheckProject(config.Project) if clustProj == nil { return nil, errors.New("no context set") } mc := &MasterClient{ UserConfig: config, } err := mc.newProjectClient() if err != nil { return nil, err } return mc, nil } func (mc *MasterClient) newManagementClient() error { options := createClientOpts(mc.UserConfig) // Setup the management client mClient, err := managementClient.NewClient(options) if err != nil { return err } mc.ManagementClient = mClient return nil } func (mc *MasterClient) newClusterClient() error { options := createClientOpts(mc.UserConfig) options.URL = options.URL + "/clusters/" + mc.UserConfig.FocusedCluster() // Setup the project client cc, err := clusterClient.NewClient(options) if err != nil { if clientbase.IsNotFound(err) { err = errorsPkg.WithMessage(err, "Current cluster not available, try running `rancher context switch`. Error") } return err } mc.ClusterClient = cc return nil } func (mc *MasterClient) newProjectClient() error { options := createClientOpts(mc.UserConfig) options.URL = options.URL + "/projects/" + mc.UserConfig.Project // Setup the project client pc, err := projectClient.NewClient(options) if err != nil { if clientbase.IsNotFound(err) { err = errorsPkg.WithMessage(err, "Current project not available, try running `rancher context switch`. Error") } return err } mc.ProjectClient = pc return nil } func (mc *MasterClient) newCAPIClient() error { options := createClientOpts(mc.UserConfig) options.URL = strings.TrimSuffix(options.URL, "/v3") + "/v1" // Setup the CAPI client cc, err := capiClient.NewClient(options) if err != nil { return err } mc.CAPIClient = cc return nil } func (mc *MasterClient) ByID(resource *ntypes.Resource, respObject interface{}) error { if strings.HasPrefix(resource.Type, "cluster.x-k8s.io") { return mc.CAPIClient.ByID(resource.Type, resource.ID, &respObject) } else if _, ok := mc.ManagementClient.APIBaseClient.Types[resource.Type]; ok { return mc.ManagementClient.ByID(resource.Type, resource.ID, &respObject) } else if _, ok := mc.ProjectClient.APIBaseClient.Types[resource.Type]; ok { return mc.ProjectClient.ByID(resource.Type, resource.ID, &respObject) } else if _, ok := mc.ClusterClient.APIBaseClient.Types[resource.Type]; ok { return mc.ClusterClient.ByID(resource.Type, resource.ID, &respObject) } return fmt.Errorf("MasterClient - unknown resource type %v", resource.Type) } func createClientOpts(config *config.ServerConfig) *clientbase.ClientOpts { serverURL := config.URL if !strings.HasSuffix(serverURL, "/v3") { serverURL = config.URL + "/v3" } options := &clientbase.ClientOpts{ URL: serverURL, AccessKey: config.AccessKey, SecretKey: config.SecretKey, CACerts: config.CACerts, } return options } func SplitOnColon(s string) []string { return strings.Split(s, ":") } // CheckProject verifies s matches the valid project ID of <cluster>:<project> func CheckProject(s string) []string { clustProj := SplitOnColon(s) if len(s) == 0 || len(clustProj) != 2 { return nil } return clustProj } 0707010000000A000041ED00000000000000000000000265D8C5D300000000000000000000000000000000000000000000000E00000000cli-2.8.3/cmd0707010000000B000081A400000000000000000000000165D8C5D30000937D000000000000000000000000000000000000001500000000cli-2.8.3/cmd/app.gopackage cmd import ( "bufio" "encoding/base64" "encoding/json" "fmt" "io/ioutil" "net/http" "net/url" "os" "path/filepath" "sort" "strings" "time" gover "github.com/hashicorp/go-version" "github.com/pkg/errors" "github.com/rancher/cli/cliclient" "github.com/rancher/norman/clientbase" clusterClient "github.com/rancher/rancher/pkg/client/generated/cluster/v3" managementClient "github.com/rancher/rancher/pkg/client/generated/management/v3" projectClient "github.com/rancher/rancher/pkg/client/generated/project/v3" "github.com/sirupsen/logrus" "github.com/urfave/cli" "gopkg.in/yaml.v2" ) const ( installAppDescription = ` Install an app template in the current Rancher server. This defaults to the newest version of the app template. Specify a version using '--version' if required. The app will be installed into a new namespace unless '--namespace' is specified. Example: # Install the redis template without any options $ rancher app install redis appFoo # Block cli until installation has finished or encountered an error. Use after app install. $ rancher wait <app-id> # Install the local redis template folder without any options $ rancher app install ./redis appFoo # Install the redis template and specify an answers file location $ rancher app install --answers /example/answers.yaml redis appFoo # Install the redis template and set multiple answers and the version to install $ rancher app install --set foo=bar --set-string baz=bunk --version 1.0.1 redis appFoo # Install the redis template and specify the namespace for the app $ rancher app install --namespace bar redis appFoo ` upgradeAppDescription = ` Upgrade an existing app to a newer version via app template or app version in the current Rancher server. Example: # Upgrade the 'appFoo' app to latest version without any options $ rancher app upgrade appFoo latest # Upgrade the 'appFoo' app by local template folder without any options $ rancher app upgrade appFoo ./redis # Upgrade the 'appFoo' app and set multiple answers and the 0.2.0 version to install $ rancher app upgrade --set foo=bar --set-string baz=bunk appFoo 0.2.0 ` ) type AppData struct { ID string App projectClient.App Catalog string Template string Version string } type TemplateData struct { ID string Template managementClient.Template Category string } type VersionData struct { Current string Version string } type revision struct { Current string Name string Created time.Time Human string Catalog string Template string Version string } type chartVersion struct { chartMetadata `yaml:",inline"` Dir string `json:"-" yaml:"-"` URLs []string `json:"urls" yaml:"urls"` Digest string `json:"digest,omitempty" yaml:"digest,omitempty"` } type chartMetadata struct { Name string `json:"name,omitempty" yaml:"name,omitempty"` Sources []string `json:"sources,omitempty" yaml:"sources,omitempty"` Version string `json:"version,omitempty" yaml:"version,omitempty"` KubeVersion string `json:"kubeVersion,omitempty" yaml:"kubeVersion,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"` Keywords []string `json:"keywords,omitempty" yaml:"keywords,omitempty"` Icon string `json:"icon,omitempty" yaml:"icon,omitempty"` } type revSlice []revision func (s revSlice) Less(i, j int) bool { return s[i].Created.After(s[j].Created) } func (s revSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s revSlice) Len() int { return len(s) } func AppCommand() cli.Command { appLsFlags := []cli.Flag{ formatFlag, cli.BoolFlag{ Name: "quiet,q", Usage: "Only display IDs", }, } return cli.Command{ Name: "apps", Aliases: []string{"app"}, Usage: "Operations with apps. Uses helm. Flags prepended with \"helm\" can also be accurately described by helm documentation.", Action: defaultAction(appLs), Flags: appLsFlags, Subcommands: []cli.Command{ cli.Command{ Name: "ls", Usage: "List apps", Description: "\nList all apps in the current Rancher server", ArgsUsage: "None", Action: appLs, Flags: appLsFlags, }, cli.Command{ Name: "delete", Usage: "Delete an app", Action: appDelete, ArgsUsage: "[APP_NAME/APP_ID]", }, cli.Command{ Name: "install", Usage: "Install an app template", Description: installAppDescription, Action: templateInstall, ArgsUsage: "[TEMPLATE_NAME/TEMPLATE_PATH, APP_NAME]", Flags: []cli.Flag{ cli.StringFlag{ Name: "answers,a", Usage: "Path to an answers file, the format of the file is a map with key:value. This supports JSON and YAML.", }, cli.StringFlag{ Name: "values", Usage: "Path to a helm values file.", }, cli.StringFlag{ Name: "namespace,n", Usage: "Namespace to install the app into", }, cli.StringSliceFlag{ Name: "set", Usage: "Set answers for the template, can be used multiple times. Example: --set foo=bar", }, cli.StringSliceFlag{ Name: "set-string", Usage: "Set string answers for the template (Skips Helm's type conversion), can be used multiple times. Example: --set-string foo=bar", }, cli.StringFlag{ Name: "version", Usage: "Version of the template to use", }, cli.BoolFlag{ Name: "no-prompt", Usage: "Suppress asking questions and use the default values when required answers are not provided", }, cli.IntFlag{ Name: "helm-timeout", Usage: "Amount of time for helm to wait for k8s commands (default is 300 secs). Example: --helm-timeout 600", Value: 300, }, cli.BoolFlag{ Name: "helm-wait", Usage: "Helm will wait for as long as timeout value, for installed resources to be ready (pods, PVCs, deployments, etc.). Example: --helm-wait", }, }, }, cli.Command{ Name: "rollback", Usage: "Rollback an app to a previous version", Action: appRollback, ArgsUsage: "[APP_NAME/APP_ID, REVISION_ID/REVISION_NAME]", Flags: []cli.Flag{ cli.BoolFlag{ Name: "show-revisions,r", Usage: "Show revisions available to rollback to", }, cli.BoolFlag{ Name: "force,f", Usage: "Force rollback, deletes and recreates resources if needed during rollback. (default is false)", }, }, }, cli.Command{ Name: "upgrade", Usage: "Upgrade an existing app to a newer version", Description: upgradeAppDescription, Action: appUpgrade, ArgsUsage: "[APP_NAME/APP_ID VERSION/TEMPLATE_PATH]", Flags: []cli.Flag{ cli.StringFlag{ Name: "answers,a", Usage: "Path to an answers file, the format of the file is a map with key:value. Supports JSON and YAML", }, cli.StringFlag{ Name: "values", Usage: "Path to a helm values file.", }, cli.StringSliceFlag{ Name: "set", Usage: "Set answers for the template, can be used multiple times. Example: --set foo=bar", }, cli.StringSliceFlag{ Name: "set-string", Usage: "Set string answers for the template (Skips Helm's type conversion), can be used multiple times. Example: --set-string foo=bar", }, cli.BoolFlag{ Name: "show-versions,v", Usage: "Display versions available to upgrade to", }, cli.BoolFlag{ Name: "reset", Usage: "Reset all catalog app answers", }, cli.BoolFlag{ Name: "force,f", Usage: "Force upgrade, deletes and recreates resources if needed during upgrade. (default is false)", }, }, }, cli.Command{ Name: "list-templates", Aliases: []string{"lt"}, Usage: "List templates available for installation", Description: "\nList all app templates in the current Rancher server", ArgsUsage: "None", Action: templateLs, Flags: []cli.Flag{ formatFlag, cli.StringFlag{ Name: "catalog", Usage: "Specify the catalog to list templates for", }, }, }, cli.Command{ Name: "show-template", Aliases: []string{"st"}, Usage: "Show versions available to install for an app template", Description: "\nShow all available versions of an app template", ArgsUsage: "[TEMPLATE_ID]", Action: templateShow, }, cli.Command{ Name: "show-app", Aliases: []string{"sa"}, Usage: "Show an app's available versions and revisions", ArgsUsage: "[APP_NAME/APP_ID]", Action: showApp, Flags: []cli.Flag{ formatFlag, }, }, cli.Command{ Name: "show-notes", Usage: "Show contents of apps notes.txt", Action: appNotes, ArgsUsage: "[APP_NAME/APP_ID]", }, }, } } func appLs(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } collection, err := c.ProjectClient.App.List(defaultListOpts(ctx)) if err != nil { return err } writer := NewTableWriter([][]string{ {"ID", "ID"}, {"NAME", "App.Name"}, {"STATE", "App.State"}, {"CATALOG", "Catalog"}, {"TEMPLATE", "Template"}, {"VERSION", "Version"}, }, ctx) defer writer.Close() for _, item := range collection.Data { appExternalID := item.ExternalID appTemplateFiles := make(map[string]string) if appExternalID == "" { // add namespace prefix to AppRevisionID to create a Rancher API style ID appRevisionID := strings.Replace(item.ID, item.Name, item.AppRevisionID, -1) appRevision, err := c.ProjectClient.AppRevision.ByID(appRevisionID) if err != nil { return err } if appRevision.Status != nil { appTemplateFiles = appRevision.Status.Files } } parsedInfo, err := parseTemplateInfo(appExternalID, appTemplateFiles) if err != nil { return err } appData := &AppData{ ID: item.ID, App: item, Catalog: parsedInfo["catalog"], Template: parsedInfo["template"], Version: parsedInfo["version"], } writer.Write(appData) } return writer.Err() } func parseTemplateInfo(appExternalID string, appTemplateFiles map[string]string) (map[string]string, error) { if appExternalID != "" { parsedExternal, parseErr := parseExternalID(appExternalID) if parseErr != nil { return nil, errors.Wrap(parseErr, "failed to parse ExternalID from app") } return parsedExternal, nil } for fileName, fileContent := range appTemplateFiles { if strings.HasSuffix(fileName, "/Chart.yaml") || strings.HasSuffix(fileName, "/Chart.yml") { content, decodeErr := base64.StdEncoding.DecodeString(fileContent) if decodeErr != nil { return nil, errors.Wrap(decodeErr, "failed to decode Chart.yaml from app") } version := &chartVersion{} unmarshalErr := yaml.Unmarshal(content, version) if unmarshalErr != nil { return nil, errors.Wrap(unmarshalErr, "failed to parse Chart.yaml from app") } return map[string]string{ "catalog": "local directory", "template": version.Name, "version": version.Version, }, nil } } return nil, errors.New("can't parse info from app") } func appDelete(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } for _, arg := range ctx.Args() { resource, err := Lookup(c, arg, "app") if err != nil { return err } app, err := c.ProjectClient.App.ByID(resource.ID) if err != nil { return err } err = c.ProjectClient.App.Delete(app) if err != nil { return err } } return nil } func appUpgrade(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } if ctx.Bool("show-versions") { return outputVersions(ctx, c) } if ctx.NArg() < 2 { return cli.ShowSubcommandHelp(ctx) } appName := ctx.Args().First() appVersionOrLocalTemplatePath := ctx.Args().Get(1) resource, err := Lookup(c, appName, "app") if err != nil { return err } app, err := c.ProjectClient.App.ByID(resource.ID) if err != nil { return err } answers := app.Answers answersSetString := app.AnswersSetString values := app.ValuesYaml answers, answersSetString, err = processAnswerUpdates(ctx, answers, answersSetString) if err != nil { return err } values, err = processValueUpgrades(ctx, values) if err != nil { return err } force := ctx.Bool("force") au := &projectClient.AppUpgradeConfig{ Answers: answers, AnswersSetString: answersSetString, ForceUpgrade: force, ValuesYaml: values, } if resolveTemplatePath(appVersionOrLocalTemplatePath) { // if it is a path, upgrade install charts locally localTemplatePath := appVersionOrLocalTemplatePath _, files, err := walkTemplateDirectory(localTemplatePath) if err != nil { return err } au.Files = files } else { appVersion := appVersionOrLocalTemplatePath externalID, err := updateExternalIDVersion(app.ExternalID, appVersion) if err != nil { return err } filter := defaultListOpts(ctx) filter.Filters["externalId"] = externalID template, err := c.ManagementClient.TemplateVersion.List(filter) if err != nil { return err } if len(template.Data) == 0 { return fmt.Errorf("version %s is not valid", appVersion) } au.ExternalID = template.Data[0].ExternalID } return c.ProjectClient.App.ActionUpgrade(app, au) } func updateExternalIDVersion(externalID string, version string) (string, error) { u, err := url.Parse(externalID) if err != nil { return "", err } oldVersionQuery := fmt.Sprintf("version=%s", u.Query().Get("version")) newVersionQuery := fmt.Sprintf("version=%s", version) return strings.Replace(externalID, oldVersionQuery, newVersionQuery, 1), nil } func appRollback(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } if ctx.Bool("show-revisions") { return outputRevisions(ctx, c) } if ctx.NArg() < 2 { return cli.ShowSubcommandHelp(ctx) } force := ctx.Bool("force") resource, err := Lookup(c, ctx.Args().First(), "app") if err != nil { return err } app, err := c.ProjectClient.App.ByID(resource.ID) if err != nil { return err } revisionResource, err := Lookup(c, ctx.Args().Get(1), "appRevision") if err != nil { return err } revision, err := c.ProjectClient.AppRevision.ByID(revisionResource.ID) if err != nil { return err } rr := &projectClient.RollbackRevision{ ForceUpgrade: force, RevisionID: revision.Name, } return c.ProjectClient.App.ActionRollback(app, rr) } func templateLs(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } filter := defaultListOpts(ctx) if ctx.String("app") != "" { resource, err := Lookup(c, ctx.String("app"), "app") if err != nil { return err } filter.Filters["appId"] = resource.ID } collection, err := c.ManagementClient.Template.List(filter) if err != nil { return err } writer := NewTableWriter([][]string{ {"ID", "ID"}, {"NAME", "Template.Name"}, {"CATEGORY", "Category"}, }, ctx) defer writer.Close() for _, item := range collection.Data { writer.Write(&TemplateData{ ID: item.ID, Template: item, Category: strings.Join(item.Categories, ","), }) } return writer.Err() } func templateShow(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } resource, err := Lookup(c, ctx.Args().First(), "template") if err != nil { return err } template, err := getFilteredTemplate(ctx, c, resource.ID) if err != nil { return err } sortedVersions, err := sortTemplateVersions(template) if err != nil { return err } if len(sortedVersions) == 0 { fmt.Println("No app versions available to install for this version of Rancher server") } for _, version := range sortedVersions { fmt.Println(version) } return nil } func templateInstall(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } templateName := ctx.Args().First() appName := ctx.Args().Get(1) c, err := GetClient(ctx) if err != nil { return err } app := &projectClient.App{ Name: appName, } if resolveTemplatePath(templateName) { // if it is a path, install charts locally chartName, files, err := walkTemplateDirectory(templateName) if err != nil { return err } answers, answersSetString, err := processAnswerInstall(ctx, nil, nil, nil, false, false) if err != nil { return err } values, err := processValueInstall(ctx, nil, "") if err != nil { return err } app.Files = files app.Answers = answers app.AnswersSetString = answersSetString app.ValuesYaml = values namespace := ctx.String("namespace") if namespace == "" { namespace = chartName + "-" + RandomLetters(5) } err = createNamespace(c, namespace) if err != nil { return err } app.TargetNamespace = namespace } else { resource, err := Lookup(c, templateName, "template") if err != nil { return err } template, err := getFilteredTemplate(ctx, c, resource.ID) if err != nil { return err } latestVersion, err := getTemplateLatestVersion(template) if err != nil { return err } templateVersionID := templateVersionIDFromVersionLink(template.VersionLinks[latestVersion]) userVersion := ctx.String("version") if userVersion != "" { if link, ok := template.VersionLinks[userVersion]; ok { templateVersionID = templateVersionIDFromVersionLink(link) } else { return fmt.Errorf( "version %s for template %s is invalid, run 'rancher app show-template %s' for a list of versions", userVersion, templateName, templateName, ) } } templateVersion, err := c.ManagementClient.TemplateVersion.ByID(templateVersionID) if err != nil { return err } interactive := !ctx.Bool("no-prompt") answers, answersSetString, err := processAnswerInstall(ctx, templateVersion, nil, nil, interactive, false) if err != nil { return err } values, err := processValueInstall(ctx, templateVersion, "") if err != nil { return err } namespace := ctx.String("namespace") if namespace == "" { namespace = template.Name + "-" + RandomLetters(5) } err = createNamespace(c, namespace) if err != nil { return err } app.Answers = answers app.AnswersSetString = answersSetString app.ValuesYaml = values app.ExternalID = templateVersion.ExternalID app.TargetNamespace = namespace } app.Wait = ctx.Bool("helm-wait") app.Timeout = ctx.Int64("helm-timeout") madeApp, err := c.ProjectClient.App.Create(app) if err != nil { return err } fmt.Printf("run \"app show-notes %s\" to view app notes once app is ready\n", madeApp.Name) return nil } // appNotes prints notes from app's notes.txt file func appNotes(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } if ctx.NArg() < 1 { return cli.ShowSubcommandHelp(ctx) } resource, err := Lookup(c, ctx.Args().First(), "app") if err != nil { return err } app, err := c.ProjectClient.App.ByID(resource.ID) if err != nil { return err } if len(app.Notes) > 0 { fmt.Println(app.Notes) } else { fmt.Println("no notes to print") } return nil } func resolveTemplatePath(templateName string) bool { return templateName == "." || strings.Contains(templateName, "\\\\") || strings.Contains(templateName, "/") } func walkTemplateDirectory(templatePath string) (string, map[string]string, error) { templateAbsPath, parsedErr := filepath.Abs(templatePath) if parsedErr != nil { return "", nil, parsedErr } if _, statErr := os.Stat(templateAbsPath); statErr != nil { return "", nil, statErr } var ( chartName string files = make(map[string]string) err error ) err = filepath.Walk(templateAbsPath, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } if !strings.EqualFold(info.Name(), "Chart.yaml") { return nil } version := &chartVersion{} content, err := ioutil.ReadFile(path) if err != nil { return err } rootDir := filepath.Dir(path) if err := yaml.Unmarshal(content, version); err != nil { return err } chartName = version.Name err = filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } content, err := ioutil.ReadFile(path) if err != nil { return err } if len(content) > 0 { key := filepath.Join(chartName, strings.TrimPrefix(path, rootDir+"/")) files[key] = base64.StdEncoding.EncodeToString(content) } return nil }) if err != nil { return err } return filepath.SkipDir }) return chartName, files, err } func showApp(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } err = outputRevisions(ctx, c) if err != nil { return err } fmt.Println() err = outputVersions(ctx, c) if err != nil { return err } return nil } func outputVersions(ctx *cli.Context, c *cliclient.MasterClient) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } resource, err := Lookup(c, ctx.Args().First(), "app") if err != nil { return err } app, err := c.ProjectClient.App.ByID(resource.ID) if err != nil { return err } externalID := app.ExternalID if externalID == "" { // local folder app doesn't show any version information return nil } externalInfo, err := parseExternalID(externalID) if err != nil { return err } template, err := getFilteredTemplate(ctx, c, "cattle-global-data:"+externalInfo["catalog"]+"-"+externalInfo["template"]) if err != nil { return err } sortedVersions, err := sortTemplateVersions(template) if err != nil { return err } if len(sortedVersions) == 0 { fmt.Println("No app versions available to install for this version of Rancher server") return nil } writer := NewTableWriter([][]string{ {"CURRENT", "Current"}, {"VERSION", "Version"}, }, ctx) defer writer.Close() for _, version := range sortedVersions { var current string if version.String() == externalInfo["version"] { current = "*" } writer.Write(&VersionData{ Current: current, Version: version.String(), }) } return writer.Err() } func outputRevisions(ctx *cli.Context, c *cliclient.MasterClient) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } resource, err := Lookup(c, ctx.Args().First(), "app") if err != nil { return err } app, err := c.ProjectClient.App.ByID(resource.ID) if err != nil { return err } revisions := &projectClient.AppRevisionCollection{} err = c.ProjectClient.GetLink(*resource, "revision", revisions) if err != nil { return err } var sorted revSlice for _, rev := range revisions.Data { parsedTime, err := time.Parse(time.RFC3339, rev.Created) if err != nil { return err } parsedInfo, err := parseTemplateInfo(rev.Status.ExternalID, rev.Status.Files) if err != nil { return err } reversionData := revision{ Name: rev.Name, Created: parsedTime, Catalog: parsedInfo["catalog"], Template: parsedInfo["template"], Version: parsedInfo["version"], } sorted = append(sorted, reversionData) } sort.Sort(sorted) writer := NewTableWriter([][]string{ {"CURRENT", "Current"}, {"REVISION", "Name"}, {"CATALOG", "Catalog"}, {"TEMPLATE", "Template"}, {"VERSION", "Version"}, {"CREATED", "Human"}, }, ctx) defer writer.Close() for _, rev := range sorted { if rev.Name == app.AppRevisionID { rev.Current = "*" } rev.Human = rev.Created.Format("02 Jan 2006 15:04:05 MST") writer.Write(rev) } return writer.Err() } func templateVersionIDFromVersionLink(s string) string { pieces := strings.Split(s, "/") return pieces[len(pieces)-1] } // parseExternalID gives back a map with the keys catalog, template and version func parseExternalID(e string) (map[string]string, error) { parsed := make(map[string]string) u, err := url.Parse(e) if err != nil { return parsed, err } q := u.Query() for key, value := range q { if len(value) > 0 { parsed[key] = value[0] } } return parsed, nil } // getFilteredTemplate uses the rancherVersion in the template request to get the // filtered template with incompatable versions dropped func getFilteredTemplate(ctx *cli.Context, c *cliclient.MasterClient, templateID string) (*managementClient.Template, error) { ver, err := getRancherServerVersion(c) if err != nil { return nil, err } filter := defaultListOpts(ctx) filter.Filters["id"] = templateID filter.Filters["rancherVersion"] = ver template, err := c.ManagementClient.Template.List(filter) if err != nil { return nil, err } if len(template.Data) == 0 { return nil, fmt.Errorf("template %v not found", templateID) } return &template.Data[0], nil } // getTemplateLatestVersion returns the newest version of the template func getTemplateLatestVersion(template *managementClient.Template) (string, error) { if len(template.VersionLinks) == 0 { return "", errors.New("no versions found for this template (the chart you are trying to install may be intentionally hidden or deprecated for your Rancher version)") } sorted, err := sortTemplateVersions(template) if err != nil { return "", err } return sorted[len(sorted)-1].String(), nil } func sortTemplateVersions(template *managementClient.Template) ([]*gover.Version, error) { var versions []*gover.Version for key := range template.VersionLinks { v, err := gover.NewVersion(key) if err != nil { return nil, err } versions = append(versions, v) } sort.Sort(gover.Collection(versions)) return versions, nil } // createNamespace checks if a namespace exists and creates it if needed func createNamespace(c *cliclient.MasterClient, n string) error { filter := defaultListOpts(nil) filter.Filters["name"] = n namespaces, err := c.ClusterClient.Namespace.List(filter) if err != nil { return err } if len(namespaces.Data) == 0 { newNamespace := &clusterClient.Namespace{ Name: n, ProjectID: c.UserConfig.Project, } ns, err := c.ClusterClient.Namespace.Create(newNamespace) if err != nil { return err } nsID := ns.ID startTime := time.Now() for { logrus.Debugf("Namespace create wait - Name: %s, State: %s, Transitioning: %s", ns.Name, ns.State, ns.Transitioning) if time.Since(startTime)/time.Second > 30 { return fmt.Errorf("timed out waiting for new namespace %s", ns.Name) } ns, err = c.ClusterClient.Namespace.ByID(nsID) if err != nil { if e, ok := err.(*clientbase.APIError); ok && e.StatusCode == http.StatusForbidden { //the new namespace is created successfully but cannot be got when RBAC rules are not ready. continue } return err } if ns.State == "active" { break } time.Sleep(500 * time.Millisecond) } } else { if namespaces.Data[0].ProjectID != c.UserConfig.Project { return fmt.Errorf("namespace %s already exists", n) } } return nil } // processValueInstall creates a map of the values file and fills in missing entries with defaults func processValueInstall(ctx *cli.Context, tv *managementClient.TemplateVersion, existingValues string) (string, error) { values, err := processValues(ctx, existingValues) if err != nil { return existingValues, err } // add default values if entries missing from map err = fillInDefaultAnswers(tv, values) if err != nil { return existingValues, err } // change map back into string to be consistent with ui existingValues, err = parseMapToYamlString(values) if err != nil { return existingValues, err } return existingValues, nil } // processValueUpgrades creates map from existing values and applies updates func processValueUpgrades(ctx *cli.Context, existingValues string) (string, error) { values, err := processValues(ctx, existingValues) if err != nil { return existingValues, err } // change map back into string to be consistent with ui existingValues, err = parseMapToYamlString(values) if err != nil { return existingValues, err } return existingValues, nil } // processValues creates a map of the values file func processValues(ctx *cli.Context, existingValues string) (map[string]interface{}, error) { var err error values := make(map[string]interface{}) if existingValues != "" { // parse values into map to ensure previous values are considered on update values, err = createValuesMap([]byte(existingValues)) if err != nil { return values, err } } if ctx.String("values") != "" { // if values file passed in, overwrite defaults with new key value pair values, err = parseFile(ctx.String("values")) if err != nil { return values, err } } return values, nil } // processAnswerInstall adds answers to given map, and prompts users to answers chart questions if interactive is true func processAnswerInstall( ctx *cli.Context, tv *managementClient.TemplateVersion, answers, answersSetString map[string]string, interactive bool, multicluster bool, ) (map[string]string, map[string]string, error) { var err error answers, answersSetString, err = processAnswerUpdates(ctx, answers, answersSetString) if err != nil { return answers, answersSetString, err } // interactive occurs before adding defaults to ensure all questions are asked if interactive { // answers to questions will be added to map err := askQuestions(tv, answers) if err != nil { return answers, answersSetString, err } } if multicluster && !interactive { // add default values if answers missing from map err = fillInDefaultAnswersStringMap(tv, answers) if err != nil { return answers, answersSetString, err } } return answers, answersSetString, nil } func processAnswerUpdates(ctx *cli.Context, answers, answersSetString map[string]string) (map[string]string, map[string]string, error) { logrus.Println("ok") if answers == nil || ctx.Bool("reset") { // this would not be possible without returning a map answers = make(map[string]string) } if answersSetString == nil || ctx.Bool("reset") { // this would not be possible without returning a map answersSetString = make(map[string]string) } if ctx.String("answers") != "" { err := parseAnswersFile(ctx.String("answers"), answers) if err != nil { return answers, answersSetString, err } } for _, answer := range ctx.StringSlice("set") { parts := strings.SplitN(answer, "=", 2) if len(parts) == 2 { answers[parts[0]] = parts[1] } } for _, answer := range ctx.StringSlice("set-string") { parts := strings.SplitN(answer, "=", 2) logrus.Printf("%v\n", parts) if len(parts) == 2 { answersSetString[parts[0]] = parts[1] } } return answers, answersSetString, nil } // parseMapToYamlString create yaml string from answers map func parseMapToYamlString(answerMap map[string]interface{}) (string, error) { yamlFileString, err := yaml.Marshal(answerMap) if err != nil { return "", err } return string(yamlFileString), nil } func parseAnswersFile(location string, answers map[string]string) error { holder, err := parseFile(location) if err != nil { return err } for key, value := range holder { switch value.(type) { case nil: answers[key] = "" default: answers[key] = fmt.Sprintf("%v", value) } } return nil } func parseFile(location string) (map[string]interface{}, error) { bytes, err := ioutil.ReadFile(location) if err != nil { return nil, err } return createValuesMap(bytes) } func createValuesMap(bytes []byte) (map[string]interface{}, error) { values := make(map[string]interface{}) if hasPrefix(bytes, []byte("{")) { // this is the check that "readFileReturnJSON" uses to differentiate between JSON and YAML if err := json.Unmarshal(bytes, &values); err != nil { return nil, err } } else { if err := yaml.Unmarshal(bytes, &values); err != nil { return nil, err } } return values, nil } func askQuestions(tv *managementClient.TemplateVersion, answers map[string]string) error { var asked bool var attempts int if tv == nil { return nil } for { attempts++ for _, question := range tv.Questions { if _, ok := answers[question.Variable]; !ok && checkShowIfStringMap(question.ShowIf, answers) { asked = true answers[question.Variable] = askQuestion(question) if checkShowSubquestionIfStringMap(question, answers) { for _, subQuestion := range question.Subquestions { // only ask the question if there is not an answer and it passes the ShowIf check if _, ok := answers[subQuestion.Variable]; !ok && checkShowIfStringMap(subQuestion.ShowIf, answers) { answers[subQuestion.Variable] = askSubQuestion(subQuestion) } } } } } if !asked { return nil } else if attempts >= 10 { return errors.New("attempted questions 10 times") } asked = false } } func askQuestion(q managementClient.Question) string { if len(q.Description) > 0 { fmt.Printf("\nDescription: %s\n", q.Description) } if len(q.Options) > 0 { options := strings.Join(q.Options, ", ") fmt.Printf("Accepted Options: %s\n", options) } fmt.Printf("Name: %s\nVariable Name: %s\nDefault:[%s]\nEnter answer or 'return' for default:", q.Label, q.Variable, q.Default) answer, err := bufio.NewReader(os.Stdin).ReadString('\n') if err != nil { return "" } answer = strings.TrimSpace(answer) if answer == "" { answer = q.Default } return answer } func askSubQuestion(q managementClient.SubQuestion) string { if len(q.Description) > 0 { fmt.Printf("\nDescription: %s\n", q.Description) } if len(q.Options) > 0 { options := strings.Join(q.Options, ", ") fmt.Printf("Accepted Options: %s\n", options) } fmt.Printf("Name: %s\nVariable Name: %s\nDefault:[%s]\nEnter answer or 'return' for default:", q.Label, q.Variable, q.Default) answer, err := bufio.NewReader(os.Stdin).ReadString('\n') if err != nil { return "" } answer = strings.TrimSpace(answer) if answer == "" { answer = q.Default } return answer } // fillInDefaultAnswers parses through questions and creates an answer map with default answers if missing from map func fillInDefaultAnswers(tv *managementClient.TemplateVersion, answers map[string]interface{}) error { if tv == nil { return nil } for _, question := range tv.Questions { if _, ok := answers[question.Variable]; !ok && checkShowIf(question.ShowIf, answers) { answers[question.Variable] = question.Default if checkShowSubquestionIf(question, answers) { for _, subQuestion := range question.Subquestions { // set the sub-question if the showIf check passes if _, ok := answers[subQuestion.Variable]; !ok && checkShowIf(subQuestion.ShowIf, answers) { answers[subQuestion.Variable] = subQuestion.Default } } } } } if answers == nil { return errors.New("could not generate default answers") } return nil } // checkShowIf uses the ShowIf field to determine if a question should be asked // this field comes in the format <key>=<value> where key is a question id and value is the answer func checkShowIf(s string, answers map[string]interface{}) bool { // No ShowIf so always ask the question if len(s) == 0 { return true } pieces := strings.Split(s, "=") if len(pieces) != 2 { return false } //if the key exists and the val matches the expression ask the question if val, ok := answers[pieces[0]]; ok && fmt.Sprintf("%v", val) == pieces[1] { return true } return false } // fillInDefaultAnswersStringMap parses through questions and creates an answer map with default answers if missing from map func fillInDefaultAnswersStringMap(tv *managementClient.TemplateVersion, answers map[string]string) error { if tv == nil { return nil } for _, question := range tv.Questions { if _, ok := answers[question.Variable]; !ok && checkShowIfStringMap(question.ShowIf, answers) { answers[question.Variable] = question.Default if checkShowSubquestionIfStringMap(question, answers) { for _, subQuestion := range question.Subquestions { // set the sub-question if the showIf check passes if _, ok := answers[subQuestion.Variable]; !ok && checkShowIfStringMap(subQuestion.ShowIf, answers) { answers[subQuestion.Variable] = subQuestion.Default } } } } } if answers == nil { return errors.New("could not generate default answers") } return nil } // checkShowIfStringMap uses the ShowIf field to determine if a question should be asked // this field comes in the format <key>=<value> where key is a question id and value is the answer func checkShowIfStringMap(s string, answers map[string]string) bool { // No ShowIf so always ask the question if len(s) == 0 { return true } pieces := strings.Split(s, "=") if len(pieces) != 2 { return false } //if the key exists and the val matches the expression ask the question if val, ok := answers[pieces[0]]; ok && val == pieces[1] { return true } return false } func checkShowSubquestionIf(q managementClient.Question, answers map[string]interface{}) bool { if val, ok := answers[q.Variable]; ok { if fmt.Sprintf("%v", val) == q.ShowSubquestionIf { return true } } return false } func checkShowSubquestionIfStringMap(q managementClient.Question, answers map[string]string) bool { if val, ok := answers[q.Variable]; ok { if val == q.ShowSubquestionIf { return true } } return false } 0707010000000C000081A400000000000000000000000165D8C5D30000038B000000000000000000000000000000000000001A00000000cli-2.8.3/cmd/app_test.gopackage cmd import ( "testing" "github.com/stretchr/testify/assert" ) func TestGetExternalIDInVersion(t *testing.T) { assert := assert.New(t) got, err := updateExternalIDVersion("catalog://?catalog=library&template=cert-manager&version=v0.5.2", "v1.2.3") assert.Nil(err) assert.Equal("catalog://?catalog=library&template=cert-manager&version=v1.2.3", got) got, err = updateExternalIDVersion("catalog://?catalog=c-29wkq/clusterscope&type=clusterCatalog&template=mysql&version=0.3.8", "0.3.9") assert.Nil(err) assert.Equal("catalog://?catalog=c-29wkq/clusterscope&type=clusterCatalog&template=mysql&version=0.3.9", got) got, err = updateExternalIDVersion("catalog://?catalog=p-j9gfw/projectscope&type=projectCatalog&template=grafana&version=0.0.31", "0.0.30") assert.Nil(err) assert.Equal("catalog://?catalog=p-j9gfw/projectscope&type=projectCatalog&template=grafana&version=0.0.30", got) } 0707010000000D000081A400000000000000000000000165D8C5D300001A8A000000000000000000000000000000000000001900000000cli-2.8.3/cmd/catalog.gopackage cmd import ( "strings" "time" "github.com/pkg/errors" managementClient "github.com/rancher/rancher/pkg/client/generated/management/v3" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) const ( addCatalogDescription = ` Add a new catalog to the Rancher server Example: # Add a catalog $ rancher catalog add foo https://my.catalog # Add a catalog and specify the branch to use $ rancher catalog add --branch awesomebranch foo https://my.catalog # Add a catalog and specify the helm version to use. Specify 'v2' for helm 2 and 'v3' for helm 3 $ rancher catalog add --helm-version v3 foo https://my.catalog ` refreshCatalogDescription = ` Refresh a catalog on the Rancher server Example: # Refresh a catalog $ rancher catalog refresh foo # Refresh multiple catalogs $ rancher catalog refresh foo bar baz # Refresh all catalogs $ rancher catalog refresh --all # Refresh is asynchronous unless you specify '--wait' $ rancher catalog refresh --all --wait --wait-timeout=60 # Default wait timeout is 60 seconds, set to 0 to remove the timeout $ rancher catalog refresh --all --wait --wait-timeout=0 ` ) type CatalogData struct { ID string Catalog managementClient.Catalog } func CatalogCommand() cli.Command { catalogLsFlags := []cli.Flag{ formatFlag, quietFlag, cli.BoolFlag{ Name: "verbose,v", Usage: "Include the catalog's state", }, } return cli.Command{ Name: "catalog", Usage: "Operations with catalogs", Action: defaultAction(catalogLs), Flags: catalogLsFlags, Subcommands: []cli.Command{ cli.Command{ Name: "ls", Usage: "List catalogs", Description: "\nList all catalogs in the current Rancher server", ArgsUsage: "None", Action: catalogLs, Flags: catalogLsFlags, }, cli.Command{ Name: "add", Usage: "Add a catalog", Description: addCatalogDescription, ArgsUsage: "[NAME, URL]", Action: catalogAdd, Flags: []cli.Flag{ cli.StringFlag{ Name: "branch", Usage: "Branch from the url to use", Value: "master", }, cli.StringFlag{ Name: "helm-version", Usage: "Version of helm the app(s) in your catalog will use for deployment. Use 'v2' for helm 2 or 'v3' for helm 3", Value: "v2", }, }, }, cli.Command{ Name: "delete", Usage: "Delete a catalog", Description: "\nDelete a catalog from the Rancher server", ArgsUsage: "[CATALOG_NAME/CATALOG_ID]", Action: catalogDelete, }, cli.Command{ Name: "refresh", Usage: "Refresh catalog templates", Description: refreshCatalogDescription, ArgsUsage: "[CATALOG_NAME/CATALOG_ID]...", Action: catalogRefresh, Flags: []cli.Flag{ cli.BoolFlag{ Name: "all", Usage: "Refresh all catalogs", }, cli.BoolFlag{ Name: "wait,w", Usage: "Wait for catalog(s) to become active", }, cli.IntFlag{ Name: "wait-timeout", Usage: "Wait timeout duration in seconds", Value: 60, }, }, }, }, } } func catalogLs(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } collection, err := c.ManagementClient.Catalog.List(defaultListOpts(ctx)) if err != nil { return err } fields := [][]string{ {"ID", "ID"}, {"NAME", "Catalog.Name"}, {"URL", "Catalog.URL"}, {"BRANCH", "Catalog.Branch"}, {"KIND", "Catalog.Kind"}, {"HELMVERSION", "Catalog.HelmVersion"}, } if ctx.Bool("verbose") { fields = append(fields, []string{"STATE", "Catalog.State"}) } writer := NewTableWriter(fields, ctx) defer writer.Close() for _, item := range collection.Data { writer.Write(&CatalogData{ ID: item.ID, Catalog: item, }) } return writer.Err() } func catalogAdd(ctx *cli.Context) error { if len(ctx.Args()) < 2 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } catalog := &managementClient.Catalog{ Branch: ctx.String("branch"), Name: ctx.Args().First(), Kind: "helm", URL: ctx.Args().Get(1), HelmVersion: strings.ToLower(ctx.String("helm-version")), } _, err = c.ManagementClient.Catalog.Create(catalog) if err != nil { return err } return nil } func catalogDelete(ctx *cli.Context) error { if len(ctx.Args()) < 1 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } for _, arg := range ctx.Args() { resource, err := Lookup(c, arg, "catalog") if err != nil { return err } catalog, err := c.ManagementClient.Catalog.ByID(resource.ID) if err != nil { return err } err = c.ManagementClient.Catalog.Delete(catalog) if err != nil { return err } } return nil } func catalogRefresh(ctx *cli.Context) error { if len(ctx.Args()) < 1 && !ctx.Bool("all") { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } var catalogs []managementClient.Catalog if ctx.Bool("all") { opts := baseListOpts() collection, err := c.ManagementClient.Catalog.List(opts) if err != nil { return err } // save the catalogs in case we need to wait for them to become active catalogs = collection.Data _, err = c.ManagementClient.Catalog.CollectionActionRefresh(collection) if err != nil { return err } } else { for _, arg := range ctx.Args() { resource, err := Lookup(c, arg, "catalog") if err != nil { return err } catalog, err := c.ManagementClient.Catalog.ByID(resource.ID) if err != nil { return err } // collect the refreshing catalogs in case we need to wait for them later catalogs = append(catalogs, *catalog) _, err = c.ManagementClient.Catalog.ActionRefresh(catalog) if err != nil { return err } } } if ctx.Bool("wait") { timeout := time.Duration(ctx.Int("wait-timeout")) * time.Second start := time.Now() logrus.Debugf("catalog: waiting for catalogs to become active (timeout=%v)", timeout) for _, catalog := range catalogs { logrus.Debugf("catalog: waiting for %s to become active", catalog.Name) resource, err := Lookup(c, catalog.Name, "catalog") if err != nil { return err } catalog, err := c.ManagementClient.Catalog.ByID(resource.ID) if err != nil { return err } for catalog.State != "active" { time.Sleep(time.Second) catalog, err = c.ManagementClient.Catalog.ByID(resource.ID) if err != nil { return err } if timeout > 0 && time.Since(start) > timeout { return errors.New("catalog: timed out waiting for refresh") } } } logrus.Debugf("catalog: waited for %v", time.Since(start)) } return nil } 0707010000000E000081A400000000000000000000000165D8C5D300004F21000000000000000000000000000000000000001900000000cli-2.8.3/cmd/cluster.gopackage cmd import ( "encoding/json" "errors" "fmt" "strconv" "strings" "github.com/rancher/cli/cliclient" managementClient "github.com/rancher/rancher/pkg/client/generated/management/v3" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) const ( importDescription = ` Imports an existing cluster to be used in rancher by using a generated kubectl command to run in your existing Kubernetes cluster. ` importClusterNotice = "If you get an error about 'certificate signed by unknown authority' " + "because your Rancher installation is running with an untrusted/self-signed SSL " + "certificate, run the command below instead to bypass the certificate check:" ) type ClusterData struct { ID string Current string Cluster managementClient.Cluster Name string Provider string Nodes int64 CPU string RAM string Pods string } func ClusterCommand() cli.Command { return cli.Command{ Name: "clusters", Aliases: []string{"cluster"}, Usage: "Operations on clusters", Action: defaultAction(clusterLs), Subcommands: []cli.Command{ { Name: "ls", Usage: "List clusters", Description: "Lists all clusters", ArgsUsage: "None", Action: clusterLs, Flags: []cli.Flag{ cli.StringFlag{ Name: "format", Usage: "'json', 'yaml' or Custom format: '{{.Cluster.ID}} {{.Cluster.Name}}'", }, quietFlag, }, }, { Name: "create", Usage: "Creates a new empty cluster", Description: "Create a new custom cluster with desired configuration", ArgsUsage: "[NEWCLUSTERNAME...]", Action: clusterCreate, Flags: []cli.Flag{ cli.StringFlag{ Name: "description", Usage: "Description to apply to the cluster", }, cli.BoolTFlag{ Name: "disable-docker-version", Usage: "Allow unsupported versions of docker on the nodes, [default=true]", }, cli.BoolFlag{ Name: "import", Usage: "Mark the cluster for import, this is required if the cluster is going to be used to import an existing k8s cluster", }, cli.StringFlag{ Name: "k8s-version", Usage: "Kubernetes version to use for the cluster, pass in 'list' to see available versions", }, cli.StringFlag{ Name: "network-provider", Usage: "Network provider for the cluster (flannel, canal, calico)", Value: "canal", }, cli.StringFlag{ Name: "psp-default-policy", Usage: "Default pod security policy to apply", }, cli.StringFlag{ Name: "rke-config", Usage: "Location of an rke config file to import. Can be JSON or YAML format", }, }, }, { Name: "import", Usage: "Import an existing Kubernetes cluster into a Rancher cluster", Description: importDescription, ArgsUsage: "[CLUSTERID CLUSTERNAME]", Action: clusterImport, Flags: []cli.Flag{ quietFlag, }, }, { Name: "add-node", Usage: "Outputs the docker command needed to add a node to an existing Rancher custom cluster", ArgsUsage: "[CLUSTERID CLUSTERNAME]", Action: clusterAddNode, Flags: []cli.Flag{ cli.StringSliceFlag{ Name: "label", Usage: "Label to apply to a node in the format [name]=[value]", }, cli.BoolFlag{ Name: "etcd", Usage: "Use node for etcd", }, cli.BoolFlag{ Name: "management", Usage: "Use node for management (DEPRECATED, use controlplane instead)", }, cli.BoolFlag{ Name: "controlplane", Usage: "Use node for controlplane", }, cli.BoolFlag{ Name: "worker", Usage: "Use node as a worker", }, quietFlag, }, }, { Name: "delete", Aliases: []string{"rm"}, Usage: "Delete a cluster", ArgsUsage: "[CLUSTERID/CLUSTERNAME...]", Action: clusterDelete, }, { Name: "export", Usage: "Export a cluster", ArgsUsage: "[CLUSTERID/CLUSTERNAME...]", Action: clusterExport, }, { Name: "kubeconfig", Aliases: []string{"kf"}, Usage: "Return the kube config used to access the cluster", ArgsUsage: "[CLUSTERID CLUSTERNAME]", Action: clusterKubeConfig, }, { Name: "add-member-role", Usage: "Add a member to the cluster", Action: addClusterMemberRoles, Description: "Examples:\n #Create the roles of 'nodes-view' and 'projects-view' for a user named 'user1'\n rancher cluster add-member-role user1 nodes-view projects-view\n", ArgsUsage: "[USERNAME, ROLE...]", Flags: []cli.Flag{ cli.StringFlag{ Name: "cluster-id", Usage: "Optional cluster ID to add member role to, defaults to the current context", }, }, }, { Name: "delete-member-role", Usage: "Delete a member from the cluster", Action: deleteClusterMemberRoles, Description: "Examples:\n #Delete the roles of 'nodes-view' and 'projects-view' for a user named 'user1'\n rancher cluster delete-member-role user1 nodes-view projects-view\n", ArgsUsage: "[USERNAME, ROLE...]", Flags: []cli.Flag{ cli.StringFlag{ Name: "cluster-id", Usage: "Optional cluster ID to remove member role from, defaults to the current context", }, }, }, { Name: "list-roles", Usage: "List all available roles for a cluster", Action: listClusterRoles, }, { Name: "list-members", Usage: "List current members of the cluster", Action: listClusterMembers, Flags: []cli.Flag{ cli.StringFlag{ Name: "cluster-id", Usage: "Optional cluster ID to list members for, defaults to the current context", }, }, }, }, } } func clusterLs(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } collection, err := c.ManagementClient.Cluster.List(defaultListOpts(ctx)) if err != nil { return err } writer := NewTableWriter([][]string{ {"CURRENT", "Current"}, {"ID", "ID"}, {"STATE", "Cluster.State"}, {"NAME", "Name"}, {"PROVIDER", "Provider"}, {"NODES", "Nodes"}, {"CPU", "CPU"}, {"RAM", "RAM"}, {"PODS", "Pods"}, }, ctx) defer writer.Close() for _, item := range collection.Data { var current string if item.ID == c.UserConfig.FocusedCluster() { current = "*" } writer.Write(&ClusterData{ ID: item.ID, Current: current, Cluster: item, Name: getClusterName(&item), Provider: getClusterProvider(item), Nodes: item.NodeCount, CPU: getClusterCPU(item), RAM: getClusterRAM(item), Pods: getClusterPods(item), }) } return writer.Err() } func clusterCreate(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } if ctx.String("k8s-version") != "" { k8sVersions := getClusterK8sOptions(c) if ok := findStringInArray(ctx.String("k8s-version"), k8sVersions); !ok { fmt.Println("Available Kubernetes versions:") for _, val := range k8sVersions { fmt.Println(val) } return nil } } config, err := getClusterConfig(ctx) if err != nil { return err } createdCluster, err := c.ManagementClient.Cluster.Create(config) if err != nil { return err } fmt.Printf("Successfully created cluster %v\n", createdCluster.Name) return nil } func clusterImport(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } resource, err := Lookup(c, ctx.Args().First(), "cluster") if err != nil { return err } cluster, err := getClusterByID(c, resource.ID) if err != nil { return err } if cluster.Driver != "" { return errors.New("existing k8s cluster can't be imported into this cluster") } clusterToken, err := getClusterRegToken(ctx, c, cluster.ID) if err != nil { return err } if ctx.Bool("quiet") { fmt.Println(clusterToken.Command) fmt.Println(clusterToken.InsecureCommand) return nil } fmt.Printf("Run the following command in your cluster:\n%s\n\n%s\n%s\n", clusterToken.Command, importClusterNotice, clusterToken.InsecureCommand) return nil } // clusterAddNode prints the command needed to add a node to a cluster func clusterAddNode(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } resource, err := Lookup(c, ctx.Args().First(), "cluster") if err != nil { return err } cluster, err := getClusterByID(c, resource.ID) if err != nil { return err } if cluster.Driver == "rancherKubernetesEngine" || cluster.Driver == "" { filter := defaultListOpts(ctx) filter.Filters["clusterId"] = cluster.ID nodePools, err := c.ManagementClient.NodePool.List(filter) if err != nil { return err } if len(nodePools.Data) > 0 { return errors.New("a node can't be manually registered to a cluster utilizing node-pools") } } else { return errors.New("a node can only be manually registered to a custom cluster") } clusterToken, err := getClusterRegToken(ctx, c, cluster.ID) if err != nil { return err } var roleFlags string if ctx.Bool("etcd") { roleFlags = roleFlags + " --etcd" } if ctx.Bool("management") || ctx.Bool("controlplane") { if ctx.Bool("management") && !ctx.Bool("quiet") { logrus.Info("The flag --management is deprecated and replaced by --controlplane") } roleFlags = roleFlags + " --controlplane" } if ctx.Bool("worker") { roleFlags = roleFlags + " --worker" } command := clusterToken.NodeCommand + roleFlags if labels := ctx.StringSlice("label"); labels != nil { for _, label := range labels { command = command + fmt.Sprintf(" --label %v", label) } } if ctx.Bool("quiet") { fmt.Println(command) return nil } fmt.Printf("Run this command on an existing machine already running a "+ "supported version of Docker:\n%v\n", command) return nil } func clusterDelete(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } for _, cluster := range ctx.Args() { resource, err := Lookup(c, cluster, "cluster") if err != nil { return err } cluster, err := getClusterByID(c, resource.ID) if err != nil { return err } err = c.ManagementClient.Cluster.Delete(cluster) if err != nil { return err } } return nil } func clusterExport(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } resource, err := Lookup(c, ctx.Args().First(), "cluster") if err != nil { return err } cluster, err := getClusterByID(c, resource.ID) if err != nil { return err } if _, ok := cluster.Actions["exportYaml"]; !ok { return errors.New("cluster does not support being exported") } export, err := c.ManagementClient.Cluster.ActionExportYaml(cluster) if err != nil { return err } fmt.Println(export.YAMLOutput) return nil } func clusterKubeConfig(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } resource, err := Lookup(c, ctx.Args().First(), "cluster") if err != nil { return err } cluster, err := getClusterByID(c, resource.ID) if err != nil { return err } config, err := c.ManagementClient.Cluster.ActionGenerateKubeconfig(cluster) if err != nil { return err } fmt.Println(config.Config) return nil } func addClusterMemberRoles(ctx *cli.Context) error { if len(ctx.Args()) < 2 { return cli.ShowSubcommandHelp(ctx) } memberName := ctx.Args().First() roles := ctx.Args()[1:] c, err := GetClient(ctx) if err != nil { return err } member, err := searchForMember(ctx, c, memberName) if err != nil { return err } clusterID := c.UserConfig.FocusedCluster() if ctx.String("cluster-id") != "" { clusterID = ctx.String("cluster-id") } for _, role := range roles { rtb := managementClient.ClusterRoleTemplateBinding{ ClusterID: clusterID, RoleTemplateID: role, UserPrincipalID: member.ID, } if member.PrincipalType == "user" { rtb.UserPrincipalID = member.ID } else { rtb.GroupPrincipalID = member.ID } _, err = c.ManagementClient.ClusterRoleTemplateBinding.Create(&rtb) if err != nil { return err } } return nil } func deleteClusterMemberRoles(ctx *cli.Context) error { if len(ctx.Args()) < 2 { return cli.ShowSubcommandHelp(ctx) } memberName := ctx.Args().First() roles := ctx.Args()[1:] c, err := GetClient(ctx) if err != nil { return err } member, err := searchForMember(ctx, c, memberName) if err != nil { return err } clusterID := c.UserConfig.FocusedCluster() if ctx.String("cluster-id") != "" { clusterID = ctx.String("cluster-id") } for _, role := range roles { filter := defaultListOpts(ctx) filter.Filters["clusterId"] = clusterID filter.Filters["roleTemplateId"] = role if member.PrincipalType == "user" { filter.Filters["userPrincipalId"] = member.ID } else { filter.Filters["groupPrincipalId"] = member.ID } bindings, err := c.ManagementClient.ClusterRoleTemplateBinding.List(filter) if err != nil { return err } for _, binding := range bindings.Data { err = c.ManagementClient.ClusterRoleTemplateBinding.Delete(&binding) if err != nil { return err } } } return nil } func listClusterRoles(ctx *cli.Context) error { return listRoles(ctx, "cluster") } func listClusterMembers(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } clusterID := c.UserConfig.FocusedCluster() if ctx.String("cluster-id") != "" { clusterID = ctx.String("cluster-id") } filter := defaultListOpts(ctx) filter.Filters["clusterId"] = clusterID bindings, err := c.ManagementClient.ClusterRoleTemplateBinding.List(filter) if err != nil { return err } userFilter := defaultListOpts(ctx) users, err := c.ManagementClient.User.List(userFilter) if err != nil { return err } userMap := usersToNameMapping(users.Data) var b []RoleTemplateBinding for _, binding := range bindings.Data { parsedTime, err := createdTimetoHuman(binding.Created) if err != nil { return err } b = append(b, RoleTemplateBinding{ ID: binding.ID, User: userMap[binding.UserID], Role: binding.RoleTemplateID, Created: parsedTime, }) } return listRoleTemplateBindings(ctx, b) } // getClusterRegToken will return an existing token or create one if none exist func getClusterRegToken( ctx *cli.Context, c *cliclient.MasterClient, clusterID string, ) (managementClient.ClusterRegistrationToken, error) { tokenOpts := defaultListOpts(ctx) tokenOpts.Filters["clusterId"] = clusterID clusterTokenCollection, err := c.ManagementClient.ClusterRegistrationToken.List(tokenOpts) if err != nil { return managementClient.ClusterRegistrationToken{}, err } if len(clusterTokenCollection.Data) == 0 { crt := &managementClient.ClusterRegistrationToken{ ClusterID: clusterID, } clusterToken, err := c.ManagementClient.ClusterRegistrationToken.Create(crt) if err != nil { return managementClient.ClusterRegistrationToken{}, err } return *clusterToken, nil } return clusterTokenCollection.Data[0], nil } func getClusterByID( c *cliclient.MasterClient, clusterID string, ) (*managementClient.Cluster, error) { cluster, err := c.ManagementClient.Cluster.ByID(clusterID) if err != nil { return nil, fmt.Errorf("no cluster found with the ID [%s], run "+ "`rancher clusters` to see available clusters: %s", clusterID, err) } return cluster, nil } func getClusterProvider(cluster managementClient.Cluster) string { switch cluster.Driver { case "imported": switch cluster.Provider { case "rke2": return "RKE2" case "k3s": return "K3S" default: return "Imported" } case "k3s": return "K3S" case "rke2": return "RKE2" case "rancherKubernetesEngine": return "Rancher Kubernetes Engine" case "azureKubernetesService", "AKS": return "Azure Kubernetes Service" case "googleKubernetesEngine", "GKE": return "Google Kubernetes Engine" case "EKS": return "Elastic Kubernetes Service" default: return "Unknown" } } func getClusterCPU(cluster managementClient.Cluster) string { req := parseResourceString(cluster.Requested["cpu"]) alloc := parseResourceString(cluster.Allocatable["cpu"]) return req + "/" + alloc } func getClusterRAM(cluster managementClient.Cluster) string { req := parseResourceString(cluster.Requested["memory"]) alloc := parseResourceString(cluster.Allocatable["memory"]) return req + "/" + alloc + " GB" } // parseResourceString returns GB for Ki and Mi and CPU cores from 'm' func parseResourceString(mem string) string { if strings.HasSuffix(mem, "Ki") { num, err := strconv.ParseFloat(strings.Replace(mem, "Ki", "", -1), 64) if err != nil { return mem } num = num / 1024 / 1024 return strings.TrimSuffix(fmt.Sprintf("%.2f", num), ".0") } if strings.HasSuffix(mem, "Mi") { num, err := strconv.ParseFloat(strings.Replace(mem, "Mi", "", -1), 64) if err != nil { return mem } num = num / 1024 return strings.TrimSuffix(fmt.Sprintf("%.2f", num), ".0") } if strings.HasSuffix(mem, "m") { num, err := strconv.ParseFloat(strings.Replace(mem, "m", "", -1), 64) if err != nil { return mem } num = num / 1000 return strconv.FormatFloat(num, 'f', 2, 32) } return mem } func getClusterPods(cluster managementClient.Cluster) string { return cluster.Requested["pods"] + "/" + cluster.Allocatable["pods"] } func getClusterK8sOptions(c *cliclient.MasterClient) []string { var options []string setting, _ := c.ManagementClient.Setting.ByID("k8s-version-to-images") var objmap map[string]*json.RawMessage json.Unmarshal([]byte(setting.Value), &objmap) for key := range objmap { options = append(options, key) } return options } func getClusterConfig(ctx *cli.Context) (*managementClient.Cluster, error) { config := managementClient.Cluster{} config.Name = ctx.Args().First() config.Description = ctx.String("description") if !ctx.Bool("import") { config.RancherKubernetesEngineConfig = new(managementClient.RancherKubernetesEngineConfig) ignoreDockerVersion := ctx.BoolT("disable-docker-version") config.RancherKubernetesEngineConfig.IgnoreDockerVersion = &ignoreDockerVersion if ctx.String("k8s-version") != "" { config.RancherKubernetesEngineConfig.Version = ctx.String("k8s-version") } if ctx.String("network-provider") != "" { config.RancherKubernetesEngineConfig.Network = &managementClient.NetworkConfig{ Plugin: ctx.String("network-provider"), } } if ctx.String("rke-config") != "" { bytes, err := readFileReturnJSON(ctx.String("rke-config")) if err != nil { return nil, err } var jsonObject map[string]interface{} if err = json.Unmarshal(bytes, &jsonObject); err != nil { return nil, err } // Most values in RancherKubernetesEngineConfig are defined with struct tags for both JSON and YAML in camelCase. // Changing the tags will be a breaking change. For proper deserialization, we must convert all keys to camelCase. // Note that we ignore kebab-case keys. Users themselves should ensure any relevant keys // (especially top-level keys in `services`, like `kube-api` or `kube-controller`) are camelCase or snake-case in cluster config. convertSnakeCaseKeysToCamelCase(jsonObject) marshalled, err := json.Marshal(jsonObject) if err != nil { return nil, err } if err = json.Unmarshal(marshalled, &config); err != nil { return nil, err } } } if ctx.String("psp-default-policy") != "" { config.DefaultPodSecurityPolicyTemplateID = ctx.String("psp-default-policy") if config.RancherKubernetesEngineConfig != nil { config.RancherKubernetesEngineConfig.Services = &managementClient.RKEConfigServices{ KubeAPI: &managementClient.KubeAPIService{ PodSecurityPolicy: true, }, } } } return &config, nil } 0707010000000F000081A400000000000000000000000165D8C5D300004578000000000000000000000000000000000000001800000000cli-2.8.3/cmd/common.gopackage cmd import ( "bufio" "bytes" "context" "crypto/x509" "encoding/pem" "fmt" "io" "io/ioutil" "math/rand" "net/url" "os" "os/exec" "path/filepath" "regexp" "strconv" "strings" "syscall" "text/template" "time" "unicode" "github.com/docker/docker/pkg/namesgenerator" "github.com/ghodss/yaml" "github.com/pkg/errors" "github.com/rancher/cli/cliclient" "github.com/rancher/cli/config" "github.com/rancher/norman/clientbase" ntypes "github.com/rancher/norman/types" "github.com/rancher/norman/types/convert" managementClient "github.com/rancher/rancher/pkg/client/generated/management/v3" "github.com/sirupsen/logrus" "github.com/urfave/cli" "k8s.io/client-go/tools/clientcmd/api" ) const ( letters = "abcdefghijklmnopqrstuvwxyz0123456789" cfgFile = "cli2.json" kubeConfigKeyFormat = "%s-%s" ) var ( errNoURL = errors.New("RANCHER_URL environment or --Url is not set, run `login`") // ManagementResourceTypes lists the types we use the management client for ManagementResourceTypes = []string{"cluster", "node", "project"} // ProjectResourceTypes lists the types we use the cluster client for ProjectResourceTypes = []string{"secret", "namespacedSecret", "workload"} // ClusterResourceTypes lists the types we use the project client for ClusterResourceTypes = []string{"persistentVolume", "storageClass", "namespace"} formatFlag = cli.StringFlag{ Name: "format,o", Usage: "'json', 'yaml' or custom format", } quietFlag = cli.BoolFlag{ Name: "quiet,q", Usage: "Only display IDs or suppress help text", } ) type MemberData struct { Name string MemberType string AccessType string } type RoleTemplate struct { ID string Name string Description string } type RoleTemplateBinding struct { ID string User string Role string Created string } func listAllRoles() []string { roles := []string{} roles = append(roles, ManagementResourceTypes...) roles = append(roles, ProjectResourceTypes...) roles = append(roles, ClusterResourceTypes...) return roles } func listRoles(ctx *cli.Context, context string) error { c, err := GetClient(ctx) if err != nil { return err } filter := defaultListOpts(ctx) filter.Filters["hidden"] = false filter.Filters["context"] = context templates, err := c.ManagementClient.RoleTemplate.List(filter) if err != nil { return err } writer := NewTableWriter([][]string{ {"ID", "ID"}, {"NAME", "Name"}, {"DESCRIPTION", "Description"}, }, ctx) defer writer.Close() for _, item := range templates.Data { writer.Write(&RoleTemplate{ ID: item.ID, Name: item.Name, Description: item.Description, }) } return writer.Err() } func listRoleTemplateBindings(ctx *cli.Context, b []RoleTemplateBinding) error { writer := NewTableWriter([][]string{ {"BINDING-ID", "ID"}, {"USER", "User"}, {"ROLE", "Role"}, {"CREATED", "Created"}, }, ctx) defer writer.Close() for _, item := range b { writer.Write(&RoleTemplateBinding{ ID: item.ID, User: item.User, Role: item.Role, Created: item.Created, }) } return writer.Err() } func getKubeConfigForUser(ctx *cli.Context, user string) (*api.Config, error) { cf, err := loadConfig(ctx) if err != nil { return nil, err } focusedServer := cf.FocusedServer() kubeConfig, _ := focusedServer.KubeConfigs[fmt.Sprintf(kubeConfigKeyFormat, user, focusedServer.FocusedCluster())] return kubeConfig, nil } func setKubeConfigForUser(ctx *cli.Context, user string, kubeConfig *api.Config) error { cf, err := loadConfig(ctx) if err != nil { return err } if cf.FocusedServer().KubeConfigs == nil { cf.FocusedServer().KubeConfigs = make(map[string]*api.Config) } focusedServer := cf.FocusedServer() focusedServer.KubeConfigs[fmt.Sprintf(kubeConfigKeyFormat, user, focusedServer.FocusedCluster())] = kubeConfig return cf.Write() } func usersToNameMapping(u []managementClient.User) map[string]string { userMapping := make(map[string]string) for _, user := range u { if user.Name != "" { userMapping[user.ID] = user.Name } else { userMapping[user.ID] = user.Username } } return userMapping } func searchForMember(ctx *cli.Context, c *cliclient.MasterClient, name string) (*managementClient.Principal, error) { filter := defaultListOpts(ctx) filter.Filters["ID"] = "thisisnotathingIhope" // A collection is needed to get the action link pCollection, err := c.ManagementClient.Principal.List(filter) if err != nil { return nil, err } p := managementClient.SearchPrincipalsInput{ Name: name, } results, err := c.ManagementClient.Principal.CollectionActionSearch(pCollection, &p) if err != nil { return nil, err } dataLength := len(results.Data) switch { case dataLength == 0: return nil, fmt.Errorf("no results found for %q", name) case dataLength == 1: return &results.Data[0], nil case dataLength >= 10: results.Data = results.Data[:10] } var names []string for _, person := range results.Data { names = append(names, person.Name+fmt.Sprintf(" (%s)", person.PrincipalType)) } selection := selectFromList("Multiple results found:", names) return &results.Data[selection], nil } func getRancherServerVersion(c *cliclient.MasterClient) (string, error) { setting, err := c.ManagementClient.Setting.ByID("server-version") if err != nil { return "", err } return setting.Value, err } func loadAndVerifyCert(path string) (string, error) { caCert, err := ioutil.ReadFile(path) if err != nil { return "", err } return verifyCert(caCert) } func verifyCert(caCert []byte) (string, error) { // replace the escaped version of the line break caCert = bytes.Replace(caCert, []byte(`\n`), []byte("\n"), -1) block, _ := pem.Decode(caCert) if nil == block { return "", errors.New("No cert was found") } parsedCert, err := x509.ParseCertificate(block.Bytes) if err != nil { return "", err } if !parsedCert.IsCA { return "", errors.New("CACerts is not valid") } return string(caCert), nil } func GetConfigPath(ctx *cli.Context) string { // path will always be set by the global flag default path := ctx.GlobalString("config") return filepath.Join(path, cfgFile) } func loadConfig(ctx *cli.Context) (config.Config, error) { path := GetConfigPath(ctx) return config.LoadFromPath(path) } func lookupConfig(ctx *cli.Context) (*config.ServerConfig, error) { cf, err := loadConfig(ctx) if err != nil { return nil, err } cs := cf.FocusedServer() if cs == nil { return nil, errors.New("no configuration found, run `login`") } return cs, nil } func GetClient(ctx *cli.Context) (*cliclient.MasterClient, error) { cf, err := lookupConfig(ctx) if err != nil { return nil, err } mc, err := cliclient.NewMasterClient(cf) if err != nil { return nil, err } return mc, nil } // GetResourceType maps an incoming resource type to a valid one from the schema func GetResourceType(c *cliclient.MasterClient, resource string) (string, error) { if c.ManagementClient != nil { for key := range c.ManagementClient.APIBaseClient.Types { if strings.ToLower(key) == strings.ToLower(resource) { return key, nil } } } if c.ProjectClient != nil { for key := range c.ProjectClient.APIBaseClient.Types { if strings.ToLower(key) == strings.ToLower(resource) { return key, nil } } } if c.ClusterClient != nil { for key := range c.ClusterClient.APIBaseClient.Types { if strings.ToLower(key) == strings.ToLower(resource) { return key, nil } } } if c.CAPIClient != nil { for key := range c.CAPIClient.APIBaseClient.Types { lowerKey := strings.ToLower(key) if strings.HasPrefix(lowerKey, "cluster.x-k8s.io") && lowerKey == strings.ToLower(resource) { return key, nil } } } return "", fmt.Errorf("unknown resource type: %s", resource) } func Lookup(c *cliclient.MasterClient, name string, types ...string) (*ntypes.Resource, error) { var byName *ntypes.Resource for _, schemaType := range types { rt, err := GetResourceType(c, schemaType) if err != nil { logrus.Debugf("Error GetResourceType: %v", err) return nil, err } var schemaClient clientbase.APIBaseClientInterface // the schemaType dictates which client we need to use if c.CAPIClient != nil { if strings.HasPrefix(rt, "cluster.x-k8s.io") { schemaClient = c.CAPIClient } } if c.ManagementClient != nil { if _, ok := c.ManagementClient.APIBaseClient.Types[rt]; ok { schemaClient = c.ManagementClient } } if c.ProjectClient != nil { if _, ok := c.ProjectClient.APIBaseClient.Types[rt]; ok { schemaClient = c.ProjectClient } } if c.ClusterClient != nil { if _, ok := c.ClusterClient.APIBaseClient.Types[rt]; ok { schemaClient = c.ClusterClient } } // Attempt to get the resource by ID var resource ntypes.Resource if err := schemaClient.ByID(schemaType, name, &resource); !clientbase.IsNotFound(err) && err != nil { logrus.Debugf("Error schemaClient.ByID: %v", err) return nil, err } else if err == nil && resource.ID == name { return &resource, nil } // Resource was not found assuming the ID, check if it's the name of a resource var collection ntypes.ResourceCollection listOpts := &ntypes.ListOpts{ Filters: map[string]interface{}{ "name": name, "removed_null": 1, }, } if err := schemaClient.List(schemaType, listOpts, &collection); !clientbase.IsNotFound(err) && err != nil { logrus.Debugf("Error schemaClient.List: %v", err) return nil, err } if len(collection.Data) > 1 { ids := []string{} for _, data := range collection.Data { ids = append(ids, data.ID) } return nil, fmt.Errorf("Multiple resources of type %s found for name %s: %v", schemaType, name, ids) } // No matches for this schemaType, try the next one if len(collection.Data) == 0 { continue } if byName != nil { return nil, fmt.Errorf("Multiple resources named %s: %s:%s, %s:%s", name, collection.Data[0].Type, collection.Data[0].ID, byName.Type, byName.ID) } byName = &collection.Data[0] } if byName == nil { return nil, fmt.Errorf("Not found: %s", name) } return byName, nil } func RandomName() string { return strings.Replace(namesgenerator.GetRandomName(0), "_", "-", -1) } // RandomLetters returns a string with random letters of length n func RandomLetters(n int) string { rand.Seed(time.Now().UnixNano()) b := make([]byte, n) for i := range b { b[i] = letters[rand.Intn(len(letters))] } return string(b) } func appendTabDelim(buf *bytes.Buffer, value string) { if buf.Len() == 0 { buf.WriteString(value) } else { buf.WriteString("\t") buf.WriteString(value) } } func SimpleFormat(values [][]string) (string, string) { headerBuffer := bytes.Buffer{} valueBuffer := bytes.Buffer{} for _, v := range values { appendTabDelim(&headerBuffer, v[0]) if strings.Contains(v[1], "{{") { appendTabDelim(&valueBuffer, v[1]) } else { appendTabDelim(&valueBuffer, "{{."+v[1]+"}}") } } headerBuffer.WriteString("\n") valueBuffer.WriteString("\n") return headerBuffer.String(), valueBuffer.String() } func defaultAction(fn func(ctx *cli.Context) error) func(ctx *cli.Context) error { return func(ctx *cli.Context) error { if ctx.Bool("help") { cli.ShowAppHelp(ctx) return nil } return fn(ctx) } } func printTemplate(out io.Writer, templateContent string, obj interface{}) error { funcMap := map[string]interface{}{ "endpoint": FormatEndpoint, "ips": FormatIPAddresses, "json": FormatJSON, } tmpl, err := template.New("").Funcs(funcMap).Parse(templateContent) if err != nil { return err } return tmpl.Execute(out, obj) } func selectFromList(header string, choices []string) int { if header != "" { fmt.Println(header) } reader := bufio.NewReader(os.Stdin) selected := -1 for selected <= 0 || selected > len(choices) { for i, choice := range choices { fmt.Printf("[%d] %s\n", i+1, choice) } fmt.Print("Select: ") text, _ := reader.ReadString('\n') text = strings.TrimSpace(text) num, err := strconv.Atoi(text) if err == nil { selected = num } } return selected - 1 } func processExitCode(err error) error { if exitErr, ok := err.(*exec.ExitError); ok { if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { os.Exit(status.ExitStatus()) } } return err } func SplitOnColon(s string) []string { return strings.Split(s, ":") } func parseClusterAndProjectName(name string) (string, string, error) { parts := strings.SplitN(name, "/", 2) if len(parts) == 2 { return parts[0], parts[1], nil } return "", "", fmt.Errorf("Unable to extract clustername and projectname from [%s]", name) } func parseClusterAndProjectID(id string) (string, string, error) { // Validate id // Examples: // c-qmpbm:p-mm62v // c-qmpbm:project-mm62v // c-m-j2s7m6lq:p-mm62v // See https://github.com/rancher/rancher/issues/14400 if match, _ := regexp.MatchString("((local)|(c-[[:alnum:]]{5})|(c-m-[[:alnum:]]{8})):(p|project)-[[:alnum:]]{5}", id); match { parts := SplitOnColon(id) return parts[0], parts[1], nil } return "", "", fmt.Errorf("Unable to extract clusterid and projectid from [%s]", id) } // Return a JSON blob of the file at path func readFileReturnJSON(path string) ([]byte, error) { file, err := ioutil.ReadFile(path) if err != nil { return []byte{}, err } // This is probably already JSON if true if hasPrefix(file, []byte("{")) { return file, nil } return yaml.YAMLToJSON(file) } // renameKeys renames the keys in a given map of arbitrary depth with a provided function for string keys. func renameKeys(input map[string]interface{}, f func(string) string) { for k, v := range input { delete(input, k) newKey := f(k) input[newKey] = v if innerMap, ok := v.(map[string]interface{}); ok { renameKeys(innerMap, f) } } } // convertSnakeCaseKeysToCamelCase takes a map and recursively transforms all snake_case keys into camelCase keys. func convertSnakeCaseKeysToCamelCase(input map[string]interface{}) { renameKeys(input, convert.ToJSONKey) } // Return true if the first non-whitespace bytes in buf is prefix. func hasPrefix(buf []byte, prefix []byte) bool { trim := bytes.TrimLeftFunc(buf, unicode.IsSpace) return bytes.HasPrefix(trim, prefix) } func settingsToMap(client *cliclient.MasterClient) (map[string]string, error) { configMap := make(map[string]string) settings, err := client.ManagementClient.Setting.List(baseListOpts()) if err != nil { return nil, err } for _, setting := range settings.Data { configMap[setting.Name] = setting.Value } return configMap, nil } // getClusterNames maps cluster ID to name and defaults to ID if name is blank func getClusterNames(ctx *cli.Context, c *cliclient.MasterClient) (map[string]string, error) { clusterNames := make(map[string]string) clusterCollection, err := c.ManagementClient.Cluster.List(defaultListOpts(ctx)) if err != nil { return clusterNames, err } for _, cluster := range clusterCollection.Data { if cluster.Name == "" { clusterNames[cluster.ID] = cluster.ID } else { clusterNames[cluster.ID] = cluster.Name } } return clusterNames, nil } func getClusterName(cluster *managementClient.Cluster) string { if cluster.Name != "" { return cluster.Name } return cluster.ID } func findStringInArray(s string, a []string) bool { for _, val := range a { if s == val { return true } } return false } func createdTimetoHuman(t string) (string, error) { parsedTime, err := time.Parse(time.RFC3339, t) if err != nil { return "", err } return parsedTime.Format("02 Jan 2006 15:04:05 MST"), nil } func outputMembers(ctx *cli.Context, c *cliclient.MasterClient, members []managementClient.Member) error { writer := NewTableWriter([][]string{ {"NAME", "Name"}, {"MEMBER_TYPE", "MemberType"}, {"ACCESS_TYPE", "AccessType"}, }, ctx) defer writer.Close() for _, m := range members { principalID := m.UserPrincipalID if m.UserPrincipalID == "" { principalID = m.GroupPrincipalID } principal, err := c.ManagementClient.Principal.ByID(url.PathEscape(principalID)) if err != nil { return err } writer.Write(&MemberData{ Name: principal.Name, MemberType: strings.Title(fmt.Sprintf("%s %s", principal.Provider, principal.PrincipalType)), AccessType: m.AccessType, }) } return writer.Err() } func addMembersByNames(ctx *cli.Context, c *cliclient.MasterClient, members []managementClient.Member, toAddMembers []string, accessType string) ([]managementClient.Member, error) { for _, name := range toAddMembers { member, err := searchForMember(ctx, c, name) if err != nil { return nil, err } toAddMember := managementClient.Member{ AccessType: accessType, } if member.PrincipalType == "user" { toAddMember.UserPrincipalID = member.ID } else { toAddMember.GroupPrincipalID = member.ID } members = append(members, toAddMember) } return members, nil } func deleteMembersByNames(ctx *cli.Context, c *cliclient.MasterClient, members []managementClient.Member, todeleteMembers []string) ([]managementClient.Member, error) { for _, name := range todeleteMembers { member, err := searchForMember(ctx, c, name) if err != nil { return nil, err } var toKeepMembers []managementClient.Member for _, m := range members { if m.GroupPrincipalID != member.ID && m.UserPrincipalID != member.ID { toKeepMembers = append(toKeepMembers, m) } } members = toKeepMembers } return members, nil } func tickerContext(ctx context.Context, duration time.Duration) <-chan time.Time { ticker := time.NewTicker(duration) go func() { <-ctx.Done() ticker.Stop() }() return ticker.C } 07070100000010000081A400000000000000000000000165D8C5D300000819000000000000000000000000000000000000001D00000000cli-2.8.3/cmd/common_test.gopackage cmd import ( "testing" "gopkg.in/check.v1" ) // Hook up gocheck into the "go test" runner. func Test(t *testing.T) { check.TestingT(t) } type CommonTestSuite struct { } var _ = check.Suite(&CommonTestSuite{}) func (s *CommonTestSuite) SetUpSuite(c *check.C) { } func (s *CommonTestSuite) TestParseClusterAndProjectID(c *check.C) { testParse(c, "local:p-12345", "local", "p-12345", false) testParse(c, "c-12345:p-12345", "c-12345", "p-12345", false) testParse(c, "cocal:p-12345", "", "", true) testParse(c, "c-123:p-123", "", "", true) testParse(c, "", "", "", true) testParse(c, "c-m-12345678:p-12345", "c-m-12345678", "p-12345", false) testParse(c, "c-m-123:p-12345", "", "", true) } func (s *CommonTestSuite) TestConvertSnakeCaseKeysToCamelCase(c *check.C) { cases := []struct { input map[string]interface{} renamed map[string]interface{} }{ { map[string]interface{}{"foo_bar": "hello"}, map[string]interface{}{"fooBar": "hello"}, }, { map[string]interface{}{"fooBar": "hello"}, map[string]interface{}{"fooBar": "hello"}, }, { map[string]interface{}{"foobar": "hello", "some_key": "valueUnmodified", "bar-baz": "bar-baz"}, map[string]interface{}{"foobar": "hello", "someKey": "valueUnmodified", "bar-baz": "bar-baz"}, }, { map[string]interface{}{"foo_bar": "hello", "backup_config": map[string]interface{}{"hello_world": true}, "config_id": 123}, map[string]interface{}{"fooBar": "hello", "backupConfig": map[string]interface{}{"helloWorld": true}, "configId": 123}, }, } for _, tc := range cases { convertSnakeCaseKeysToCamelCase(tc.input) c.Assert(tc.input, check.DeepEquals, tc.renamed) } } func testParse(c *check.C, testID, expectedCluster, expectedProject string, errorExpected bool) { actualCluster, actualProject, actualErr := parseClusterAndProjectID(testID) c.Assert(actualCluster, check.Equals, expectedCluster) c.Assert(actualProject, check.Equals, expectedProject) if errorExpected { c.Assert(actualErr, check.NotNil) } else { c.Assert(actualErr, check.IsNil) } } 07070100000011000081A400000000000000000000000165D8C5D300000625000000000000000000000000000000000000001900000000cli-2.8.3/cmd/context.gopackage cmd import ( "github.com/rancher/cli/cliclient" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) func ContextCommand() cli.Command { return cli.Command{ Name: "context", Usage: "Operations for the context", Description: `Switch or view context. A context is the server->cluster->project currently in focus. `, Subcommands: []cli.Command{ { Name: "switch", Usage: "Switch to a new context", Description: ` The project arg is optional, if not passed in a list of available projects will be displayed and one can be selected. If only one project is available it will be automatically selected. `, ArgsUsage: "[PROJECT_ID/PROJECT_NAME]", Action: contextSwitch, }, { Name: "current", Usage: "Display the current context", Action: loginContext, }, }, } } func contextSwitch(ctx *cli.Context) error { cf, err := loadConfig(ctx) if err != nil { return err } server := cf.FocusedServer() c, err := cliclient.NewManagementClient(server) if err != nil { return err } var projectID string if ctx.NArg() == 0 { projectID, err = getProjectContext(ctx, c) if err != nil { return nil } } else { resource, err := Lookup(c, ctx.Args().First(), "project") if err != nil { return err } projectID = resource.ID } project, err := c.ManagementClient.Project.ByID(projectID) if err != nil { return nil } logrus.Infof("Setting new context to project %s", project.Name) server.Project = project.ID err = cf.Write() if err != nil { return err } return nil } 07070100000012000081A400000000000000000000000165D8C5D300000271000000000000000000000000000000000000001800000000cli-2.8.3/cmd/delete.gopackage cmd import ( "fmt" "github.com/pkg/errors" "github.com/urfave/cli" ) func DeleteCommand() cli.Command { return cli.Command{ Name: "delete", Aliases: []string{"rm"}, Usage: "Delete resources by ID", Action: deleteResource, Flags: []cli.Flag{ cli.StringFlag{ Name: "type", Usage: "type of resource to delete", }, }, } } func deleteResource(ctx *cli.Context) error { if ctx.String("type") == "" { return errors.New("type is required for deletes") } //c, err := GetClient(ctx) //if err != nil { // return err //} fmt.Println("This isn't implemented yet") return nil } 07070100000013000081A400000000000000000000000165D8C5D3000003DA000000000000000000000000000000000000001800000000cli-2.8.3/cmd/format.gopackage cmd import ( "bytes" "encoding/json" "fmt" ) func FormatEndpoint(data interface{}) string { dataSlice, ok := data.([]interface{}) if !ok { return "" } buf := &bytes.Buffer{} for _, value := range dataSlice { dataMap, ok := value.(map[string]interface{}) if !ok { return "" } s := fmt.Sprintf("%v:%v", dataMap["ipAddress"], dataMap["port"]) if buf.Len() == 0 { buf.WriteString(s) } else { buf.WriteString(", ") buf.WriteString(s) } } return buf.String() } func FormatIPAddresses(data interface{}) string { //todo: revisit return "" //ips, ok := data.([]client.IpAddress) //if !ok { // return "" //} // //ipStrings := []string{} //for _, ip := range ips { // if ip.Address != "" { // ipStrings = append(ipStrings, ip.Address) // } //} // //return strings.Join(ipStrings, ", ") } func FormatJSON(data interface{}) (string, error) { bytes, err := json.MarshalIndent(data, "", " ") return string(bytes) + "\n", err } 07070100000014000081A400000000000000000000000165D8C5D300005C20000000000000000000000000000000000000001B00000000cli-2.8.3/cmd/globaldns.gopackage cmd import ( "errors" "fmt" "strings" "github.com/rancher/cli/cliclient" managementClient "github.com/rancher/rancher/pkg/client/generated/management/v3" "github.com/urfave/cli" ) const ( alidnsProvider = "alidns" cloudflareProvider = "cloudflare" route53Provider = "route53" envAlibabaCloudAccessKey = "ALI_ACCESS_KEY_ID" envAlibabaCloudSecretKey = "ALI_ACCESS_KEY_SECRET" envAwsAccessKey = "AWS_ACCESS_KEY_ID" envAwsSecretKey = "AWS_SECRET_ACCESS_KEY" envCloudflareAPIEmail = "CF_API_EMAIL" envCloudflareAPIKey = "CF_API_KEY" argAlibabaCloudAccessKey = "alibabacloud-access-key-id" argAlibabaCloudSecretKey = "alibabacloud-access-key-secret" argAwsAccessKey = "aws-access-key" argAwsSecretKey = "aws-secret-key" argCloudflareAPIEmail = "cloudflare-api-email" argCloudflareAPIKey = "cloudflare-api-key" argFQDN = "fqdn" argMember = "member" argMultiClusterApp = "multi-cluster-app" argProject = "project" argProvider = "provider" argRootDomain = "root-domain" argTTL = "ttl" argType = "type" memberAccessTypeOwner = "owner" ) type globalDNSHolder struct { ID string Provider managementClient.GlobalDnsProvider Entry managementClient.GlobalDns Target string } var globalDNSProviderCredentialFlags = []cli.Flag{ cli.StringFlag{ Name: argAlibabaCloudAccessKey, EnvVar: envAlibabaCloudAccessKey, Usage: "Alibaba Cloud access key ID for alidns provider", }, cli.StringFlag{ Name: argAlibabaCloudSecretKey, EnvVar: envAlibabaCloudSecretKey, Usage: "Alibaba Cloud access key secret for alidns provider", }, cli.StringFlag{ Name: argAwsAccessKey, EnvVar: envAwsAccessKey, Usage: "AWS access key for route53 provider", }, cli.StringFlag{ Name: argAwsSecretKey, EnvVar: envAwsSecretKey, Usage: "AWS secret key for route53 provider", }, cli.StringFlag{ Name: argCloudflareAPIEmail, EnvVar: envCloudflareAPIEmail, Usage: "API email for Cloudflare provider", }, cli.StringFlag{ Name: argCloudflareAPIKey, EnvVar: envCloudflareAPIKey, Usage: "API key for Cloudflare provider", }, } func GlobalDNSCommand() cli.Command { return cli.Command{ Name: "globaldns", Usage: "Operations on global DNS providers and entries", Subcommands: []cli.Command{ { Name: "providers", Aliases: []string{"provider"}, Usage: "Operations on global DNS providers", Action: defaultAction(globalDNSProviderLs), Subcommands: []cli.Command{ { Name: "ls", Usage: "List global DNS providers", Action: globalDNSProviderLs, Flags: []cli.Flag{ formatFlag, quietFlag, }, }, { Name: "create", Usage: "Create a global DNS provider", Action: globalDNSProviderCreate, ArgsUsage: "[NAME]", Flags: append([]cli.Flag{ cli.StringFlag{ Name: argType, Usage: "Global DNS provider type, available options are \"alidns\", \"cloudflare\" and \"route53\"", }, cli.StringFlag{ Name: argRootDomain, Usage: "Set root domain of a global DNS provider", }, cli.StringSliceFlag{ Name: argMember, Usage: "Set members of the global DNS provider, can be used multiple times", }, }, globalDNSProviderCredentialFlags...), }, { Name: "update", Usage: "Update a global DNS provider", Action: globalDNSProviderUpdate, ArgsUsage: "[PROVIDER_NAME/PROVIDER_ID]", Flags: append([]cli.Flag{ cli.StringFlag{ Name: argRootDomain, Usage: "Set root domain of a global DNS provider", }, }, globalDNSProviderCredentialFlags...), }, { Name: "delete", Aliases: []string{"rm"}, Usage: "Delete a global DNS provider", Action: globalDNSProviderDelete, ArgsUsage: "[PROVIDER_NAME/PROVIDER_ID...]", }, { Name: "list-members", Aliases: []string{"lm"}, Usage: "List members of a global DNS provider", Action: listGlobalDNSProviderMembers, ArgsUsage: "[PROVIDER_NAME/PROVIDER_ID]", Flags: []cli.Flag{ formatFlag, }, }, { Name: "add-member", Usage: "Add members to a global DNS provider", Action: addGlobalDNSProviderMembers, ArgsUsage: "[PROVIDER_NAME/PROVIDER_ID, USER_NAME/USER_ID...]", }, { Name: "delete-member", Usage: "Delete members from a global DNS provider", Action: deleteGlobalDNSProviderMembers, ArgsUsage: "[PROVIDER_NAME/PROVIDER_ID, USER_NAME/USER_ID...]", }, }, }, { Name: "entries", Aliases: []string{"entry"}, Usage: "Operations on global DNS entries", Action: defaultAction(globalDNSLs), Subcommands: []cli.Command{ { Name: "ls", Usage: "List global DNS entries", Action: globalDNSLs, Flags: []cli.Flag{ formatFlag, quietFlag, }, }, { Name: "create", Usage: "Create a global DNS entry", Action: globalDNSCreate, Flags: []cli.Flag{ cli.StringFlag{ Name: argFQDN, Usage: "FQDN of a global DNS entry", }, cli.Int64Flag{ Name: argTTL, Usage: "DNS TTL in seconds", Value: 300, }, cli.StringFlag{ Name: argProvider, Usage: "Global DNS provider for an entry. Run \"rancher globaldns provider ls\" to see available ones", }, cli.StringFlag{ Name: argMultiClusterApp, Usage: "Set a multi-cluster app as the target to which a global DNS entry resolves", }, cli.StringSliceFlag{ Name: argProject, Usage: "Set projects as the target to which a global DNS entry resolves, can be used multiple times", }, cli.StringSliceFlag{ Name: argMember, Usage: "Set members of a global DNS entry, can be used multiple times", }, }, }, { Name: "update", Usage: "Update a global DNS entry", Action: globalDNSUpdate, ArgsUsage: "[ENTRY_ID]", Flags: []cli.Flag{ cli.StringFlag{ Name: argFQDN, Usage: "FQDN of a global DNS entry", }, cli.Int64Flag{ Name: argTTL, Usage: "DNS TTL in seconds", Value: 300, }, cli.StringFlag{ Name: argProvider, Usage: "Global DNS provider for an entry. Run \"rancher globaldns provider ls\" to see available ones", }, cli.StringFlag{ Name: argMultiClusterApp, Usage: "Set a multi-cluster app as the target to which a global DNS entry resolves", }, }, }, { Name: "delete", Aliases: []string{"rm"}, Usage: "Delete global DNS entries", Action: globalDNSDelete, ArgsUsage: "[ENTRY_ID...]", }, { Name: "list-members", Aliases: []string{"lm"}, Usage: "List members of a global DNS entry", Action: listGlobalDNSMembers, ArgsUsage: "[ENTRY_ID]", Flags: []cli.Flag{ formatFlag, }, }, { Name: "add-member", Usage: "Add members to a global DNS entry", Action: addGlobalDNSMembers, ArgsUsage: "[ENTRY_ID, USER_NAME/USER_ID...]", }, { Name: "delete-member", Usage: "Delete members from a global DNS entry", Action: deleteGlobalDNSMembers, ArgsUsage: "[ENTRY_ID, USER_NAME/USER_ID...]", }, { Name: "add-project", Usage: "Add target projects to a global DNS entry", Description: "If the global DNS entry uses a multi-cluster app as the target, it will remove " + "the multi-cluster app target and take the newly added projects as the target", Action: addGlobalDNSProjects, ArgsUsage: "[ENTRY_ID, PROJECT_NAME/PROJECT_ID...]", }, { Name: "delete-project", Usage: "Delete target projects from a global DNS entry", Action: deleteGlobalDNSProjects, ArgsUsage: "[ENTRY_ID, PROJECT_NAME/PROJECT_ID...]", }, }, }, }, } } func globalDNSProviderLs(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } providers, err := c.ManagementClient.GlobalDnsProvider.List(defaultListOpts(ctx)) if err != nil { return err } writer := NewTableWriter([][]string{ {"ID", "ID"}, {"NAME", "Provider.Name"}, {"ROOT_DOMAIN", "Provider.RootDomain"}, {"CREATED", "Provider.Created"}, }, ctx) defer writer.Close() for _, provider := range providers.Data { writer.Write(&globalDNSHolder{ ID: provider.ID, Provider: provider, }) } return writer.Err() } func listGlobalDNSProviderMembers(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } provider, err := searchForGlobalDNSProvider(c, ctx.Args().First()) if err != nil { return err } return outputMembers(ctx, c, provider.Members) } func globalDNSProviderCreate(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } name := ctx.Args().First() providerType := strings.ToLower(ctx.String(argType)) rootDomain := ctx.String(argRootDomain) if providerType == "" { return errors.New("--type is required") } provider := &managementClient.GlobalDnsProvider{ Name: name, RootDomain: rootDomain, } switch providerType { case route53Provider: accessKey := ctx.String(argAwsAccessKey) if accessKey == "" { return errors.New("AWS access key is required for route53 type") } secretKey := ctx.String(argAwsSecretKey) if secretKey == "" { return errors.New("AWS secret key is required for route53 type") } provider.Route53ProviderConfig = &managementClient.Route53ProviderConfig{ AccessKey: accessKey, SecretKey: secretKey, } case cloudflareProvider: apiEmail := ctx.String(argCloudflareAPIEmail) if apiEmail == "" { return errors.New("API email is required for cloudflare type") } apiKey := ctx.String(argCloudflareAPIKey) if apiKey == "" { return errors.New("API key is required for cloudflare type") } provider.CloudflareProviderConfig = &managementClient.CloudflareProviderConfig{ APIEmail: apiEmail, APIKey: apiKey, } case alidnsProvider: accessKey := ctx.String(argAlibabaCloudAccessKey) if accessKey == "" { return errors.New("Alibaba Cloud access key ID is required for alidns type") } secretKey := ctx.String(argAlibabaCloudSecretKey) if secretKey == "" { return errors.New("Alibaba Cloud access key secret is required for alidns type") } provider.AlidnsProviderConfig = &managementClient.AlidnsProviderConfig{ AccessKey: accessKey, SecretKey: secretKey, } default: return fmt.Errorf("unsupported provider type %q", providerType) } members, err := addMembersByNames(ctx, c, provider.Members, ctx.StringSlice(argMember), memberAccessTypeOwner) if err != nil { return err } provider.Members = members if _, err = c.ManagementClient.GlobalDnsProvider.Create(provider); nil != err { return err } fmt.Printf("Successfully created global DNS provider %q\n", name) return nil } func globalDNSProviderUpdate(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } name := ctx.Args().First() provider, err := searchForGlobalDNSProvider(c, name) if err != nil { return err } update := make(map[string]interface{}) if ctx.IsSet(argRootDomain) { update["rootDomain"] = ctx.String(argRootDomain) } if provider.Route53ProviderConfig != nil { accessKey := ctx.String(argAwsAccessKey) if accessKey != "" { provider.Route53ProviderConfig.AccessKey = accessKey } secretKey := ctx.String(argAwsSecretKey) if secretKey != "" { provider.Route53ProviderConfig.SecretKey = secretKey } update["route53ProviderConfig"] = provider.Route53ProviderConfig } else if provider.CloudflareProviderConfig != nil { apiEmail := ctx.String(argCloudflareAPIEmail) if apiEmail != "" { provider.CloudflareProviderConfig.APIEmail = apiEmail } apiKey := ctx.String(argCloudflareAPIKey) if apiKey != "" { provider.CloudflareProviderConfig.APIKey = apiKey } update["cloudflareProviderConfig"] = provider.CloudflareProviderConfig } else if provider.AlidnsProviderConfig != nil { accessKey := ctx.String(argAlibabaCloudAccessKey) if accessKey != "" { provider.AlidnsProviderConfig.AccessKey = accessKey } secretKey := ctx.String(argAlibabaCloudSecretKey) if secretKey != "" { provider.AlidnsProviderConfig.SecretKey = secretKey } update["alidnsProviderConfig"] = provider.AlidnsProviderConfig } else { return fmt.Errorf("unsupported provider type for %q", name) } _, err = c.ManagementClient.GlobalDnsProvider.Update(provider, update) return err } func globalDNSProviderDelete(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } for _, name := range ctx.Args() { provider, err := searchForGlobalDNSProvider(c, name) if err != nil { return err } if err = c.ManagementClient.GlobalDnsProvider.Delete(provider); nil != err { return err } } return nil } func addGlobalDNSProviderMembers(ctx *cli.Context) error { if ctx.NArg() < 2 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } provider, err := searchForGlobalDNSProvider(c, ctx.Args().First()) if err != nil { return err } members, err := addMembersByNames(ctx, c, provider.Members, ctx.Args()[1:], memberAccessTypeOwner) if err != nil { return err } update := make(map[string][]managementClient.Member) update["members"] = members _, err = c.ManagementClient.GlobalDnsProvider.Update(provider, update) return err } func deleteGlobalDNSProviderMembers(ctx *cli.Context) error { if ctx.NArg() < 2 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } provider, err := searchForGlobalDNSProvider(c, ctx.Args().First()) if err != nil { return err } members, err := deleteMembersByNames(ctx, c, provider.Members, ctx.Args()[1:]) if err != nil { return err } update := make(map[string]interface{}) update["members"] = members _, err = c.ManagementClient.GlobalDnsProvider.Update(provider, update) return err } func globalDNSLs(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } entries, err := c.ManagementClient.GlobalDns.List(defaultListOpts(ctx)) if err != nil { return err } writer := NewTableWriter([][]string{ {"ID", "ID"}, {"FQDN", "Entry.FQDN"}, {"PROVIDER_ID", "Entry.ProviderID"}, {"TARGET", "Target"}, {"CREATED", "Entry.Created"}, }, ctx) defer writer.Close() clusterCache, projectCache, err := getClusterProjectMap(ctx, c.ManagementClient) if err != nil { return err } for _, entry := range entries.Data { target, err := getEntryTarget(c, clusterCache, projectCache, &entry) if err != nil { return err } writer.Write(&globalDNSHolder{ ID: entry.ID, Entry: entry, Target: target, }) } return writer.Err() } func getEntryTarget(c *cliclient.MasterClient, clusterCache map[string]managementClient.Cluster, projectCache map[string]managementClient.Project, entry *managementClient.GlobalDns) (string, error) { var target string if entry.MultiClusterAppID != "" { _, app, err := searchForMcapp(c, entry.MultiClusterAppID) if err != nil { return "", err } target = fmt.Sprintf("Multi Cluster App: %s", app.Name) } else { var targets []managementClient.Target for _, projectID := range entry.ProjectIDs { targets = append(targets, managementClient.Target{ ProjectID: projectID, }) } targetNames := getReadableTargetNames(clusterCache, projectCache, targets) target = strings.Join(targetNames, ",") } return target, nil } func listGlobalDNSMembers(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } entry, err := searchForGlobalDNS(c, ctx.Args().First()) if err != nil { return err } return outputMembers(ctx, c, entry.Members) } func globalDNSCreate(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } fqdn := ctx.String(argFQDN) provider := ctx.String(argProvider) appName := ctx.String(argMultiClusterApp) projects := ctx.StringSlice(argProject) ttl := ctx.Int64(argTTL) if fqdn == "" { return errors.New("--fqdn is required") } if provider == "" { return errors.New("--provider is required") } if appName != "" && len(projects) > 0 || appName == "" && len(projects) == 0 { return fmt.Errorf("please specify either --multi-cluster-app or --project as the global DNS entry target") } entry := &managementClient.GlobalDns{ FQDN: fqdn, TTL: ttl, } globalDNSProvider, err := searchForGlobalDNSProvider(c, provider) if err != nil { return err } entry.ProviderID = globalDNSProvider.ID members, err := addMembersByNames(ctx, c, entry.Members, ctx.StringSlice(argMember), memberAccessTypeOwner) if err != nil { return err } entry.Members = members if appName != "" { resource, err := Lookup(c, appName, managementClient.MultiClusterAppType) if err != nil { return err } entry.MultiClusterAppID = resource.ID } else { projectIDs, err := lookupProjectIDsFromTargets(c, projects) if err != nil { return err } entry.ProjectIDs = projectIDs } if _, err = c.ManagementClient.GlobalDns.Create(entry); nil != err { return err } fmt.Printf("Successfully created global DNS entry for %q\n", fqdn) return nil } func globalDNSUpdate(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } entry, err := searchForGlobalDNS(c, ctx.Args().First()) if err != nil { return err } update := make(map[string]interface{}) if ctx.IsSet(argFQDN) { update["fqdn"] = ctx.String(argFQDN) } if ctx.IsSet(argProvider) { globalDNSProvider, err := searchForGlobalDNSProvider(c, ctx.String(argProvider)) if err != nil { return err } update["providerId"] = globalDNSProvider.ID } if ctx.IsSet(argMultiClusterApp) { resource, err := Lookup(c, ctx.String(argMultiClusterApp), managementClient.MultiClusterAppType) if err != nil { return err } update["multiClusterAppId"] = resource.ID if len(entry.ProjectIDs) > 0 { input := &managementClient.UpdateGlobalDNSTargetsInput{ ProjectIDs: entry.ProjectIDs, } if err := c.ManagementClient.GlobalDns.ActionRemoveProjects(entry, input); err != nil { return err } } } if ctx.IsSet(argTTL) { update["ttl"] = ctx.Int64(argTTL) } if _, err := c.ManagementClient.GlobalDns.Update(entry, update); err != nil { // Rollback target projects on update failure if ctx.IsSet(argMultiClusterApp) && len(entry.ProjectIDs) > 0 { input := &managementClient.UpdateGlobalDNSTargetsInput{ ProjectIDs: entry.ProjectIDs, } if rbErr := c.ManagementClient.GlobalDns.ActionAddProjects(entry, input); rbErr != nil { return fmt.Errorf("failed to update global DNS entry: %v and failed to rollback to previous target projects: %v", err, rbErr) } } return err } return nil } func globalDNSDelete(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } for _, name := range ctx.Args() { entry, err := searchForGlobalDNS(c, name) if err != nil { return err } if err = c.ManagementClient.GlobalDns.Delete(entry); nil != err { return err } } return nil } func addGlobalDNSMembers(ctx *cli.Context) error { if ctx.NArg() < 2 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } entry, err := searchForGlobalDNS(c, ctx.Args().First()) if err != nil { return err } members, err := addMembersByNames(ctx, c, entry.Members, ctx.Args()[1:], memberAccessTypeOwner) if err != nil { return err } update := make(map[string]interface{}) update["members"] = members _, err = c.ManagementClient.GlobalDns.Update(entry, update) return err } func deleteGlobalDNSMembers(ctx *cli.Context) error { if ctx.NArg() < 2 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } entry, err := searchForGlobalDNS(c, ctx.Args().First()) if err != nil { return err } members, err := deleteMembersByNames(ctx, c, entry.Members, ctx.Args()[1:]) if err != nil { return err } update := make(map[string]interface{}) update["members"] = members _, err = c.ManagementClient.GlobalDns.Update(entry, update) return err } func deleteGlobalDNSProjects(ctx *cli.Context) error { if len(ctx.Args()) < 2 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } entry, err := searchForGlobalDNS(c, ctx.Args().First()) if err != nil { return err } targets := ctx.Args()[1:] projectIDs, err := lookupProjectIDsFromTargets(c, targets) if err != nil { return err } input := &managementClient.UpdateGlobalDNSTargetsInput{ ProjectIDs: projectIDs, } return c.ManagementClient.GlobalDns.ActionRemoveProjects(entry, input) } func addGlobalDNSProjects(ctx *cli.Context) error { if len(ctx.Args()) < 2 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } entry, err := searchForGlobalDNS(c, ctx.Args().First()) if err != nil { return err } targets := ctx.Args()[1:] projectIDs, err := lookupProjectIDsFromTargets(c, targets) if err != nil { return err } update := make(map[string]interface{}) if entry.MultiClusterAppID != "" { update["multiClusterAppId"] = "" if _, err := c.ManagementClient.GlobalDns.Update(entry, update); err != nil { return err } } input := &managementClient.UpdateGlobalDNSTargetsInput{ ProjectIDs: projectIDs, } if err := c.ManagementClient.GlobalDns.ActionAddProjects(entry, input); err != nil { // Rollback target mcapp on add-project failure if entry.MultiClusterAppID != "" { update["multiClusterAppId"] = entry.MultiClusterAppID if _, rbErr := c.ManagementClient.GlobalDns.Update(entry, update); rbErr != nil { return fmt.Errorf("failed to add target projects to the entry: %v and failed to rollback to previous multi-cluster app target: %v", err, rbErr) } } return err } return nil } func searchForGlobalDNSProvider(c *cliclient.MasterClient, name string) (*managementClient.GlobalDnsProvider, error) { resource, err := Lookup(c, name, managementClient.GlobalDnsProviderType) if err != nil { return nil, err } return c.ManagementClient.GlobalDnsProvider.ByID(resource.ID) } func searchForGlobalDNS(c *cliclient.MasterClient, name string) (*managementClient.GlobalDns, error) { resource, err := Lookup(c, name, managementClient.GlobalDnsType) if err != nil { return nil, err } return c.ManagementClient.GlobalDns.ByID(resource.ID) } 07070100000015000081A400000000000000000000000165D8C5D300000745000000000000000000000000000000000000001900000000cli-2.8.3/cmd/inspect.gopackage cmd import ( "strings" "github.com/urfave/cli" ) func InspectCommand() cli.Command { return cli.Command{ Name: "inspect", Usage: "View details of resources", Description: ` Inspect resources by name or ID in the current context. If the 'type' is not specified inspect will search: ` + strings.Join(listAllRoles(), ", ") + ` Examples: # Specify the type $ rancher inspect --type cluster clusterFoo # No type is specified so defaults are checked $ rancher inspect myvolume # Inspect a project and get the output in yaml format with the projects links $ rancher inspect --type project --format yaml --links projectFoo `, ArgsUsage: "[RESOURCEID RESOURCENAME]", Action: inspectResources, Flags: []cli.Flag{ cli.BoolFlag{ Name: "links", Usage: "Include URLs to actions and links in resource output", }, cli.StringFlag{ Name: "type", Usage: "Specify the type of resource to inspect", }, cli.StringFlag{ Name: "format", Usage: "'json', 'yaml' or Custom format: '{{.kind}}'", Value: "json", }, }, } } func inspectResources(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowCommandHelp(ctx, "inspect") } c, err := GetClient(ctx) if err != nil { return err } t := ctx.String("type") types := []string{} if t != "" { rt, err := GetResourceType(c, t) if err != nil { return err } types = append(types, rt) } else { types = listAllRoles() } resource, err := Lookup(c, ctx.Args().First(), types...) if err != nil { return err } mapResource := map[string]interface{}{} err = c.ByID(resource, &mapResource) if err != nil { return err } if !ctx.Bool("links") { delete(mapResource, "links") delete(mapResource, "actions") } writer := NewTableWriter(nil, ctx) writer.Write(mapResource) writer.Close() return writer.Err() } 07070100000016000081A400000000000000000000000165D8C5D300000D68000000000000000000000000000000000000001900000000cli-2.8.3/cmd/kubectl.gopackage cmd import ( "fmt" "io/ioutil" "os" "os/exec" "strings" "github.com/rancher/norman/clientbase" client "github.com/rancher/rancher/pkg/client/generated/management/v3" "github.com/urfave/cli" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd/api" ) func KubectlCommand() cli.Command { return cli.Command{ Name: "kubectl", Usage: "Run kubectl commands", Description: "Use the current cluster context to run kubectl commands in the cluster", Action: runKubectl, SkipFlagParsing: true, } } func runKubectl(ctx *cli.Context) error { args := ctx.Args() if len(args) > 0 && (args[0] == "-h" || args[0] == "--help") { return cli.ShowCommandHelp(ctx, "kubectl") } path, err := exec.LookPath("kubectl") if err != nil { return fmt.Errorf("kubectl is required to be set in your path to use this "+ "command. See https://kubernetes.io/docs/tasks/tools/install-kubectl/ "+ "for more info. Error: %s", err.Error()) } c, err := GetClient(ctx) if err != nil { return err } config, err := loadConfig(ctx) if err != nil { return err } currentRancherServer := config.FocusedServer() if currentRancherServer == nil { return fmt.Errorf("no focused server") } currentToken := currentRancherServer.AccessKey t, err := c.ManagementClient.Token.ByID(currentToken) if err != nil { return err } currentUser := t.UserID kubeConfig, err := getKubeConfigForUser(ctx, currentUser) if err != nil { return err } var isTokenValid bool if kubeConfig != nil { tokenID, err := extractKubeconfigTokenID(*kubeConfig) if err != nil { return err } isTokenValid, err = validateToken(tokenID, c.ManagementClient.Token) if err != nil { return err } } if kubeConfig == nil || !isTokenValid { cluster, err := getClusterByID(c, c.UserConfig.FocusedCluster()) if err != nil { return err } config, err := c.ManagementClient.Cluster.ActionGenerateKubeconfig(cluster) if err != nil { return err } kubeConfigBytes := []byte(config.Config) kubeConfig, err = clientcmd.Load(kubeConfigBytes) if err != nil { return err } if err := setKubeConfigForUser(ctx, currentUser, kubeConfig); err != nil { return err } } tmpfile, err := ioutil.TempFile("", "rancher-") if err != nil { return err } defer os.Remove(tmpfile.Name()) if err := clientcmd.WriteToFile(*kubeConfig, tmpfile.Name()); err != nil { return err } if err := tmpfile.Close(); err != nil { return err } cmd := exec.Command(path, ctx.Args()...) cmd.Env = append(os.Environ(), "KUBECONFIG="+tmpfile.Name()) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Stdin = os.Stdin err = cmd.Run() if err != nil { return err } return nil } func extractKubeconfigTokenID(kubeconfig api.Config) (string, error) { if len(kubeconfig.AuthInfos) != 1 { return "", fmt.Errorf("invalid kubeconfig, expected to contain exactly 1 user") } var parts []string for _, val := range kubeconfig.AuthInfos { parts = strings.Split(val.Token, ":") if len(parts) != 2 { return "", fmt.Errorf("failed to parse kubeconfig token") } } return parts[0], nil } func validateToken(tokenID string, tokenClient client.TokenOperations) (bool, error) { token, err := tokenClient.ByID(tokenID) if err != nil { if !clientbase.IsNotFound(err) { return false, err } return false, nil } return !token.Expired, nil } 07070100000017000081A400000000000000000000000165D8C5D300003AC4000000000000000000000000000000000000001F00000000cli-2.8.3/cmd/kubectl_token.gopackage cmd import ( "bytes" "crypto" "crypto/rand" "crypto/rsa" "crypto/tls" "crypto/x509" "encoding/base64" "encoding/json" "fmt" "io" "io/ioutil" "math/big" "net/http" url2 "net/url" "os" "os/signal" "strings" "time" "github.com/rancher/cli/config" "github.com/rancher/norman/types/convert" managementClient "github.com/rancher/rancher/pkg/client/generated/management/v3" "github.com/urfave/cli" "golang.org/x/crypto/ssh/terminal" ) const deleteExample = ` Example: # Delete a cached credential $ rancher token delete cluster1_c-1234 # Delete multiple cached credentials $ rancher token delete cluster1_c-1234 cluster2_c-2345 # Delete all credentials $ rancher token delete all ` var deleteCommandUsage = fmt.Sprintf("Delete cached token used for kubectl login at [%s] \n %s", os.ExpandEnv("${HOME}/.rancher"), deleteExample) type LoginInput struct { server string userID string clusterID string authProvider string caCerts string skipVerify bool } const ( authProviderURL = "%s/v3-public/authProviders" authTokenURL = "%s/v3-public/authTokens/%s" ) var samlProviders = map[string]bool{ "pingProvider": true, "adfsProvider": true, "keyCloakProvider": true, "oktaProvider": true, "shibbolethProvider": true, } var supportedAuthProviders = map[string]bool{ "localProvider": true, "freeIpaProvider": true, "openLdapProvider": true, "activeDirectoryProvider": true, // all saml providers "pingProvider": true, "adfsProvider": true, "keyCloakProvider": true, "oktaProvider": true, "shibbolethProvider": true, } func CredentialCommand() cli.Command { return cli.Command{ Name: "token", Usage: "Authenticate and generate new kubeconfig token", Action: runCredential, Flags: []cli.Flag{ cli.StringFlag{ Name: "server", Usage: "Name of rancher server", }, cli.StringFlag{ Name: "user", Usage: "user-id", }, cli.StringFlag{ Name: "cluster", Usage: "cluster-id", }, cli.StringFlag{ Name: "auth-provider", Usage: "Name of Auth Provider to use for authentication", }, cli.StringFlag{ Name: "cacerts", Usage: "Location of CaCerts to use", }, cli.BoolFlag{ Name: "skip-verify", Usage: "Skip verification of the CACerts presented by the Server", }, }, Subcommands: []cli.Command{ cli.Command{ Name: "delete", Usage: deleteCommandUsage, Action: deleteCachedCredential, }, }, } } func runCredential(ctx *cli.Context) error { if ctx.Bool("delete") { return deleteCachedCredential(ctx) } server := ctx.String("server") if server == "" { return fmt.Errorf("name of rancher server is required") } url, err := url2.Parse(server) if err != nil { return err } if url.Scheme == "" { server = fmt.Sprintf("https://%s", server) } userID := ctx.String("user") if userID == "" { return fmt.Errorf("user-id is required") } clusterID := ctx.String("cluster") cachedCredName := fmt.Sprintf("%s_%s", userID, clusterID) cachedCred, err := loadCachedCredential(ctx, cachedCredName) if err != nil { customPrint(fmt.Errorf("LoadToken: %v", err)) } if cachedCred != nil { return json.NewEncoder(os.Stdout).Encode(cachedCred) } input := &LoginInput{ server: server, userID: userID, clusterID: clusterID, authProvider: ctx.String("auth-provider"), caCerts: ctx.String("cacerts"), skipVerify: ctx.Bool("skip-verify"), } newCred, err := loginAndGenerateCred(input) if err != nil { return err } if err := cacheCredential(ctx, newCred, fmt.Sprintf("%s_%s", userID, clusterID)); err != nil { customPrint(fmt.Errorf("CacheToken: %v", err)) } return json.NewEncoder(os.Stdout).Encode(newCred) } func deleteCachedCredential(ctx *cli.Context) error { if len(ctx.Args()) == 0 { return cli.ShowSubcommandHelp(ctx) } cf, err := loadConfig(ctx) if err != nil { return err } // dir is always set by global default. dir := ctx.GlobalString("config") if len(cf.Servers) == 0 { customPrint(fmt.Sprintf("there are no cached tokens in [%s]", dir)) return nil } if ctx.Args().First() == "all" { customPrint(fmt.Sprintf("removing cached tokens in [%s]", dir)) for _, server := range cf.Servers { server.KubeCredentials = make(map[string]*config.ExecCredential) } return cf.Write() } for _, key := range ctx.Args() { customPrint(fmt.Sprintf("removing [%s]", key)) for _, server := range cf.Servers { server.KubeCredentials[key] = nil } } return cf.Write() } func loadCachedCredential(ctx *cli.Context, key string) (*config.ExecCredential, error) { sc, err := lookupServerConfig(ctx) if err != nil { return nil, err } cred := sc.KubeToken(key) if cred == nil { return cred, nil } ts := cred.Status.ExpirationTimestamp if ts != nil && ts.Time.Before(time.Now()) { cf, err := loadConfig(ctx) if err != nil { return nil, err } cf.Servers[ctx.String("server")].KubeCredentials[key] = nil if err := cf.Write(); err != nil { return nil, err } return nil, nil } return cred, nil } // there is overlap between this and the lookupConfig() function. However, lookupConfig() requires // a server to be previously set in the Config, which might not be the case if rancher token // is run before rancher login. Perhaps we can depricate rancher token down the line and defer // all it does to login. func lookupServerConfig(ctx *cli.Context) (*config.ServerConfig, error) { server := ctx.String("server") if server == "" { return nil, fmt.Errorf("name of rancher server is required") } cf, err := loadConfig(ctx) if err != nil { return nil, err } sc := cf.Servers[server] if sc == nil { sc = &config.ServerConfig{ KubeCredentials: make(map[string]*config.ExecCredential), } cf.Servers[server] = sc if err := cf.Write(); err != nil { return nil, err } } return sc, nil } func cacheCredential(ctx *cli.Context, cred *config.ExecCredential, id string) error { // cache only if valid if cred.Status.Token == "" { return nil } server := ctx.String("server") if server == "" { return fmt.Errorf("name of rancher server is required") } cf, err := loadConfig(ctx) if err != nil { return err } sc, err := lookupServerConfig(ctx) if err != nil { return err } if sc.KubeCredentials[id] == nil { sc.KubeCredentials = make(map[string]*config.ExecCredential) } sc.KubeCredentials[id] = cred cf.Servers[server] = sc return cf.Write() } func loginAndGenerateCred(input *LoginInput) (*config.ExecCredential, error) { if input.authProvider == "" { provider, err := getAuthProvider(input.server) if err != nil { return nil, err } input.authProvider = provider } tlsConfig, err := getTLSConfig(input) if err != nil { return nil, err } token := managementClient.Token{} if samlProviders[input.authProvider] { token, err = samlAuth(input, tlsConfig) if err != nil { return nil, err } } else { customPrint(fmt.Sprintf("Enter credentials for %s \n", input.authProvider)) token, err = basicAuth(input, tlsConfig) if err != nil { return nil, err } } cred := &config.ExecCredential{ TypeMeta: config.TypeMeta{ Kind: "ExecCredential", APIVersion: "client.authentication.k8s.io/v1beta1", }, Status: &config.ExecCredentialStatus{}, } cred.Status.Token = token.Token if token.ExpiresAt == "" { return cred, nil } ts, err := time.Parse(time.RFC3339, token.ExpiresAt) if err != nil { customPrint(fmt.Sprintf("\n error parsing time %s %v", token.ExpiresAt, err)) return nil, err } cred.Status.ExpirationTimestamp = &config.Time{Time: ts} return cred, nil } func basicAuth(input *LoginInput, tlsConfig *tls.Config) (managementClient.Token, error) { token := managementClient.Token{} username, err := customPrompt("username", true) if err != nil { return token, err } password, err := customPrompt("password", false) if err != nil { return token, err } responseType := "kubeconfig" if input.clusterID != "" { responseType = fmt.Sprintf("%s_%s", responseType, input.clusterID) } body := fmt.Sprintf(`{"responseType":%q, "username":%q, "password":%q}`, responseType, username, password) url := fmt.Sprintf("%s/v3-public/%ss/%s?action=login", input.server, input.authProvider, strings.ToLower(strings.Replace(input.authProvider, "Provider", "", 1))) response, err := request(http.MethodPost, url, bytes.NewBufferString(body)) if err != nil { return token, nil } apiError := map[string]interface{}{} err = json.Unmarshal(response, &apiError) if err != nil { return token, err } if responseType := apiError["type"]; responseType == "error" { return token, fmt.Errorf("error logging in: code: "+ "[%v] message:[%v]", apiError["code"], apiError["message"]) } err = json.Unmarshal(response, &token) if err != nil { return token, err } return token, nil } func samlAuth(input *LoginInput, tlsConfig *tls.Config) (managementClient.Token, error) { token := managementClient.Token{} privateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return token, err } publicKey := privateKey.PublicKey marshalKey, err := json.Marshal(publicKey) if err != nil { return token, err } encodedKey := base64.StdEncoding.EncodeToString(marshalKey) id, err := generateKey() if err != nil { return token, err } responseType := "kubeconfig" if input.clusterID != "" { responseType = fmt.Sprintf("%s_%s", responseType, input.clusterID) } tokenURL := fmt.Sprintf(authTokenURL, input.server, id) req, err := http.NewRequest(http.MethodGet, tokenURL, bytes.NewBuffer(nil)) if err != nil { return token, err } req.Header.Set("content-type", "application/json") req.Header.Set("accept", "application/json") tr := &http.Transport{ TLSClientConfig: tlsConfig, } client := &http.Client{Transport: tr, Timeout: 300 * time.Second} loginRequest := fmt.Sprintf("%s/dashboard/auth/login?requestId=%s&publicKey=%s&responseType=%s", input.server, id, encodedKey, responseType) customPrint(fmt.Sprintf("\nLogin to Rancher Server at %s \n", loginRequest)) interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt) // timeout for user to login and get token timeout := time.NewTicker(15 * time.Minute) defer timeout.Stop() poll := time.NewTicker(10 * time.Second) defer poll.Stop() for { select { case <-poll.C: res, err := client.Do(req) if err != nil { return token, err } content, err := ioutil.ReadAll(res.Body) if err != nil { res.Body.Close() return token, err } res.Body.Close() err = json.Unmarshal(content, &token) if err != nil { return token, err } if token.Token == "" { continue } decoded, err := base64.StdEncoding.DecodeString(token.Token) if err != nil { return token, err } decryptedBytes, err := privateKey.Decrypt(nil, decoded, &rsa.OAEPOptions{Hash: crypto.SHA256}) if err != nil { panic(err) } token.Token = string(decryptedBytes) // delete token req, err = http.NewRequest(http.MethodDelete, tokenURL, bytes.NewBuffer(nil)) if err != nil { return token, err } req.Header.Set("content-type", "application/json") req.Header.Set("accept", "application/json") tr := &http.Transport{ TLSClientConfig: tlsConfig, } client = &http.Client{Transport: tr, Timeout: 150 * time.Second} res, err = client.Do(req) if err != nil { // log error and use the token if login succeeds customPrint(fmt.Errorf("DeleteToken: %v", err)) } return token, nil case <-timeout.C: break case <-interrupt: customPrint("received interrupt") break } return token, nil } } func getAuthProviders(server string) (map[string]string, error) { authProviders := fmt.Sprintf(authProviderURL, server) customPrint(authProviders) response, err := request(http.MethodGet, authProviders, nil) data := map[string]interface{}{} err = json.Unmarshal(response, &data) if err != nil { return nil, err } providers := map[string]string{} i := 0 for _, value := range convert.ToMapSlice(data["data"]) { provider := convert.ToString(value["type"]) if provider != "" && supportedAuthProviders[provider] { providers[fmt.Sprintf("%v", i)] = provider i++ } } return providers, err } func getAuthProvider(server string) (string, error) { authProviders, err := getAuthProviders(server) if err != nil || authProviders == nil { return "", err } if len(authProviders) == 0 { return "", fmt.Errorf("no auth provider configured") } if len(authProviders) == 1 { return authProviders["0"], nil } try := 0 var providers []string for key, val := range authProviders { providers = append(providers, fmt.Sprintf("%s - %s", key, val)) } for try < 3 { provider, err := customPrompt(fmt.Sprintf("auth provider\n%v", strings.Join(providers, "\n")), true) if err != nil { try++ continue } if _, ok := authProviders[provider]; !ok { customPrint("pick valid auth provider") try++ continue } provider = authProviders[provider] return provider, nil } return "", fmt.Errorf("invalid auth provider") } func generateKey() (string, error) { characters := "abcdfghjklmnpqrstvwxz12456789" tokenLength := 32 token := make([]byte, tokenLength) for i := range token { r, err := rand.Int(rand.Reader, big.NewInt(int64(len(characters)))) if err != nil { return "", err } token[i] = characters[r.Int64()] } return string(token), nil } func getTLSConfig(input *LoginInput) (*tls.Config, error) { config := &tls.Config{} if input.skipVerify || input.caCerts == "" { config = &tls.Config{ InsecureSkipVerify: true, } return config, nil } if input.caCerts != "" { cert, err := loadAndVerifyCert(input.caCerts) if err != nil { return nil, err } roots := x509.NewCertPool() ok := roots.AppendCertsFromPEM([]byte(cert)) if !ok { return nil, err } config.RootCAs = roots } return config, nil } func request(method, url string, body io.Reader) ([]byte, error) { var response []byte var client *http.Client req, err := http.NewRequest(method, url, body) if err != nil { return response, err } tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } client = &http.Client{Transport: tr} res, err := client.Do(req) if err != nil { return nil, err } defer res.Body.Close() response, err = ioutil.ReadAll(res.Body) if err != nil { return nil, err } return response, nil } func customPrompt(field string, show bool) (result string, err error) { fmt.Fprintf(os.Stderr, "Enter %s: ", field) if show { _, err = fmt.Fscan(os.Stdin, &result) } else { var data []byte data, err = terminal.ReadPassword(int(os.Stdin.Fd())) result = string(data) fmt.Fprintf(os.Stderr, "\n") } return result, err } func customPrint(data interface{}) { fmt.Fprintf(os.Stderr, "%v \n", data) } 07070100000018000081A400000000000000000000000165D8C5D3000020FA000000000000000000000000000000000000001700000000cli-2.8.3/cmd/login.gopackage cmd import ( "bufio" "crypto/tls" "encoding/json" "errors" "fmt" "io/ioutil" "net/http" "net/url" "os" "strconv" "strings" "github.com/sirupsen/logrus" "github.com/grantae/certinfo" "github.com/rancher/cli/cliclient" "github.com/rancher/cli/config" managementClient "github.com/rancher/rancher/pkg/client/generated/management/v3" "github.com/urfave/cli" ) type LoginData struct { Project managementClient.Project Index int ClusterName string } type CACertResponse struct { Name string `json:"name"` Value string `json:"value"` } func LoginCommand() cli.Command { return cli.Command{ Name: "login", Aliases: []string{"l"}, Usage: "Login to a Rancher server", Action: loginSetup, ArgsUsage: "[SERVERURL]", Flags: []cli.Flag{ cli.StringFlag{ Name: "context", Usage: "Set the context during login", }, cli.StringFlag{ Name: "token,t", Usage: "Token from the Rancher UI", }, cli.StringFlag{ Name: "cacert", Usage: "Location of the CACerts to use", }, cli.StringFlag{ Name: "name", Usage: "Name of the Server", }, cli.BoolFlag{ Name: "skip-verify", Usage: "Skip verification of the CACerts presented by the Server", }, }, } } func loginSetup(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowCommandHelp(ctx, "login") } cf, err := loadConfig(ctx) if err != nil { return err } serverName := ctx.String("name") if serverName == "" { serverName = "rancherDefault" } serverConfig := &config.ServerConfig{} // Validate the url and drop the path u, err := url.ParseRequestURI(ctx.Args().First()) if err != nil { return fmt.Errorf("Failed to parse SERVERURL (%s), make sure it is a valid HTTPS URL (e.g. https://rancher.yourdomain.com or https://1.1.1.1). Error: %s", ctx.Args().First(), err) } u.Path = "" serverConfig.URL = u.String() if ctx.String("token") != "" { auth := SplitOnColon(ctx.String("token")) if len(auth) != 2 { return errors.New("invalid token") } serverConfig.AccessKey = auth[0] serverConfig.SecretKey = auth[1] serverConfig.TokenKey = ctx.String("token") } else { // This can be removed once username and password is accepted return errors.New("token flag is required") } if ctx.String("cacert") != "" { cert, err := loadAndVerifyCert(ctx.String("cacert")) if err != nil { return err } serverConfig.CACerts = cert } c, err := cliclient.NewManagementClient(serverConfig) if err != nil { if _, ok := err.(*url.Error); ok && strings.Contains(err.Error(), "certificate signed by unknown authority") { // no cert was provided and it's most likely a self signed cert if // we get here so grab the cacert and see if the user accepts the server c, err = getCertFromServer(ctx, serverConfig) if err != nil { return err } } else { return err } } proj, err := getProjectContext(ctx, c) if err != nil { return err } // Set the default server and proj for the user serverConfig.Project = proj cf.CurrentServer = serverName cf.Servers[serverName] = serverConfig err = cf.Write() if err != nil { return err } return nil } func getProjectContext(ctx *cli.Context, c *cliclient.MasterClient) (string, error) { // If context is given if ctx.String("context") != "" { context := ctx.String("context") // Check if given context is in valid format _, _, err := parseClusterAndProjectID(context) if err != nil { return "", fmt.Errorf("Unable to parse context (%s). Please provide context as local:p-xxxxx, c-xxxxx:p-xxxxx, c-xxxxx:project-xxxxx, c-m-xxxxxxxx:p-xxxxx or c-m-xxxxxxxx:project-xxxxx", context) } // Check if context exists _, err = Lookup(c, context, "project") if err != nil { return "", fmt.Errorf("Unable to find context (%s). Make sure the context exists and you have permissions to use it. Error: %s", context, err) } return context, nil } projectCollection, err := c.ManagementClient.Project.List(defaultListOpts(ctx)) if err != nil { return "", err } projDataLen := len(projectCollection.Data) if projDataLen == 0 { logrus.Warn("No projects found, context could not be set. Please create a project and run `rancher login` again.") return "", nil } if projDataLen == 1 { logrus.Infof("Only 1 project available: %s", projectCollection.Data[0].Name) return projectCollection.Data[0].ID, nil } if projDataLen == 2 { var sysProj bool var defaultID string for _, proj := range projectCollection.Data { if proj.Name == "Default" { defaultID = proj.ID } if proj.Name == "System" { sysProj = true } if sysProj && defaultID != "" { return defaultID, nil } } } clusterNames, err := getClusterNames(ctx, c) if err != nil { return "", err } writer := NewTableWriter([][]string{ {"NUMBER", "Index"}, {"CLUSTER NAME", "ClusterName"}, {"PROJECT ID", "Project.ID"}, {"PROJECT NAME", "Project.Name"}, {"PROJECT DESCRIPTION", "Project.Description"}, }, ctx) for i, item := range projectCollection.Data { writer.Write(&LoginData{ Project: item, Index: i + 1, ClusterName: clusterNames[item.ClusterID], }) } writer.Close() if nil != writer.Err() { return "", writer.Err() } fmt.Print("Select a Project:") reader := bufio.NewReader(os.Stdin) errMessage := fmt.Sprintf("Invalid input, enter a number between 1 and %v: ", len(projectCollection.Data)) var selection int for { input, err := reader.ReadString('\n') if err != nil { return "", err } input = strings.TrimSpace(input) if input != "" { i, err := strconv.Atoi(input) if err != nil { fmt.Print(errMessage) continue } if i <= len(projectCollection.Data) && i != 0 { selection = i - 1 break } fmt.Print(errMessage) continue } } return projectCollection.Data[selection].ID, nil } func getCertFromServer(ctx *cli.Context, cf *config.ServerConfig) (*cliclient.MasterClient, error) { req, err := http.NewRequest("GET", cf.URL+"/v3/settings/cacerts", nil) if err != nil { return nil, err } req.SetBasicAuth(cf.AccessKey, cf.SecretKey) tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } client := &http.Client{Transport: tr} res, err := client.Do(req) if err != nil { return nil, err } defer res.Body.Close() content, err := ioutil.ReadAll(res.Body) if err != nil { return nil, err } var certReponse *CACertResponse err = json.Unmarshal(content, &certReponse) if err != nil { return nil, fmt.Errorf("Unable to parse response from %s/v3/settings/cacerts\nError: %s\nResponse:\n%s", cf.URL, err, content) } cert, err := verifyCert([]byte(certReponse.Value)) if err != nil { return nil, err } // Get the server cert chain in a printable form serverCerts, err := processServerChain(res) if err != nil { return nil, err } if !ctx.Bool("skip-verify") { if ok := verifyUserAcceptsCert(serverCerts, cf.URL); !ok { return nil, errors.New("CACert of server was not accepted, unable to login") } } cf.CACerts = cert return cliclient.NewManagementClient(cf) } func verifyUserAcceptsCert(certs []string, url string) bool { fmt.Printf("The authenticity of server '%s' can't be established.\n", url) fmt.Printf("Cert chain is : %v \n", certs) fmt.Print("Do you want to continue connecting (yes/no)? ") scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { input := scanner.Text() input = strings.ToLower(strings.TrimSpace(input)) if input == "yes" || input == "y" { return true } else if input == "no" || input == "n" { return false } fmt.Printf("Please type 'yes' or 'no': ") } return false } func processServerChain(res *http.Response) ([]string, error) { var allCerts []string for _, cert := range res.TLS.PeerCertificates { result, err := certinfo.CertificateText(cert) if err != nil { return allCerts, err } allCerts = append(allCerts, result) } return allCerts, nil } func loginContext(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } cluster, err := getClusterByID(c, c.UserConfig.FocusedCluster()) if err != nil { return err } clusterName := getClusterName(cluster) project, err := getProjectByID(c, c.UserConfig.Project) if err != nil { return err } fmt.Printf("Cluster:%s Project:%s\n", clusterName, project.Name) return nil } 07070100000019000081A400000000000000000000000165D8C5D300000B36000000000000000000000000000000000000001900000000cli-2.8.3/cmd/machine.gopackage cmd import ( "fmt" "github.com/rancher/cli/cliclient" capiClient "github.com/rancher/rancher/pkg/client/generated/cluster/v1alpha4" "github.com/urfave/cli" ) type MachineData struct { ID string Machine capiClient.Machine Name string } func MachineCommand() cli.Command { return cli.Command{ Name: "machines", Aliases: []string{"machine"}, Usage: "Operations on machines", Action: defaultAction(machineLs), Subcommands: []cli.Command{ { Name: "ls", Usage: "List machines", Description: "\nLists all machines in the current cluster.", ArgsUsage: "None", Action: machineLs, Flags: []cli.Flag{ cli.StringFlag{ Name: "format", Usage: "'json', 'yaml' or Custom format: '{{.Machine.ID}} {{.Machine.Name}}'", }, quietFlag, }, }, }, } } func machineLs(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } collection, err := getMachinesList(ctx, c) if err != nil { return err } writer := NewTableWriter([][]string{ {"ID", "ID"}, {"NAME", "Name"}, {"PHASE", "Machine.Status.Phase"}, }, ctx) defer writer.Close() for _, item := range collection.Data { writer.Write(&MachineData{ ID: item.ID, Machine: item, Name: getMachineName(item), }) } return writer.Err() } func getMachinesList( ctx *cli.Context, c *cliclient.MasterClient, ) (*capiClient.MachineCollection, error) { filter := defaultListOpts(ctx) return c.CAPIClient.Machine.List(filter) } func getMachineByNodeName( ctx *cli.Context, c *cliclient.MasterClient, nodeName string, ) (capiClient.Machine, error) { machineCollection, err := getMachinesList(ctx, c) if err != nil { return capiClient.Machine{}, err } for _, machine := range machineCollection.Data { if machine.Status.NodeRef != nil && machine.Status.NodeRef.Name == nodeName { return machine, nil } } return capiClient.Machine{}, fmt.Errorf("no machine found with associated to node [%s], run "+ "`rancher machines` to see available nodes", nodeName) } func getMachineByID( ctx *cli.Context, c *cliclient.MasterClient, machineID string, ) (capiClient.Machine, error) { machineCollection, err := getMachinesList(ctx, c) if err != nil { return capiClient.Machine{}, err } for _, machine := range machineCollection.Data { if machine.ID == machineID { return machine, nil } } return capiClient.Machine{}, fmt.Errorf("no machine found with the ID [%s], run "+ "`rancher machines` to see available nodes", machineID) } func getMachineName(machine capiClient.Machine) string { if machine.Name != "" { return machine.Name } else if machine.Status.NodeRef != nil { return machine.Status.NodeRef.Name } else if machine.InfrastructureRef != nil { return machine.InfrastructureRef.Name } return machine.ID } 0707010000001A000081A400000000000000000000000165D8C5D30000950F000000000000000000000000000000000000002100000000cli-2.8.3/cmd/multiclusterapp.gopackage cmd import ( "fmt" "reflect" "sort" "strings" "time" "github.com/rancher/cli/cliclient" "github.com/rancher/norman/types" "github.com/rancher/norman/types/slice" managementClient "github.com/rancher/rancher/pkg/client/generated/management/v3" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) const ( installMultiClusterAppDescription = ` Install a multi-cluster app in the current Rancher server. This defaults to the newest version of the app template. Specify a version using '--version' if required. Example: # Install the redis template with no other options $ rancher multiclusterapp install redis appFoo # Install the redis template and specify an answers file location $ rancher multiclusterapp install --answers /example/answers.yaml redis appFoo # Install the redis template and set multiple answers and the version to install $ rancher multiclusterapp install --set foo=bar --set-string baz=bunk --version 1.0.1 redis appFoo # Install the redis template and set target projects to install $ rancher multiclusterapp install --target mycluster:Default --target c-98pjr:p-w6c5f redis appFoo # Block cli until installation has finished or encountered an error. Use after multiclusterapp install. $ rancher wait <multiclusterapp-id> ` upgradeStrategySimultaneously = "simultaneously" upgradeStrategyRollingUpdate = "rolling-update" argUpgradeStrategy = "upgrade-strategy" argUpgradeBatchSize = "upgrade-batch-size" argUpgradeBatchInterval = "upgrade-batch-interval" ) var ( memberAccessTypes = []string{"owner", "member", "read-only"} upgradeStrategies = []string{upgradeStrategySimultaneously, upgradeStrategyRollingUpdate} ) type MultiClusterAppData struct { ID string App managementClient.MultiClusterApp Version string Targets string } type scopeAnswers struct { Answers map[string]string AnswersSetString map[string]string } func MultiClusterAppCommand() cli.Command { appLsFlags := []cli.Flag{ formatFlag, cli.BoolFlag{ Name: "quiet,q", Usage: "Only display IDs", }, } return cli.Command{ Name: "multiclusterapps", Aliases: []string{"multiclusterapp", "mcapps", "mcapp"}, Usage: "Operations with multi-cluster apps", Action: defaultAction(multiClusterAppLs), Flags: appLsFlags, Subcommands: []cli.Command{ cli.Command{ Name: "ls", Usage: "List multi-cluster apps", Description: "\nList all multi-cluster apps in the current Rancher server", ArgsUsage: "None", Action: multiClusterAppLs, Flags: appLsFlags, }, cli.Command{ Name: "delete", Usage: "Delete a multi-cluster app", Action: multiClusterAppDelete, ArgsUsage: "[APP_NAME]", }, cli.Command{ Name: "install", Usage: "Install a multi-cluster app", Description: installMultiClusterAppDescription, Action: multiClusterAppTemplateInstall, ArgsUsage: "[TEMPLATE_NAME, APP_NAME]...", Flags: []cli.Flag{ cli.StringFlag{ Name: "answers,a", Usage: "Path to an answers file, the format of the file is a map with key:value. This supports JSON and YAML.", }, cli.StringFlag{ Name: "values", Usage: "Path to a helm values file.", }, cli.StringSliceFlag{ Name: "set", Usage: "Set answers for the template, can be used multiple times. You can set overriding answers for specific clusters or projects " + "by providing cluster ID or project ID as the prefix. Example: --set foo=bar --set c-rvcrl:foo=bar --set c-rvcrl:p-8w2x8:foo=bar", }, cli.StringSliceFlag{ Name: "set-string", Usage: "Set string answers for the template (Skips Helm's type conversion), can be used multiple times. You can set overriding answers for specific clusters or projects " + "by providing cluster ID or project ID as the prefix. Example: --set-string foo=bar --set-string c-rvcrl:foo=bar --set-string c-rvcrl:p-8w2x8:foo=bar", }, cli.StringFlag{ Name: "version", Usage: "Version of the template to use", }, cli.BoolFlag{ Name: "no-prompt", Usage: "Suppress asking questions and use the default values when required answers are not provided", }, cli.StringSliceFlag{ Name: "target,t", Usage: "Target project names/ids to install the app into", }, cli.StringSliceFlag{ Name: "role", Usage: "Set roles required to launch/manage the apps in target projects. For example, set \"project-member\" role when the app needs to manage resources " + "in the projects in which it is deployed. Or set \"cluster-owner\" role when the app needs to manage resources in the clusters in which it is deployed. " + "(default: \"project-member\")", }, cli.StringSliceFlag{ Name: "member", Usage: "Set members of the app, with the same access type defined by --member-access-type", }, cli.StringFlag{ Name: "member-access-type", Usage: "Access type of the members. Specify only one value, and it applies to all members defined by --member. Valid options are 'owner', 'member' and 'read-only'", Value: "owner", }, cli.StringFlag{ Name: argUpgradeStrategy, Usage: "Strategy for upgrade. Valid options are \"rolling-update\" and \"simultaneously\"", Value: upgradeStrategySimultaneously, }, cli.Int64Flag{ Name: argUpgradeBatchSize, Usage: "The number of apps in target projects to be upgraded at a time. Only used if --upgrade-strategy is rolling-update.", Value: 1, }, cli.Int64Flag{ Name: argUpgradeBatchInterval, Usage: "The number of seconds between updating the next app during upgrade. Only used if --upgrade-strategy is rolling-update.", Value: 1, }, cli.IntFlag{ Name: "helm-timeout", Usage: "Amount of time for helm to wait for k8s commands (default is 300 secs). Example: --helm-timeout 600", Value: 300, }, cli.BoolFlag{ Name: "helm-wait", Usage: "Helm will wait for as long as timeout value, for installed resources to be ready (pods, PVCs, deployments, etc.). Example: --helm-wait", }, }, }, cli.Command{ Name: "rollback", Usage: "Rollback a multi-cluster app to a previous version", Action: multiClusterAppRollback, ArgsUsage: "[APP_NAME/APP_ID, REVISION_ID/REVISION_NAME]", Flags: []cli.Flag{ cli.BoolFlag{ Name: "show-revisions,r", Usage: "Show revisions available to rollback to", }, }, }, cli.Command{ Name: "upgrade", Usage: "Upgrade an app to a newer version", Action: multiClusterAppUpgrade, ArgsUsage: "[APP_NAME/APP_ID VERSION]", Flags: []cli.Flag{ cli.StringFlag{ Name: "answers,a", Usage: "Path to an answers file, the format of the file is a map with key:value. Supports JSON and YAML", }, cli.StringFlag{ Name: "values", Usage: "Path to a helm values file.", }, cli.StringSliceFlag{ Name: "set", Usage: "Set answers for the template, can be used multiple times. You can set overriding answers for specific clusters or projects " + "by providing cluster ID or project ID as the prefix. Example: --set foo=bar --set c-rvcrl:foo=bar --set c-rvcrl:p-8w2x8:foo=bar", }, cli.StringSliceFlag{ Name: "set-string", Usage: "Set string answers for the template (Skips Helm's type conversion), can be used multiple times. You can set overriding answers for specific clusters or projects " + "by providing cluster ID or project ID as the prefix. Example: --set-string foo=bar --set-string c-rvcrl:foo=bar --set-string c-rvcrl:p-8w2x8:foo=bar", }, cli.BoolFlag{ Name: "reset", Usage: "Reset all catalog app answers", }, cli.StringSliceFlag{ Name: "role,r", Usage: "Set roles required to launch/manage the apps in target projects. Specified roles on upgrade will override all the original roles. " + "For example, provide all existing roles if you want to add additional roles. Leave it empty to keep current roles", }, cli.BoolFlag{ Name: "show-versions,v", Usage: "Display versions available to upgrade to", }, cli.StringFlag{ Name: argUpgradeStrategy, Usage: "Strategy for upgrade. Valid options are \"rolling-update\" and \"simultaneously\"", }, cli.Int64Flag{ Name: argUpgradeBatchSize, Usage: "The number of apps in target projects to be upgraded at a time. Only used if --upgrade-strategy is rolling-update.", }, cli.Int64Flag{ Name: argUpgradeBatchInterval, Usage: "The number of seconds between updating the next app during upgrade. Only used if --upgrade-strategy is rolling-update.", }, }, }, cli.Command{ Name: "add-project", Usage: "Add target projects to a multi-cluster app", Action: addMcappTargetProject, Description: "Examples:\n #Add 'p1' project in cluster 'mycluster' to target projects of a multi-cluster app named 'myapp'\n rancher multiclusterapp add-project myapp mycluster:p1\n", ArgsUsage: "[APP_NAME/APP_ID, CLUSTER_NAME:PROJECT_NAME/PROJECT_ID...]", Flags: []cli.Flag{ cli.StringFlag{ Name: "answers,a", Usage: "Path to an answers file that provides overriding answers for the new target projects, the format of the file is a map with key:value. Supports JSON and YAML", }, cli.StringFlag{ Name: "values", Usage: "Path to a helm values file that provides overriding answers for the new target projects", }, cli.StringSliceFlag{ Name: "set", Usage: "Set overriding answers for the new target projects", }, cli.StringSliceFlag{ Name: "set-string", Usage: "Set overriding string answers for the new target projects", }, }, }, cli.Command{ Name: "delete-project", Usage: "Delete target projects from a multi-cluster app", Action: deleteMcappTargetProject, Description: "Examples:\n #Delete 'p1' project in cluster 'mycluster' from target projects of a multi-cluster app named 'myapp'\n rancher multiclusterapp delete-project myapp mycluster:p1\n", ArgsUsage: "[APP_NAME/APP_ID, CLUSTER_NAME:PROJECT_NAME/PROJECT_ID...]", }, cli.Command{ Name: "add-member", Usage: "Add members to a multi-cluster app", Action: addMcappMember, Description: "Examples:\n #Add 'user1' and 'user2' as the owners of a multi-cluster app named 'myapp'\n rancher multiclusterapp add-member myapp owner user1 user2\n", ArgsUsage: "[APP_NAME/APP_ID, ACCESS_TYPE, USER_NAME/USER_ID...]", }, cli.Command{ Name: "delete-member", Usage: "Delete members from a multi-cluster app", Action: deleteMcappMember, Description: "Examples:\n #Delete the membership of a user named 'user1' from a multi-cluster app named 'myapp'\n rancher multiclusterapp delete-member myapp user1\n", ArgsUsage: "[APP_NAME/APP_ID, USER_NAME/USER_ID...]", }, cli.Command{ Name: "list-members", Aliases: []string{"lm"}, Usage: "List current members of a multi-cluster app", ArgsUsage: "[APP_NAME/APP_ID]", Action: listMultiClusterAppMembers, Flags: []cli.Flag{ formatFlag, }, }, cli.Command{ Name: "list-answers", Aliases: []string{"la"}, Usage: "List current answers of a multi-cluster app", ArgsUsage: "[APP_NAME/APP_ID]", Action: listMultiClusterAppAnswers, Flags: []cli.Flag{ formatFlag, }, }, cli.Command{ Name: "list-templates", Aliases: []string{"lt"}, Usage: "List templates available for installation", Description: "\nList all app templates in the current Rancher server", ArgsUsage: "None", Action: globalTemplateLs, Flags: []cli.Flag{ formatFlag, cli.StringFlag{ Name: "catalog", Usage: "Specify the catalog to list templates for", }, }, }, cli.Command{ Name: "show-template", Aliases: []string{"st"}, Usage: "Show versions available to install for an app template", Description: "\nShow all available versions of an app template", ArgsUsage: "[TEMPLATE_ID]", Action: templateShow, }, cli.Command{ Name: "show-app", Aliases: []string{"sa"}, Usage: "Show an app's available versions and revisions", ArgsUsage: "[APP_NAME/APP_ID]", Action: showMultiClusterApp, Flags: []cli.Flag{ formatFlag, cli.BoolFlag{ Name: "show-roles", Usage: "Show roles required to manage the app", }, }, }, }, } } func multiClusterAppLs(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } collection, err := c.ManagementClient.MultiClusterApp.List(defaultListOpts(ctx)) writer := NewTableWriter([][]string{ {"ID", "ID"}, {"NAME", "App.Name"}, {"STATE", "App.State"}, {"VERSION", "Version"}, {"TARGET_PROJECTS", "Targets"}, }, ctx) defer writer.Close() clusterCache, projectCache, err := getClusterProjectMap(ctx, c.ManagementClient) if err != nil { return err } templateVersionCache := make(map[string]string) for _, item := range collection.Data { version, err := getTemplateVersion(c.ManagementClient, templateVersionCache, item.TemplateVersionID) if err != nil { return err } targetNames := getReadableTargetNames(clusterCache, projectCache, item.Targets) writer.Write(&MultiClusterAppData{ ID: item.ID, App: item, Version: version, Targets: strings.Join(targetNames, ","), }) } return writer.Err() } func getTemplateVersion(client *managementClient.Client, templateVersionCache map[string]string, ID string) (string, error) { var version string if cachedVersion, ok := templateVersionCache[ID]; ok { version = cachedVersion } else { templateVersion, err := client.TemplateVersion.ByID(ID) if err != nil { return "", err } templateVersionCache[templateVersion.ID] = templateVersion.Version version = templateVersion.Version } return version, nil } func getClusterProjectMap(ctx *cli.Context, client *managementClient.Client) (map[string]managementClient.Cluster, map[string]managementClient.Project, error) { clusters := make(map[string]managementClient.Cluster) clusterCollectionData, err := listAllClusters(ctx, client) if err != nil { return nil, nil, err } for _, c := range clusterCollectionData { clusters[c.ID] = c } projects := make(map[string]managementClient.Project) projectCollectionData, err := listAllProjects(ctx, client) if err != nil { return nil, nil, err } for _, p := range projectCollectionData { projects[p.ID] = p } return clusters, projects, nil } func listAllClusters(ctx *cli.Context, client *managementClient.Client) ([]managementClient.Cluster, error) { clusterCollection, err := client.Cluster.List(defaultListOpts(ctx)) if err != nil { return nil, err } clusterCollectionData := clusterCollection.Data for { clusterCollection, err = clusterCollection.Next() if err != nil { return nil, err } if clusterCollection == nil { break } clusterCollectionData = append(clusterCollectionData, clusterCollection.Data...) if !clusterCollection.Pagination.Partial { break } } return clusterCollectionData, nil } func listAllProjects(ctx *cli.Context, client *managementClient.Client) ([]managementClient.Project, error) { projectCollection, err := client.Project.List(defaultListOpts(ctx)) if err != nil { return nil, err } projectCollectionData := projectCollection.Data for { projectCollection, err = projectCollection.Next() if err != nil { return nil, err } if projectCollection == nil { break } projectCollectionData = append(projectCollectionData, projectCollection.Data...) if !projectCollection.Pagination.Partial { break } } return projectCollectionData, nil } func getReadableTargetNames(clusterCache map[string]managementClient.Cluster, projectCache map[string]managementClient.Project, targets []managementClient.Target) []string { var targetNames []string for _, target := range targets { projectID := target.ProjectID clusterID, _ := parseScope(projectID) cluster, ok := clusterCache[clusterID] if !ok { logrus.Debugf("Cannot get readable name for target %q, showing ID", target.ProjectID) targetNames = append(targetNames, target.ProjectID) continue } project, ok := projectCache[projectID] if !ok { logrus.Debugf("Cannot get readable name for target %q, showing ID", target.ProjectID) targetNames = append(targetNames, target.ProjectID) continue } targetNames = append(targetNames, concatScope(cluster.Name, project.Name)) } return targetNames } func multiClusterAppDelete(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } for _, name := range ctx.Args() { _, app, err := searchForMcapp(c, name) if err != nil { return err } err = c.ManagementClient.MultiClusterApp.Delete(app) if err != nil { return err } } return nil } func multiClusterAppUpgrade(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } if ctx.Bool("show-versions") { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } _, app, err := searchForMcapp(c, ctx.Args().First()) if err != nil { return err } return outputMultiClusterAppVersions(ctx, c, app) } if ctx.NArg() != 2 { return cli.ShowSubcommandHelp(ctx) } upgradeStrategy := strings.ToLower(ctx.String(argUpgradeStrategy)) if ctx.IsSet(argUpgradeStrategy) && !slice.ContainsString(upgradeStrategies, upgradeStrategy) { return fmt.Errorf("invalid upgrade-strategy %q, supported values are \"rolling-update\" and \"simultaneously\"", upgradeStrategy) } _, app, err := searchForMcapp(c, ctx.Args().First()) if err != nil { return err } update := make(map[string]interface{}) answers, answersSetString := fromMultiClusterAppAnswers(app.Answers) answers, answersSetString, err = processAnswerUpdates(ctx, answers, answersSetString) if err != nil { return err } update["answers"], err = toMultiClusterAppAnswers(c, answers, answersSetString) if err != nil { return err } version := ctx.Args().Get(1) templateVersion, err := c.ManagementClient.TemplateVersion.ByID(app.TemplateVersionID) if err != nil { return err } toUpgradeTemplateversionID := strings.TrimSuffix(templateVersion.ID, templateVersion.Version) + version // Check if the template version is valid before applying it _, err = c.ManagementClient.TemplateVersion.ByID(toUpgradeTemplateversionID) if err != nil { templateName := strings.TrimSuffix(toUpgradeTemplateversionID, "-"+version) return fmt.Errorf( "version %s for template %s is invalid, run 'rancher mcapp show-template %s' for available versions", version, templateName, templateName, ) } update["templateVersionId"] = toUpgradeTemplateversionID roles := ctx.StringSlice("role") if len(roles) > 0 { update["roles"] = roles } else { update["roles"] = app.Roles } if upgradeStrategy == upgradeStrategyRollingUpdate { update["upgradeStrategy"] = &managementClient.UpgradeStrategy{ RollingUpdate: &managementClient.RollingUpdate{ BatchSize: ctx.Int64(argUpgradeBatchSize), Interval: ctx.Int64(argUpgradeBatchInterval), }, } } else if upgradeStrategy == upgradeStrategySimultaneously { update["upgradeStrategy"] = nil } if _, err := c.ManagementClient.MultiClusterApp.Update(app, update); err != nil { return err } return nil } func multiClusterAppRollback(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } resource, app, err := searchForMcapp(c, ctx.Args().First()) if err != nil { return err } if ctx.Bool("show-revisions") { return outputMultiClusterAppRevisions(ctx, c, resource, app) } if ctx.NArg() != 2 { return cli.ShowSubcommandHelp(ctx) } revisionResource, err := Lookup(c, ctx.Args().Get(1), managementClient.MultiClusterAppRevisionType) if err != nil { return err } rr := &managementClient.MultiClusterAppRollbackInput{ RevisionID: revisionResource.ID, } if err := c.ManagementClient.MultiClusterApp.ActionRollback(app, rr); err != nil { return err } return nil } func multiClusterAppTemplateInstall(ctx *cli.Context) error { if ctx.NArg() > 2 { return cli.ShowSubcommandHelp(ctx) } templateName := ctx.Args().First() appName := ctx.Args().Get(1) c, err := GetClient(ctx) if err != nil { return err } roles := ctx.StringSlice("role") if len(roles) == 0 { // Handle the default here because the cli default value for stringSlice do not get overridden. roles = []string{"project-member"} } app := &managementClient.MultiClusterApp{ Name: appName, Roles: roles, } upgradeStrategy := strings.ToLower(ctx.String(argUpgradeStrategy)) if !slice.ContainsString(upgradeStrategies, upgradeStrategy) { return fmt.Errorf("invalid upgrade-strategy %q, supported values are \"rolling-update\" and \"simultaneously\"", upgradeStrategy) } else if upgradeStrategy == upgradeStrategyRollingUpdate { app.UpgradeStrategy = &managementClient.UpgradeStrategy{ RollingUpdate: &managementClient.RollingUpdate{ BatchSize: ctx.Int64(argUpgradeBatchSize), Interval: ctx.Int64(argUpgradeBatchInterval), }, } } resource, err := Lookup(c, templateName, managementClient.TemplateType) if err != nil { return err } template, err := getFilteredTemplate(ctx, c, resource.ID) if err != nil { return err } latestVersion, err := getTemplateLatestVersion(template) if err != nil { return err } templateVersionID := templateVersionIDFromVersionLink(template.VersionLinks[latestVersion]) userVersion := ctx.String("version") if userVersion != "" { if link, ok := template.VersionLinks[userVersion]; ok { templateVersionID = templateVersionIDFromVersionLink(link) } else { return fmt.Errorf( "version %s for template %s is invalid, run 'rancher mcapp show-template %s' for a list of versions", userVersion, templateName, templateName, ) } } templateVersion, err := c.ManagementClient.TemplateVersion.ByID(templateVersionID) if err != nil { return err } interactive := !ctx.Bool("no-prompt") answers, answersSetString, err := processAnswerInstall(ctx, templateVersion, nil, nil, interactive, true) if err != nil { return err } projectIDs, err := lookupProjectIDsFromTargets(c, ctx.StringSlice("target")) if err != nil { return err } for _, target := range projectIDs { app.Targets = append(app.Targets, managementClient.Target{ ProjectID: target, }) } if len(projectIDs) == 0 { app.Targets = append(app.Targets, managementClient.Target{ ProjectID: c.UserConfig.Project, }) } app.Answers, err = toMultiClusterAppAnswers(c, answers, answersSetString) if err != nil { return err } app.TemplateVersionID = templateVersionID accessType := strings.ToLower(ctx.String("member-access-type")) if !slice.ContainsString(memberAccessTypes, accessType) { return fmt.Errorf("invalid access type %q, supported values are \"owner\",\"member\" and \"read-only\"", accessType) } members, err := addMembersByNames(ctx, c, app.Members, ctx.StringSlice("member"), accessType) if err != nil { return err } app.Members = members app.Wait = ctx.Bool("helm-wait") app.Timeout = ctx.Int64("helm-timeout") app, err = c.ManagementClient.MultiClusterApp.Create(app) if err != nil { return err } fmt.Printf("Installing multi-cluster app %q...\n", app.Name) return nil } func lookupProjectIDsFromTargets(c *cliclient.MasterClient, targets []string) ([]string, error) { var projectIDs []string for _, target := range targets { projectID, err := lookupProjectIDFromProjectScope(c, target) if err != nil { return nil, err } projectIDs = append(projectIDs, projectID) } return projectIDs, nil } func lookupClusterIDFromClusterScope(c *cliclient.MasterClient, clusterNameOrID string) (string, error) { clusterResource, err := Lookup(c, clusterNameOrID, managementClient.ClusterType) if err != nil { return "", err } return clusterResource.ID, nil } func lookupProjectIDFromProjectScope(c *cliclient.MasterClient, scope string) (string, error) { cluster, project := parseScope(scope) clusterResource, err := Lookup(c, cluster, managementClient.ClusterType) if err != nil { return "", err } if clusterResource.ID == cluster { // Lookup by ID projectResource, err := Lookup(c, scope, managementClient.ProjectType) if err != nil { return "", err } return projectResource.ID, nil } // Lookup by clusterName:projectName projectResource, err := Lookup(c, project, managementClient.ProjectType) if err != nil { return "", err } return projectResource.ID, nil } func toMultiClusterAppAnswers(c *cliclient.MasterClient, answers, answersSetString map[string]string) ([]managementClient.Answer, error) { answerMap := make(map[string]scopeAnswers) var answerSlice []managementClient.Answer if err := setValueInAnswerMapByScope(c, answerMap, answers, "Answers"); err != nil { return nil, err } if err := setValueInAnswerMapByScope(c, answerMap, answersSetString, "AnswersSetString"); err != nil { return nil, err } for k, v := range answerMap { answer := managementClient.Answer{ Values: v.Answers, ValuesSetString: v.AnswersSetString, } if strings.Contains(k, ":") { answer.ProjectID = k } else if k != "" { answer.ClusterID = k } answerSlice = append(answerSlice, answer) } return answerSlice, nil } func setValueInAnswerMapByScope(c *cliclient.MasterClient, answerMap map[string]scopeAnswers, inputAnswers map[string]string, scopeAnswersFieldStr string) error { for k, v := range inputAnswers { switch parts := strings.SplitN(k, ":", 3); { case len(parts) == 1: // Global scope setValueInAnswerMap(answerMap, "", "", scopeAnswersFieldStr, k, v) case len(parts) == 2: // Cluster scope clusterNameOrID := parts[0] clusterID, err := lookupClusterIDFromClusterScope(c, clusterNameOrID) if err != nil { return err } setValueInAnswerMap(answerMap, clusterNameOrID, clusterID, scopeAnswersFieldStr, parts[1], v) case len(parts) == 3: // Project scope projectScope := concatScope(parts[0], parts[1]) projectID, err := lookupProjectIDFromProjectScope(c, projectScope) if err != nil { return err } setValueInAnswerMap(answerMap, projectScope, projectID, scopeAnswersFieldStr, parts[2], v) } } return nil } func setValueInAnswerMap(answerMap map[string]scopeAnswers, scope, scopeID, fieldNameToUpdate, key, value string) { var exist bool if answerMap[scopeID].Answers == nil && answerMap[scopeID].AnswersSetString == nil { answerMap[scopeID] = scopeAnswers{ Answers: make(map[string]string), AnswersSetString: make(map[string]string), } } scopeAnswersStruct := answerMap[scopeID] scopeAnswersMap := reflect.ValueOf(&scopeAnswersStruct).Elem().FieldByName(fieldNameToUpdate) for _, k := range scopeAnswersMap.MapKeys() { if reflect.ValueOf(key) == k { exist = true break } } if exist { // It is possible that there are different forms of the same answer key in aggregated answers // In this case, name format from users overrides id format from existing app answers. if scope != scopeID { scopeAnswersMap.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(value)) } } else { scopeAnswersMap.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(value)) } } func fromMultiClusterAppAnswers(answerSlice []managementClient.Answer) (map[string]string, map[string]string) { answers := make(map[string]string) answersSetString := make(map[string]string) for _, answer := range answerSlice { for k, v := range answer.Values { scopedKey := getAnswerScopedKey(answer, k) answers[scopedKey] = v } for k, v := range answer.ValuesSetString { scopedKey := getAnswerScopedKey(answer, k) answersSetString[scopedKey] = v } } return answers, answersSetString } func getAnswerScopedKey(answer managementClient.Answer, key string) string { scope := "" if answer.ProjectID != "" { scope = answer.ProjectID } else if answer.ClusterID != "" { scope = answer.ClusterID } scopedKey := key if scope != "" { scopedKey = concatScope(scope, key) } return scopedKey } func addMcappTargetProject(ctx *cli.Context) error { if len(ctx.Args()) < 2 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } _, app, err := searchForMcapp(c, ctx.Args().First()) if err != nil { return err } input, err := getTargetInput(ctx, c) if err != nil { return err } if err := c.ManagementClient.MultiClusterApp.ActionAddProjects(app, input); err != nil { return err } return nil } func deleteMcappTargetProject(ctx *cli.Context) error { if len(ctx.Args()) < 2 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } _, app, err := searchForMcapp(c, ctx.Args().First()) if err != nil { return err } input, err := getTargetInput(ctx, c) if err != nil { return err } return c.ManagementClient.MultiClusterApp.ActionRemoveProjects(app, input) } func getTargetInput(ctx *cli.Context, c *cliclient.MasterClient) (*managementClient.UpdateMultiClusterAppTargetsInput, error) { targets := ctx.Args()[1:] projectIDs, err := lookupProjectIDsFromTargets(c, targets) if err != nil { return nil, err } answers, answersSetString, err := processAnswerUpdates(ctx, nil, nil) if err != nil { return nil, err } mcaAnswers, err := toMultiClusterAppAnswers(c, answers, answersSetString) if err != nil { return nil, err } input := &managementClient.UpdateMultiClusterAppTargetsInput{ Projects: projectIDs, Answers: mcaAnswers, } return input, nil } func addMcappMember(ctx *cli.Context) error { if len(ctx.Args()) < 3 { return cli.ShowSubcommandHelp(ctx) } appName := ctx.Args().First() accessType := strings.ToLower(ctx.Args().Get(1)) memberNames := ctx.Args()[2:] if !slice.ContainsString(memberAccessTypes, accessType) { return fmt.Errorf("invalid access type %q, supported values are \"owner\",\"member\" and \"read-only\"", accessType) } c, err := GetClient(ctx) if err != nil { return err } _, app, err := searchForMcapp(c, appName) if err != nil { return err } members, err := addMembersByNames(ctx, c, app.Members, memberNames, accessType) if err != nil { return err } update := make(map[string]interface{}) update["members"] = members update["roles"] = app.Roles _, err = c.ManagementClient.MultiClusterApp.Update(app, update) return err } func deleteMcappMember(ctx *cli.Context) error { if len(ctx.Args()) < 2 { return cli.ShowSubcommandHelp(ctx) } appName := ctx.Args().First() memberNames := ctx.Args()[1:] c, err := GetClient(ctx) if err != nil { return err } _, app, err := searchForMcapp(c, appName) if err != nil { return err } members, err := deleteMembersByNames(ctx, c, app.Members, memberNames) if err != nil { return err } update := make(map[string]interface{}) update["members"] = members update["roles"] = app.Roles _, err = c.ManagementClient.MultiClusterApp.Update(app, update) return err } func showMultiClusterApp(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } resource, app, err := searchForMcapp(c, ctx.Args().First()) if err != nil { return err } err = outputMultiClusterAppRevisions(ctx, c, resource, app) if err != nil { return err } fmt.Println() err = outputMultiClusterAppVersions(ctx, c, app) if err != nil { return err } if ctx.Bool("show-roles") { fmt.Println() err = outputMultiClusterAppRoles(ctx, c, app) if err != nil { return err } } return nil } func listMultiClusterAppMembers(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } _, app, err := searchForMcapp(c, ctx.Args().First()) if err != nil { return err } return outputMembers(ctx, c, app.Members) } func listMultiClusterAppAnswers(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } _, app, err := searchForMcapp(c, ctx.Args().First()) if err != nil { return err } return outputMultiClusterAppAnswers(ctx, c, app) } func searchForMcapp(c *cliclient.MasterClient, name string) (*types.Resource, *managementClient.MultiClusterApp, error) { resource, err := Lookup(c, name, managementClient.MultiClusterAppType) if err != nil { return nil, nil, err } app, err := c.ManagementClient.MultiClusterApp.ByID(resource.ID) if err != nil { return nil, nil, err } return resource, app, nil } func outputMultiClusterAppVersions(ctx *cli.Context, c *cliclient.MasterClient, app *managementClient.MultiClusterApp) error { templateVersion, err := c.ManagementClient.TemplateVersion.ByID(app.TemplateVersionID) if err != nil { return err } ver, err := getRancherServerVersion(c) if err != nil { return err } filter := defaultListOpts(ctx) filter.Filters["rancherVersion"] = ver template := &managementClient.Template{} if err := c.ManagementClient.Ops.DoGet(templateVersion.Links["template"], filter, template); err != nil { return err } writer := NewTableWriter([][]string{ {"CURRENT", "Current"}, {"VERSION", "Version"}, }, ctx) defer writer.Close() sortedVersions, err := sortTemplateVersions(template) if err != nil { return err } for _, version := range sortedVersions { var current string if version.String() == templateVersion.Version { current = "*" } writer.Write(&VersionData{ Current: current, Version: version.String(), }) } return writer.Err() } func outputMultiClusterAppRevisions(ctx *cli.Context, c *cliclient.MasterClient, resource *types.Resource, app *managementClient.MultiClusterApp) error { revisions := &managementClient.MultiClusterAppRevisionCollection{} if err := c.ManagementClient.GetLink(*resource, "revisions", revisions); err != nil { return err } var sorted revSlice for _, rev := range revisions.Data { parsedTime, err := time.Parse(time.RFC3339, rev.Created) if err != nil { return err } sorted = append(sorted, revision{Name: rev.Name, Created: parsedTime}) } sort.Sort(sorted) writer := NewTableWriter([][]string{ {"CURRENT", "Current"}, {"REVISION", "Name"}, {"CREATED", "Human"}, }, ctx) defer writer.Close() for _, rev := range sorted { if rev.Name == app.Status.RevisionID { rev.Current = "*" } rev.Human = rev.Created.Format("02 Jan 2006 15:04:05 MST") writer.Write(rev) } return writer.Err() } func outputMultiClusterAppRoles(ctx *cli.Context, c *cliclient.MasterClient, app *managementClient.MultiClusterApp) error { writer := NewTableWriter([][]string{ {"ROLE_NAME", "Name"}, }, ctx) defer writer.Close() for _, r := range app.Roles { writer.Write(map[string]string{"Name": r}) } return writer.Err() } func outputMultiClusterAppAnswers(ctx *cli.Context, c *cliclient.MasterClient, app *managementClient.MultiClusterApp) error { writer := NewTableWriter([][]string{ {"SCOPE", "Scope"}, {"QUESTION", "Question"}, {"ANSWER", "Answer"}, }, ctx) defer writer.Close() answers := app.Answers // Sort answers by scope in the Global-Cluster-Project order sort.Slice(answers, func(i, j int) bool { if answers[i].ClusterID == "" && answers[i].ProjectID == "" { return true } else if answers[i].ClusterID != "" && answers[j].ProjectID != "" { return true } return false }) var scope string for _, r := range answers { scope = "Global" if r.ClusterID != "" { cluster, err := getClusterByID(c, r.ClusterID) if err != nil { return err } scope = fmt.Sprintf("All projects in cluster %s", cluster.Name) } else if r.ProjectID != "" { project, err := getProjectByID(c, r.ProjectID) if err != nil { return err } scope = fmt.Sprintf("Project %s", project.Name) } for key, value := range r.Values { writer.Write(map[string]string{ "Scope": scope, "Question": key, "Answer": value, }) } for key, value := range r.ValuesSetString { writer.Write(map[string]string{ "Scope": scope, "Question": key, "Answer": fmt.Sprintf("\"%s\"", value), }) } } return writer.Err() } func globalTemplateLs(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } filter := defaultListOpts(ctx) if ctx.String("catalog") != "" { resource, err := Lookup(c, ctx.String("catalog"), managementClient.CatalogType) if err != nil { return err } filter.Filters["catalogId"] = resource.ID } collection, err := c.ManagementClient.Template.List(filter) if err != nil { return err } writer := NewTableWriter([][]string{ {"ID", "ID"}, {"NAME", "Template.Name"}, {"CATEGORY", "Category"}, }, ctx) defer writer.Close() for _, item := range collection.Data { // Skip non-global catalogs if item.CatalogID == "" { continue } writer.Write(&TemplateData{ ID: item.ID, Template: item, Category: strings.Join(item.Categories, ","), }) } return writer.Err() } func concatScope(scope, key string) string { return fmt.Sprintf("%s:%s", scope, key) } func parseScope(ref string) (scope string, key string) { parts := strings.SplitN(ref, ":", 2) if len(parts) == 1 { return "", parts[0] } return parts[0], parts[1] } 0707010000001B000081A400000000000000000000000165D8C5D300000AB0000000000000000000000000000000000000002600000000cli-2.8.3/cmd/multiclusterapp_test.gopackage cmd import ( "testing" client "github.com/rancher/rancher/pkg/client/generated/management/v3" "github.com/stretchr/testify/assert" ) func TestFromMultiClusterAppAnswers(t *testing.T) { assert := assert.New(t) answerSlice := []client.Answer{ { ProjectID: "c-1:p-1", Values: map[string]string{ "var-1": "val1", "var-2": "val2", }, ValuesSetString: map[string]string{ "str-var-1": "str-val1", "str-var-2": "str-val2", }, }, { ProjectID: "c-1:p-2", Values: map[string]string{ "var-3": "val3", }, ValuesSetString: map[string]string{ "str-var-3": "str-val3", }, }, { ClusterID: "c-1", Values: map[string]string{ "var-4": "val4", }, ValuesSetString: map[string]string{ "str-var-4": "str-val4", }, }, { ClusterID: "c-2", Values: map[string]string{ "var-5": "val5", }, ValuesSetString: map[string]string{ "str-var-5": "str-val5", }, }, { Values: map[string]string{ "var-6": "val6", }, ValuesSetString: map[string]string{ "str-var-6": "str-val6", }, }, } answers, answersSetString := fromMultiClusterAppAnswers(answerSlice) assert.Equal(len(answers), 6) assert.Equal(answers["c-1:p-1:var-1"], "val1") assert.Equal(answers["c-1:p-1:var-2"], "val2") assert.Equal(answers["c-1:p-2:var-3"], "val3") assert.Equal(answers["c-1:var-4"], "val4") assert.Equal(answers["c-2:var-5"], "val5") assert.Equal(answers["var-6"], "val6") assert.Equal(len(answersSetString), 6) assert.Equal(answersSetString["c-1:p-1:str-var-1"], "str-val1") assert.Equal(answersSetString["c-1:p-1:str-var-2"], "str-val2") assert.Equal(answersSetString["c-1:p-2:str-var-3"], "str-val3") assert.Equal(answersSetString["c-1:str-var-4"], "str-val4") assert.Equal(answersSetString["c-2:str-var-5"], "str-val5") assert.Equal(answersSetString["str-var-6"], "str-val6") } func TestGetReadableTargetNames(t *testing.T) { assert := assert.New(t) clusters := map[string]client.Cluster{ "c-1": { Name: "cn-1", }, "c-2": { Name: "cn-2", }, } projects := map[string]client.Project{ "c-1:p-1": { Name: "pn-1", }, "c-1:p-2": { Name: "pn-2", }, "c-2:p-3": { Name: "pn-3", }, "c-2:p-4": { Name: "pn-4", }, } targets := []client.Target{ { ProjectID: "c-1:p-1", }, { ProjectID: "c-1:p-2", }, { ProjectID: "c-2:p-3", }, } result := getReadableTargetNames(clusters, projects, targets) assert.Contains(result, "cn-1:pn-1") assert.Contains(result, "cn-1:pn-2") assert.Contains(result, "cn-2:pn-3") targets = []client.Target{ { ProjectID: "c-0:p-0", }, } result = getReadableTargetNames(clusters, projects, targets) assert.Contains(result, "c-0:p-0") } 0707010000001C000081A400000000000000000000000165D8C5D300001464000000000000000000000000000000000000001B00000000cli-2.8.3/cmd/namespace.gopackage cmd import ( "fmt" "github.com/pkg/errors" "github.com/rancher/cli/cliclient" clusterClient "github.com/rancher/rancher/pkg/client/generated/cluster/v3" "github.com/urfave/cli" ) type NamespaceData struct { ID string Namespace clusterClient.Namespace } func NamespaceCommand() cli.Command { return cli.Command{ Name: "namespaces", Aliases: []string{"namespace"}, Usage: "Operations on namespaces", Action: defaultAction(namespaceLs), Flags: []cli.Flag{ quietFlag, }, Subcommands: []cli.Command{ { Name: "ls", Usage: "List namespaces", Description: "\nLists all namespaces in the current project.", ArgsUsage: "None", Action: namespaceLs, Flags: []cli.Flag{ cli.BoolFlag{ Name: "all-namespaces", Usage: "List all namespaces in the current cluster", }, cli.StringFlag{ Name: "format", Usage: "'json', 'yaml' or Custom format: '{{.Namespace.ID}} {{.Namespace.Name}}'", }, quietFlag, }, }, { Name: "create", Usage: "Create a namespace", Description: "\nCreates a namespace in the current cluster.", ArgsUsage: "[NEWPROJECTNAME...]", Action: namespaceCreate, Flags: []cli.Flag{ cli.StringFlag{ Name: "description", Usage: "Description to apply to the namespace", }, }, }, { Name: "delete", Aliases: []string{"rm"}, Usage: "Delete a namespace by name or ID", ArgsUsage: "[NAMESPACEID NAMESPACENAME]", Action: namespaceDelete, }, { Name: "move", Usage: "Move a namespace to a different project", ArgsUsage: "[NAMESPACEID/NAMESPACENAME PROJECTID]", Action: namespaceMove, }, }, } } func namespaceLs(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } collection, err := getNamespaceList(ctx, c) if err != nil { return err } if !ctx.Bool("all-namespaces") { var projectNamespaces []clusterClient.Namespace for _, namespace := range collection.Data { if namespace.ProjectID == c.UserConfig.Project { projectNamespaces = append(projectNamespaces, namespace) } } collection.Data = projectNamespaces } writer := NewTableWriter([][]string{ {"ID", "ID"}, {"NAME", "Namespace.Name"}, {"STATE", "Namespace.State"}, {"PROJECT", "Namespace.ProjectID"}, {"DESCRIPTION", "Namespace.Description"}, }, ctx) defer writer.Close() for _, item := range collection.Data { writer.Write(&NamespaceData{ ID: item.ID, Namespace: item, }) } return writer.Err() } func namespaceCreate(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } newNamespace := &clusterClient.Namespace{ Name: ctx.Args().First(), ProjectID: c.UserConfig.Project, Description: ctx.String("description"), } _, err = c.ClusterClient.Namespace.Create(newNamespace) if err != nil { return err } return nil } func namespaceDelete(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } for _, arg := range ctx.Args() { resource, err := Lookup(c, arg, "namespace") if err != nil { return err } namespace, err := getNamespaceByID(c, resource.ID) if err != nil { return err } err = c.ClusterClient.Namespace.Delete(namespace) if err != nil { return err } } return nil } func namespaceMove(ctx *cli.Context) error { if ctx.NArg() < 2 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } resource, err := Lookup(c, ctx.Args().First(), "namespace") if err != nil { return err } namespace, err := getNamespaceByID(c, resource.ID) if err != nil { return err } projResource, err := Lookup(c, ctx.Args().Get(1), "project") if err != nil { return err } proj, err := getProjectByID(c, projResource.ID) if err != nil { return err } if anno, ok := namespace.Annotations["cattle.io/appIds"]; ok && anno != "" { return errors.Errorf("Namespace %v cannot be moved", namespace.Name) } if _, ok := namespace.Actions["move"]; ok { move := &clusterClient.NamespaceMove{ ProjectID: proj.ID, } return c.ClusterClient.Namespace.ActionMove(namespace, move) } update := make(map[string]string) update["projectId"] = proj.ID _, err = c.ClusterClient.Namespace.Update(namespace, update) if err != nil { return err } return nil } func getNamespaceList( ctx *cli.Context, c *cliclient.MasterClient, ) (*clusterClient.NamespaceCollection, error) { collection, err := c.ClusterClient.Namespace.List(defaultListOpts(ctx)) if err != nil { return nil, err } return collection, nil } func getNamespaceByID( c *cliclient.MasterClient, namespaceID string, ) (*clusterClient.Namespace, error) { namespace, err := c.ClusterClient.Namespace.ByID(namespaceID) if err != nil { return nil, fmt.Errorf("no namespace found with the ID [%s], run "+ "`rancher namespaces` to see available namespaces: %s", namespaceID, err) } return namespace, nil } 0707010000001D000081A400000000000000000000000165D8C5D300000F7D000000000000000000000000000000000000001600000000cli-2.8.3/cmd/node.gopackage cmd import ( "fmt" "github.com/sirupsen/logrus" "github.com/rancher/cli/cliclient" managementClient "github.com/rancher/rancher/pkg/client/generated/management/v3" "github.com/urfave/cli" ) type NodeData struct { ID string Node managementClient.Node Name string Pool string } func NodeCommand() cli.Command { return cli.Command{ Name: "nodes", Aliases: []string{"node"}, Usage: "Operations on nodes", Action: defaultAction(nodeLs), Subcommands: []cli.Command{ { Name: "ls", Usage: "List nodes", Description: "\nLists all nodes in the current cluster.", ArgsUsage: "None", Action: nodeLs, Flags: []cli.Flag{ cli.StringFlag{ Name: "format", Usage: "'json', 'yaml' or Custom format: '{{.Node.ID}} {{.Node.Name}}'", }, quietFlag, }, }, { Name: "delete", Aliases: []string{"rm"}, Usage: "Delete a node by ID", ArgsUsage: "[NODEID NODENAME]", Action: nodeDelete, }, }, } } func nodeLs(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } collection, err := getNodesList(ctx, c, c.UserConfig.FocusedCluster()) if err != nil { return err } nodePools, err := getNodePools(ctx, c) if err != nil { return err } writer := NewTableWriter([][]string{ {"ID", "ID"}, {"NAME", "Name"}, {"STATE", "Node.State"}, {"POOL", "Pool"}, {"DESCRIPTION", "Node.Description"}, }, ctx) defer writer.Close() for _, item := range collection.Data { writer.Write(&NodeData{ ID: item.ID, Node: item, Name: getNodeName(item), Pool: getNodePoolName(item, nodePools), }) } return writer.Err() } func nodeDelete(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } for _, arg := range ctx.Args() { resource, err := Lookup(c, arg, "node") if err != nil { return err } node, err := getNodeByID(ctx, c, resource.ID) if err != nil { return err } if _, ok := node.Links["remove"]; !ok { logrus.Warnf("node %v is externally managed and must be deleted "+ "through the provider", getNodeName(node)) continue } err = c.ManagementClient.Node.Delete(&node) if err != nil { return err } } return nil } func getNodesList( ctx *cli.Context, c *cliclient.MasterClient, clusterID string, ) (*managementClient.NodeCollection, error) { filter := defaultListOpts(ctx) filter.Filters["clusterId"] = clusterID collection, err := c.ManagementClient.Node.List(filter) if err != nil { return nil, err } return collection, nil } func getNodeByID( ctx *cli.Context, c *cliclient.MasterClient, nodeID string, ) (managementClient.Node, error) { nodeCollection, err := getNodesList(ctx, c, c.UserConfig.FocusedCluster()) if err != nil { return managementClient.Node{}, err } for _, node := range nodeCollection.Data { if node.ID == nodeID { return node, nil } } return managementClient.Node{}, fmt.Errorf("no node found with the ID [%s], run "+ "`rancher nodes` to see available nodes", nodeID) } func getNodeName(node managementClient.Node) string { if node.Name != "" { return node.Name } else if node.NodeName != "" { return node.NodeName } else if node.RequestedHostname != "" { return node.RequestedHostname } return node.ID } func getNodePools( ctx *cli.Context, c *cliclient.MasterClient, ) (*managementClient.NodePoolCollection, error) { filter := defaultListOpts(ctx) filter.Filters["clusterId"] = c.UserConfig.FocusedCluster() collection, err := c.ManagementClient.NodePool.List(filter) if err != nil { return nil, err } return collection, nil } func getNodePoolName(node managementClient.Node, pools *managementClient.NodePoolCollection) string { for _, pool := range pools.Data { if node.NodePoolID == pool.ID { return pool.HostnamePrefix } } return "" } 0707010000001E000081A400000000000000000000000165D8C5D300001FF8000000000000000000000000000000000000001900000000cli-2.8.3/cmd/project.gopackage cmd import ( "fmt" "github.com/rancher/cli/cliclient" managementClient "github.com/rancher/rancher/pkg/client/generated/management/v3" "github.com/urfave/cli" ) type ProjectData struct { ID string Project managementClient.Project } func ProjectCommand() cli.Command { return cli.Command{ Name: "projects", Aliases: []string{"project"}, Usage: "Operations on projects", Action: defaultAction(projectLs), Subcommands: []cli.Command{ { Name: "ls", Usage: "List projects", Description: "\nLists all projects in the current cluster.", ArgsUsage: "None", Action: projectLs, Flags: []cli.Flag{ cli.StringFlag{ Name: "format", Usage: "'json', 'yaml' or Custom format: '{{.Project.ID}} {{.Project.Name}}'", }, quietFlag, }, }, { Name: "create", Usage: "Create a project", Description: "\nCreates a project in the current cluster.", ArgsUsage: "[NEWPROJECTNAME...]", Action: projectCreate, Flags: []cli.Flag{ cli.StringFlag{ Name: "cluster", Usage: "Cluster ID to create the project in", }, cli.StringFlag{ Name: "description", Usage: "Description to apply to the project", }, }, }, { Name: "delete", Aliases: []string{"rm"}, Usage: "Delete a project by ID", ArgsUsage: "[PROJECTID PROJECTNAME]", Action: projectDelete, }, { Name: "add-member-role", Usage: "Add a member to the project", Action: addProjectMemberRoles, Description: "Examples:\n #Create the roles of 'create-ns' and 'services-manage' for a user named 'user1'\n rancher project add-member-role user1 create-ns services-manage\n", ArgsUsage: "[USERNAME, ROLE...]", Flags: []cli.Flag{ cli.StringFlag{ Name: "project-id", Usage: "Optional project ID to apply this change to, defaults to the current context", }, }, }, { Name: "delete-member-role", Usage: "Delete a member from the project", Action: deleteProjectMemberRoles, Description: "Examples:\n #Delete the roles of 'create-ns' and 'services-manage' for a user named 'user1'\n rancher project delete-member-role user1 create-ns services-manage\n", ArgsUsage: "[USERNAME, ROLE...]", Flags: []cli.Flag{ cli.StringFlag{ Name: "project-id", Usage: "Optional project ID to apply this change to, defaults to the current context", }, }, }, { Name: "list-roles", Usage: "List all available roles for a project", Action: listProjectRoles, }, { Name: "list-members", Usage: "List current members of the project", Action: listProjectMembers, Flags: []cli.Flag{ cli.StringFlag{ Name: "project-id", Usage: "Optional project ID to list members for, defaults to the current context", }, }, }, }, } } func projectLs(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } collection, err := getProjectList(ctx, c) if err != nil { return err } writer := NewTableWriter([][]string{ {"ID", "ID"}, {"NAME", "Project.Name"}, {"STATE", "Project.State"}, {"DESCRIPTION", "Project.Description"}, }, ctx) defer writer.Close() for _, item := range collection.Data { writer.Write(&ProjectData{ ID: item.ID, Project: item, }) } return writer.Err() } func projectCreate(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } clusterID := c.UserConfig.FocusedCluster() if ctx.String("cluster") != "" { resource, err := Lookup(c, ctx.String("cluster"), "cluster") if err != nil { return err } clusterID = resource.ID } newProj := &managementClient.Project{ Name: ctx.Args().First(), ClusterID: clusterID, Description: ctx.String("description"), } _, err = c.ManagementClient.Project.Create(newProj) if err != nil { return err } return nil } func projectDelete(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowSubcommandHelp(ctx) } c, err := GetClient(ctx) if err != nil { return err } for _, arg := range ctx.Args() { resource, err := Lookup(c, arg, "project") if err != nil { return err } project, err := getProjectByID(c, resource.ID) if err != nil { return err } err = c.ManagementClient.Project.Delete(project) if err != nil { return err } } return nil } func addProjectMemberRoles(ctx *cli.Context) error { if len(ctx.Args()) < 2 { return cli.ShowSubcommandHelp(ctx) } memberName := ctx.Args().First() roles := ctx.Args()[1:] c, err := GetClient(ctx) if err != nil { return err } member, err := searchForMember(ctx, c, memberName) if err != nil { return err } projectID := c.UserConfig.Project if ctx.String("project-id") != "" { projectID = ctx.String("project-id") } for _, role := range roles { rtb := managementClient.ProjectRoleTemplateBinding{ ProjectID: projectID, RoleTemplateID: role, } if member.PrincipalType == "user" { rtb.UserPrincipalID = member.ID } else { rtb.GroupPrincipalID = member.ID } _, err = c.ManagementClient.ProjectRoleTemplateBinding.Create(&rtb) if err != nil { return err } } return nil } func deleteProjectMemberRoles(ctx *cli.Context) error { if len(ctx.Args()) < 2 { return cli.ShowSubcommandHelp(ctx) } memberName := ctx.Args().First() roles := ctx.Args()[1:] c, err := GetClient(ctx) if err != nil { return err } member, err := searchForMember(ctx, c, memberName) if err != nil { return err } projectID := c.UserConfig.Project if ctx.String("project-id") != "" { projectID = ctx.String("project-id") } for _, role := range roles { filter := defaultListOpts(ctx) filter.Filters["projectId"] = projectID filter.Filters["roleTemplateId"] = role if member.PrincipalType == "user" { filter.Filters["userPrincipalId"] = member.ID } else { filter.Filters["groupPrincipalId"] = member.ID } bindings, err := c.ManagementClient.ProjectRoleTemplateBinding.List(filter) if err != nil { return err } for _, binding := range bindings.Data { err = c.ManagementClient.ProjectRoleTemplateBinding.Delete(&binding) if err != nil { return err } } } return nil } func listProjectRoles(ctx *cli.Context) error { return listRoles(ctx, "project") } func listProjectMembers(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } projectID := c.UserConfig.Project if ctx.String("project-id") != "" { projectID = ctx.String("project-id") } filter := defaultListOpts(ctx) filter.Filters["projectId"] = projectID bindings, err := c.ManagementClient.ProjectRoleTemplateBinding.List(filter) if err != nil { return err } userFilter := defaultListOpts(ctx) users, err := c.ManagementClient.User.List(userFilter) if err != nil { return err } userMap := usersToNameMapping(users.Data) var b []RoleTemplateBinding for _, binding := range bindings.Data { parsedTime, err := createdTimetoHuman(binding.Created) if err != nil { return err } b = append(b, RoleTemplateBinding{ ID: binding.ID, User: userMap[binding.UserID], Role: binding.RoleTemplateID, Created: parsedTime, }) } return listRoleTemplateBindings(ctx, b) } func getProjectList( ctx *cli.Context, c *cliclient.MasterClient, ) (*managementClient.ProjectCollection, error) { filter := defaultListOpts(ctx) filter.Filters["clusterId"] = c.UserConfig.FocusedCluster() collection, err := c.ManagementClient.Project.List(filter) if err != nil { return nil, err } return collection, nil } func getProjectByID( c *cliclient.MasterClient, projectID string, ) (*managementClient.Project, error) { project, err := c.ManagementClient.Project.ByID(projectID) if err != nil { return nil, fmt.Errorf("no project found with the ID [%s], run "+ "`rancher projects` to see available projects: %s", projectID, err) } return project, nil } 0707010000001F000081A400000000000000000000000165D8C5D3000002F7000000000000000000000000000000000000001800000000cli-2.8.3/cmd/prompt.gopackage cmd import ( "fmt" rancherprompt "github.com/rancher/cli/rancher_prompt" "github.com/c-bata/go-prompt" "github.com/urfave/cli" ) func PromptCommand() cli.Command { return cli.Command{ Name: "prompt", Usage: "Enter rancher cli auto-prompt mode", ArgsUsage: "None", Action: promptAction, Flags: []cli.Flag{}, } } func promptAction(ctx *cli.Context) error { fmt.Print("rancher cli auto-completion mode") defer fmt.Println("Goodbye!") p := prompt.New( rancherprompt.Executor, rancherprompt.Completer, prompt.OptionTitle("rancher-prompt: interactive rancher client"), prompt.OptionPrefix("rancher$ "), prompt.OptionInputTextColor(prompt.Yellow), prompt.OptionMaxSuggestion(20), ) p.Run() return nil } 07070100000020000081A400000000000000000000000165D8C5D300000A5E000000000000000000000000000000000000001400000000cli-2.8.3/cmd/ps.gopackage cmd import ( "strconv" "strings" "github.com/rancher/cli/cliclient" "github.com/urfave/cli" ) type PSHolder struct { NameSpace string Name string Type string State string Image string Scale string } func PsCommand() cli.Command { return cli.Command{ Name: "ps", Usage: "Show workloads in a project", Description: `Show information on the workloads in a project. Defaults to the current context. Examples: # Show workloads in the current context $ rancher ps # Show workloads in a specific project and output the results in yaml $ rancher ps --project projectFoo --format yaml `, Action: psLs, Flags: []cli.Flag{ cli.StringFlag{ Name: "project", Usage: "Optional project to show workloads for", }, cli.StringFlag{ Name: "format", Usage: "'json', 'yaml' or Custom format: '{{.Name}} {{.Image}}'", }, }, } } func psLs(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } if ctx.String("project") != "" { //Verify the project given is valid resource, err := Lookup(c, ctx.String("project"), "project") if err != nil { return err } sc, err := lookupConfig(ctx) if err != nil { return err } sc.Project = resource.ID projClient, err := cliclient.NewProjectClient(sc) if err != nil { return err } c.ProjectClient = projClient.ProjectClient } workLoads, err := c.ProjectClient.Workload.List(defaultListOpts(ctx)) if err != nil { return err } wlWriter := NewTableWriter([][]string{ {"NAMESPACE", "NameSpace"}, {"NAME", "Name"}, {"TYPE", "Type"}, {"STATE", "State"}, {"IMAGE", "Image"}, {"SCALE", "Scale"}, }, ctx) defer wlWriter.Close() for _, item := range workLoads.Data { var scale string if item.Scale == nil { scale = "-" } else { scale = strconv.Itoa(int(*item.Scale)) } item.Type = strings.Title(item.Type) wlWriter.Write(&PSHolder{ NameSpace: item.NamespaceId, Name: item.Name, Type: item.Type, State: item.State, Image: item.Containers[0].Image, Scale: scale, }) } opts := defaultListOpts(ctx) opts.Filters["workloadId"] = "" orphanPods, err := c.ProjectClient.Pod.List(opts) if err != nil { return err } if len(orphanPods.Data) > 0 { for _, item := range orphanPods.Data { item.Type = strings.Title(item.Type) wlWriter.Write(&PSHolder{ NameSpace: item.NamespaceId, Name: item.Name, Type: item.Type, State: item.State, Image: item.Containers[0].Image, Scale: "Standalone", // a single pod doesn't have scale }) } } return nil } 07070100000021000081A400000000000000000000000165D8C5D3000021B9000000000000000000000000000000000000001500000000cli-2.8.3/cmd/run.gopackage cmd import ( "github.com/urfave/cli" ) /* -a, --attach=[] Attach to STDIN, STDOUT or STDERR --add-host=[] Add a custom host-to-IP mapping (host:ip) --blkio-weight Block IO (relative weight), between 10 and 1000 --blkio-weight-device=[] Block IO weight (relative device weight) --cgroup-parent Optional parent cgroup for the container --cidfile Write the container ID to the file --cpu-period Limit CPU CFS (Completely Fair Scheduler) period --cpu-quota Limit CPU CFS (Completely Fair Scheduler) quota --cpuset-cpus CPUs in which to allow execution (0-3, 0,1) --cpuset-mems MEMs in which to allow execution (0-3, 0,1) -d, --detach Run container in background and print container ID --detach-keys Override the key sequence for detaching a container --device-read-bps=[] Limit read rate (bytes per second) from a device --device-read-iops=[] Limit read rate (IO per second) from a device --device-write-bps=[] Limit write rate (bytes per second) to a device --device-write-iops=[] Limit write rate (IO per second) to a device --disable-content-trust=true Skip Image verification --dns-opt=[] Set DNS options -e, --env=[] Set environment variables --env-file=[] Read in a file of environment variables --group-add=[] Add additional groups to join --help Print usage --ip Container IPv4 address (e.g. 172.30.100.104) --ip6 Container IPv6 address (e.g. 2001:db8::33) --ipc IPC namespace to use --isolation Container isolation level --kernel-memory Kernel memory limit -l, --label=[] Set meta data on a container --label-file=[] Read in a line delimited file of labels --link=[] Add link to another container --log-driver Logging driver for container --log-opt=[] Log driver options --mac-address Container MAC address (e.g. 92:d0:c6:0a:29:33) --memory-reservation Memory soft limit --memory-swappiness=-1 Tune container memory swappiness (0 to 100) --net=default Connect a container to a network --net-alias=[] Add network-scoped alias for the container --oom-kill-disable Disable OOM Killer --oom-score-adj Tune host's OOM preferences (-1000 to 1000) --restart=no Restart policy to apply when a container exits --rm Automatically remove the container when it exits --shm-size Size of /dev/shm, default value is 64MB --sig-proxy=true Proxy received signals to the process --stop-signal=SIGTERM Signal to stop a container, SIGTERM by default --tmpfs=[] Mount a tmpfs directory --ulimit=[] Ulimit options --uts UTS namespace to use -v, --volume=[] Bind mount a volume --volumes-from=[] Mount volumes from the specified container(s) */ func RunCommand() cli.Command { return cli.Command{ Name: "run", Usage: "Run services", Action: serviceRun, Flags: []cli.Flag{ cli.Int64Flag{ Name: "blkio-weight", Usage: "Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)", }, cli.Int64Flag{ Name: "cpu-quota", Usage: "Limit CPU CFS (Completely Fair Scheduler) quota", }, cli.Int64Flag{ Name: "cpu-shares", Usage: "CPU shares (relative weight)", }, cli.StringSliceFlag{ Name: "cap-add", Usage: "Add Linux capabilities", }, cli.StringSliceFlag{ Name: "cap-drop", Usage: "Drop Linux capabilities", }, cli.StringFlag{ Name: "cgroup-parent", Usage: "Optional parent cgroup for the container", }, cli.Int64Flag{ Name: "cpu-period", Usage: "Limit CPU CFS (Completely Fair Scheduler) period", }, cli.StringFlag{ Name: "cpuset-mems", Usage: "MEMs in which to allow execution (0-3, 0,1)", }, cli.StringSliceFlag{ Name: "device", Usage: "Add a host device to the container", }, cli.StringSliceFlag{ Name: "dns", Usage: "Set custom DNS servers", }, cli.StringSliceFlag{ Name: "dns-opt, dns-option", Usage: "Set DNS options", }, cli.StringSliceFlag{ Name: "dns-search", Usage: "Set custom DNS search domains", }, cli.StringSliceFlag{ Name: "entrypoint", Usage: "Overwrite the default ENTRYPOINT of the Image", }, cli.StringSliceFlag{ Name: "expose", Usage: "Expose a port or a range of ports", }, cli.StringSliceFlag{ Name: "group-add", Usage: "Add additional groups to join", }, cli.StringFlag{ Name: "hostname", Usage: "Container host name", }, cli.BoolFlag{ Name: "init", Usage: "Run an init inside the container that forwards signals and reaps processes", }, cli.BoolFlag{ Name: "interactive, i", Usage: "Keep STDIN open even if not attached", }, cli.Int64Flag{ Name: "kernel-memory", Usage: "Kernel memory limit", }, cli.Int64Flag{ Name: "memory, m", Usage: "Memory limit", }, cli.Int64Flag{ Name: "memory-reservation", Usage: "Memory soft limit", }, cli.Int64Flag{ Name: "memory-swap", Usage: "Swap limit equal to memory plus swap: '-1' to enable unlimited swap", }, cli.Int64Flag{ Name: "memory-swappiness", Usage: "Tune container memory swappiness (0 to 100)", }, cli.StringFlag{ Name: "name", Usage: "Assign a name to the container", }, cli.BoolFlag{ Name: "oom-kill-disable", Usage: "Disable OOM Killer", }, cli.Int64Flag{ Name: "oom-score-adj", Usage: "Tune host’s OOM preferences (-1000 to 1000)", }, cli.BoolFlag{ Name: "publish-all, P", Usage: "Publish all exposed ports to random ports", }, cli.StringSliceFlag{ Name: "publish, p", Usage: "Publish a container's `port`(s) to the host", }, cli.StringFlag{ Name: "pid", Usage: "PID namespace to use", }, cli.Int64Flag{ Name: "pids-limit", Usage: "Tune container pids limit (set -1 for unlimited)", }, cli.BoolFlag{ Name: "privileged", Usage: "Give extended privileges to this container", }, cli.BoolFlag{ Name: "read-only", Usage: "Mount the container's root filesystem as read only", }, cli.StringSliceFlag{ Name: "security-opt", Usage: "Security Options", }, cli.Int64Flag{ Name: "shm-size", Usage: "Size of /dev/shm", }, cli.BoolFlag{ Name: "tty, t", Usage: "Allocate a pseudo-TTY", }, cli.StringFlag{ Name: "user, u", Usage: "Username or UID (format: <name|uid>[:<group|gid>])", }, cli.StringFlag{ Name: "volume-driver", Usage: "Optional volume driver for the container", }, cli.StringFlag{ Name: "workdir, w", Usage: "Working directory inside the container", }, cli.StringFlag{ Name: "log-driver", Usage: "Logging driver for container", }, cli.StringSliceFlag{ Name: "log-opt", Usage: "Log driver options", }, cli.StringFlag{ Name: "uts", Usage: "UTS namespace to use", }, cli.StringSliceFlag{ Name: "volume, v", Usage: "Bind mount a volume", }, cli.StringFlag{ Name: "net", Usage: "Connect a container to a network: host, none, bridge, managed", Value: "managed", }, cli.IntFlag{ Name: "scale", Usage: "Number of containers to run", Value: 1, }, cli.BoolFlag{ Name: "schedule-global", Usage: "Run 1 container per host", }, cli.StringFlag{ Name: "stop-signal", Usage: "Signal to stop a container", }, cli.StringSliceFlag{ Name: "label,l", Usage: "Add label in the form of key=value", }, cli.StringSliceFlag{ Name: "env,e", Usage: "Set one or more environment variable in the form of key=value, key=, and key", }, cli.BoolFlag{ Name: "pull", Usage: "Always pull Image on container start", }, }, } } func serviceRun(ctx *cli.Context) error { return nil } 07070100000022000081A400000000000000000000000165D8C5D300001518000000000000000000000000000000000000001800000000cli-2.8.3/cmd/server.gopackage cmd import ( "bufio" "fmt" "os" "sort" "strconv" "strings" "github.com/pkg/errors" "github.com/rancher/cli/config" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) type serverData struct { Index int Current string Name string URL string } // ServerCommand defines the 'rancher server' sub-commands func ServerCommand() cli.Command { return cli.Command{ Name: "server", Usage: "Operations for the server", Description: `Switch or view the server currently in focus. `, Subcommands: []cli.Command{ { Name: "current", Usage: "Display the current server", Action: serverCurrent, }, { Name: "delete", Usage: "Delete a server from the local config", ArgsUsage: "[SERVER_NAME]", Description: ` The server arg is optional, if not passed in a list of available servers will be displayed and one can be selected. `, Action: serverDelete, }, { Name: "ls", Usage: "List all servers", Action: serverLs, }, { Name: "switch", Usage: "Switch to a new server", ArgsUsage: "[SERVER_NAME]", Description: ` The server arg is optional, if not passed in a list of available servers will be displayed and one can be selected. `, Action: serverSwitch, }, }, } } // serverCurrent command to display the name of the current server in the local config func serverCurrent(ctx *cli.Context) error { cf, err := loadConfig(ctx) if err != nil { return err } serverName := cf.CurrentServer currentServer, found := cf.Servers[serverName] if !found { return errors.New("Current server not set") } fmt.Printf("Name: %s URL: %s\n", serverName, currentServer.URL) return nil } // serverDelete command to delete a server from the local config func serverDelete(ctx *cli.Context) error { cf, err := loadConfig(ctx) if err != nil { return err } if err := validateServersConfig(cf); err != nil { return err } var serverName string if ctx.NArg() == 1 { serverName = ctx.Args().First() } else { serverName, err = serverFromInput(ctx, cf) if err != nil { return err } } _, ok := cf.Servers[serverName] if !ok { return errors.New("Server not found") } delete(cf.Servers, serverName) err = cf.Write() if err != nil { return err } logrus.Infof("Server %s deleted", serverName) return nil } // serverLs command to list rancher servers from the local config func serverLs(ctx *cli.Context) error { cf, err := loadConfig(ctx) if err != nil { return err } if err := validateServersConfig(cf); err != nil { return err } writer := NewTableWriter([][]string{ {"CURRENT", "Current"}, {"NAME", "Name"}, {"URL", "URL"}, }, ctx) defer writer.Close() for name, server := range cf.Servers { var current string if name == cf.CurrentServer { current = "*" } writer.Write(&serverData{ Current: current, Name: name, URL: server.URL, }) } return writer.Err() } // serverSwitch command to switch rancher server. func serverSwitch(ctx *cli.Context) error { cf, err := loadConfig(ctx) if err != nil { return err } if err := validateServersConfig(cf); err != nil { return err } var serverName string if ctx.NArg() == 1 { serverName = ctx.Args().First() } else { serverName, err = serverFromInput(ctx, cf) if err != nil { return err } } _, ok := cf.Servers[serverName] if !ok { return errors.New("Server not found") } if len(cf.Servers[serverName].Project) == 0 { logrus.Warn("No context set; some commands will not work. Run 'rancher context switch'") } cf.CurrentServer = serverName err = cf.Write() if err != nil { return err } return nil } // serverFromInput displays the list of servers from the local config and // prompt the user to select one. func serverFromInput(ctx *cli.Context, cf config.Config) (string, error) { serverNames := getServerNames(cf) displayListServers(ctx, cf) fmt.Print("Select a Server:") reader := bufio.NewReader(os.Stdin) errMessage := fmt.Sprintf("Invalid input, enter a number between 1 and %v: ", len(serverNames)) var selection int for { input, err := reader.ReadString('\n') if err != nil { return "", err } input = strings.TrimSpace(input) if input != "" { i, err := strconv.Atoi(input) if err != nil { fmt.Print(errMessage) continue } if i <= len(serverNames) && i != 0 { selection = i - 1 break } fmt.Print(errMessage) continue } } return serverNames[selection], nil } // displayListServers displays the list of rancher servers func displayListServers(ctx *cli.Context, cf config.Config) error { writer := NewTableWriter([][]string{ {"INDEX", "Index"}, {"NAME", "Name"}, {"URL", "URL"}, }, ctx) defer writer.Close() for idx, server := range getServerNames(cf) { writer.Write(&serverData{ Index: idx + 1, Name: server, URL: cf.Servers[server].URL, }) } return writer.Err() } // getServerNames returns an order slice of existing server names func getServerNames(cf config.Config) []string { var serverNames []string for server := range cf.Servers { serverNames = append(serverNames, server) } sort.Strings(serverNames) return serverNames } func validateServersConfig(cnf config.Config) error { if len(cnf.Servers) == 0 { return errors.New("no servers are currently configured") } return nil } 07070100000023000081A400000000000000000000000165D8C5D300000D69000000000000000000000000000000000000001A00000000cli-2.8.3/cmd/settings.gopackage cmd import ( managementClient "github.com/rancher/rancher/pkg/client/generated/management/v3" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) type settingHolder struct { ID string Setting managementClient.Setting } func SettingsCommand() cli.Command { return cli.Command{ Name: "settings", Aliases: []string{"setting"}, Usage: "Show settings for the current server", Description: "List get or set settings for the current Rancher server", Action: defaultAction(settingsLs), Flags: []cli.Flag{ formatFlag, }, Subcommands: []cli.Command{ { Name: "ls", Usage: "List settings", Description: "Lists all settings in the current cluster.", ArgsUsage: "[SETTINGNAME]", Action: settingsLs, Flags: []cli.Flag{ formatFlag, quietFlag, }, }, { Name: "get", Usage: "Print a setting", Action: settingGet, Flags: []cli.Flag{ formatFlag, }, }, { Name: "set", Usage: "Set the value for a setting", Action: settingSet, ArgsUsage: "[SETTINGNAME VALUE]", Flags: []cli.Flag{ formatFlag, cli.BoolFlag{ Name: "default", Usage: "Reset the setting back to it's default value. If the default value is (blank) it will be set to that.", }, }, }, }, } } func settingsLs(ctx *cli.Context) error { c, err := GetClient(ctx) if err != nil { return err } settings, err := c.ManagementClient.Setting.List(defaultListOpts(ctx)) if err != nil { return err } writer := NewTableWriter([][]string{ {"ID", "ID"}, {"NAME", "Setting.Name"}, {"VALUE", "Setting.Value"}, }, ctx) defer writer.Close() for _, setting := range settings.Data { writer.Write(&settingHolder{ ID: setting.ID, Setting: setting, }) } return writer.Err() } func settingGet(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowCommandHelp(ctx, "settings") } c, err := GetClient(ctx) if err != nil { return err } resource, err := Lookup(c, ctx.Args().First(), "setting") if err != nil { return err } setting, err := c.ManagementClient.Setting.ByID(resource.ID) if err != nil { return err } writer := NewTableWriter([][]string{ {"ID", "ID"}, {"NAME", "Setting.Name"}, {"VALUE", "Setting.Value"}, {"DEFAULT", "Setting.Default"}, {"CUSTOMIZED", "Setting.Customized"}, }, ctx) defer writer.Close() writer.Write(&settingHolder{ ID: setting.ID, Setting: *setting, }) return writer.Err() } func settingSet(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowCommandHelp(ctx, "settings") } c, err := GetClient(ctx) if err != nil { return err } resource, err := Lookup(c, ctx.Args().First(), "setting") if err != nil { return err } setting, err := c.ManagementClient.Setting.ByID(resource.ID) if err != nil { return err } update := make(map[string]string) if ctx.Bool("default") { update["value"] = setting.Default } else { update["value"] = ctx.Args().Get(1) } updatedSetting, err := c.ManagementClient.Setting.Update(setting, update) if err != nil { return err } var updatedValue string if updatedSetting.Value == "" { updatedValue = "(blank)" } else { updatedValue = updatedSetting.Value } logrus.Infof("Successfully updated setting %s with a new value of: %s", updatedSetting.Name, updatedValue) return nil } 07070100000024000081A400000000000000000000000165D8C5D30000151E000000000000000000000000000000000000001500000000cli-2.8.3/cmd/ssh.gopackage cmd import ( "archive/zip" "bytes" "crypto/tls" "crypto/x509" "encoding/json" "fmt" "io/ioutil" "net/http" "os" "os/exec" "path" "strings" "github.com/pkg/errors" "github.com/rancher/cli/cliclient" managementClient "github.com/rancher/rancher/pkg/client/generated/management/v3" "github.com/urfave/cli" ) const sshDescription = ` For any nodes created through Rancher using docker-machine, you can SSH into the node. This is not supported for any custom nodes. Examples: # SSH into a node by ID/name $ rancher ssh nodeFoo # SSH into a node by ID/name using the external IP address $ rancher ssh -e nodeFoo # SSH into a node by name but specify the login name to use $ rancher ssh -l login1 nodeFoo # SSH into a node by specifying login name and node using the @ syntax while adding a command to run $ rancher ssh login1@nodeFoo -- netstat -p tcp ` func SSHCommand() cli.Command { return cli.Command{ Name: "ssh", Usage: "SSH into a node", Description: sshDescription, Action: nodeSSH, ArgsUsage: "[NODE_ID/NODE_NAME]", Flags: []cli.Flag{ cli.BoolFlag{ Name: "external,e", Usage: "Use the external ip address of the node", }, cli.StringFlag{ Name: "login,l", Usage: "The login name", }, }, } } func nodeSSH(ctx *cli.Context) error { args := ctx.Args() if len(args) > 0 && (args[0] == "-h" || args[0] == "--help") { return cli.ShowCommandHelp(ctx, "ssh") } if ctx.NArg() == 0 { return cli.ShowCommandHelp(ctx, "ssh") } user := ctx.String("login") nodeName := ctx.Args().First() if strings.Contains(nodeName, "@") { user = strings.Split(nodeName, "@")[0] nodeName = strings.Split(nodeName, "@")[1] } args = args[1:] c, err := GetClient(ctx) if err != nil { return err } sshNode, key, err := getNodeAndKey(ctx, c, nodeName) if err != nil { return err } if user == "" { user = sshNode.SshUser } ipAddress := sshNode.IPAddress if ctx.Bool("external") { ipAddress = sshNode.ExternalIPAddress } return processExitCode(callSSH(key, ipAddress, user, args)) } func getNodeAndKey(ctx *cli.Context, c *cliclient.MasterClient, nodeName string) (managementClient.Node, []byte, error) { sshNode := managementClient.Node{} resource, err := Lookup(c, nodeName, "node") if err != nil { return sshNode, nil, err } sshNode, err = getNodeByID(ctx, c, resource.ID) if err != nil { return sshNode, nil, err } link := sshNode.Links["nodeConfig"] if link == "" { // Get the machine and use that instead. machine, err := getMachineByNodeName(ctx, c, sshNode.NodeName) if err != nil { return sshNode, nil, fmt.Errorf("failed to find SSH key for node [%s]", nodeName) } link = machine.Links["sshkeys"] } key, sshUser, err := getSSHKey(c, link, getNodeName(sshNode)) if err != nil { return sshNode, nil, err } if sshUser != "" { sshNode.SshUser = sshUser } return sshNode, key, nil } func callSSH(content []byte, ip string, user string, args []string) error { dest := fmt.Sprintf("%s@%s", user, ip) tmpfile, err := ioutil.TempFile("", "ssh") if err != nil { return err } defer os.Remove(tmpfile.Name()) if err := os.Chmod(tmpfile.Name(), 0600); err != nil { return err } _, err = tmpfile.Write(content) if err != nil { return err } if err := tmpfile.Close(); err != nil { return err } cmd := exec.Command("ssh", append([]string{"-i", tmpfile.Name(), dest}, args...)...) cmd.Stdout = os.Stdout cmd.Stdin = os.Stdin cmd.Stderr = os.Stderr return cmd.Run() } func getSSHKey(c *cliclient.MasterClient, link, nodeName string) ([]byte, string, error) { if link == "" { return nil, "", fmt.Errorf("failed to find SSH key for %s", nodeName) } req, err := http.NewRequest("GET", link, nil) if err != nil { return nil, "", err } req.SetBasicAuth(c.UserConfig.AccessKey, c.UserConfig.SecretKey) req.Header.Add("Accept-Encoding", "zip") client := &http.Client{} if c.UserConfig.CACerts != "" { roots := x509.NewCertPool() ok := roots.AppendCertsFromPEM([]byte(c.UserConfig.CACerts)) if !ok { return []byte{}, "", err } tr := &http.Transport{ TLSClientConfig: &tls.Config{ RootCAs: roots, }, } client.Transport = tr } resp, err := client.Do(req) if err != nil { return nil, "", err } defer resp.Body.Close() zipFiles, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, "", err } if resp.StatusCode != 200 { return nil, "", fmt.Errorf("%s", zipFiles) } zipReader, err := zip.NewReader(bytes.NewReader(zipFiles), resp.ContentLength) if err != nil { return nil, "", err } var sshKey []byte var sshUser string for _, file := range zipReader.File { if path.Base(file.Name) == "id_rsa" { sshKey, err = readFile(file) if err != nil { return nil, "", err } } else if path.Base(file.Name) == "config.json" { config, err := readFile(file) if err != nil { return nil, "", err } var data map[string]interface{} err = json.Unmarshal(config, &data) if err != nil { return nil, "", err } sshUser, _ = data["SSHUser"].(string) } } if len(sshKey) == 0 { return sshKey, "", errors.New("can't find private key file") } return sshKey, sshUser, nil } func readFile(file *zip.File) ([]byte, error) { r, err := file.Open() if err != nil { return nil, err } defer r.Close() return ioutil.ReadAll(r) } 07070100000025000081A400000000000000000000000165D8C5D30000039E000000000000000000000000000000000000001400000000cli-2.8.3/cmd/up.gopackage cmd import ( "io/ioutil" "github.com/rancher/cli/cliclient" client "github.com/rancher/rancher/pkg/client/generated/management/v3" "github.com/urfave/cli" ) func UpCommand() cli.Command { return cli.Command{ Name: "up", Usage: "apply compose config", Action: defaultAction(apply), Flags: []cli.Flag{ cli.StringFlag{ Name: "file,f", Usage: "The location of compose config file", }, }, } } func apply(ctx *cli.Context) error { cf, err := lookupConfig(ctx) if err != nil { return err } c, err := cliclient.NewManagementClient(cf) if err != nil { return err } filePath := ctx.String("file") compose, err := ioutil.ReadFile(filePath) if err != nil { return err } globalComposeConfig := &client.ComposeConfig{ RancherCompose: string(compose), } if _, err := c.ManagementClient.ComposeConfig.Create(globalComposeConfig); err != nil { return err } return nil } 07070100000026000081A400000000000000000000000165D8C5D3000003A0000000000000000000000000000000000000001900000000cli-2.8.3/cmd/util_ls.gopackage cmd import ( "github.com/rancher/norman/types" "github.com/urfave/cli" ) func listAllFlag() cli.BoolFlag { return cli.BoolFlag{ Name: "all,a", Usage: "Show stop/inactive and recently removed resources", } } func listSystemFlag() cli.BoolFlag { return cli.BoolFlag{ Name: "system,s", Usage: "Show system resources", } } func baseListOpts() *types.ListOpts { return &types.ListOpts{ Filters: map[string]interface{}{ "limit": -1, "all": true, }, } } func defaultListOpts(ctx *cli.Context) *types.ListOpts { listOpts := baseListOpts() if ctx != nil && !ctx.Bool("all") { listOpts.Filters["removed_null"] = "1" listOpts.Filters["state_ne"] = []string{ "inactive", "stopped", "removing", } delete(listOpts.Filters, "all") } if ctx != nil && ctx.Bool("system") { delete(listOpts.Filters, "system") } else { listOpts.Filters["system"] = "false" } return listOpts } 07070100000027000081A400000000000000000000000165D8C5D300000854000000000000000000000000000000000000001600000000cli-2.8.3/cmd/wait.gopackage cmd import ( "fmt" "strings" "time" ntypes "github.com/rancher/norman/types" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) var ( waitTypes = []string{"cluster", "app", "project", "multiClusterApp"} ) func WaitCommand() cli.Command { return cli.Command{ Name: "wait", Usage: "Wait for resources " + strings.Join(waitTypes, ", "), ArgsUsage: "[ID/NAME]", Action: defaultAction(wait), Flags: []cli.Flag{ cli.IntFlag{ Name: "timeout", Usage: "Time in seconds to wait for a resource", Value: 120, }, }, } } func wait(ctx *cli.Context) error { if ctx.NArg() == 0 { return cli.ShowCommandHelp(ctx, "wait") } c, err := GetClient(ctx) if err != nil { return err } resource, err := Lookup(c, ctx.Args().First(), waitTypes...) if err != nil { return err } mapResource := map[string]interface{}{} // Initial check shortcut err = c.ByID(resource, &mapResource) if err != nil { return err } ok, err := checkDone(resource, mapResource) if err != nil { return err } if ok { return nil } timeout := time.After(time.Duration(ctx.Int("timeout")) * time.Second) every := time.Tick(1 * time.Second) for { select { case <-timeout: return fmt.Errorf("Timeout reached %v:%v transitioningMessage: %v", resource.Type, resource.ID, mapResource["transitioningMessage"]) case <-every: err = c.ByID(resource, &mapResource) if err != nil { return err } ok, err := checkDone(resource, mapResource) if err != nil { return err } if ok { return nil } } } } func checkDone(resource *ntypes.Resource, data map[string]interface{}) (bool, error) { transitioning := fmt.Sprint(data["transitioning"]) logrus.Debugf("%s:%s transitioning=%s state=%v", resource.Type, resource.ID, transitioning, data["state"]) switch transitioning { case "yes": return false, nil case "error": if data["state"] == "provisioning" { break } return false, fmt.Errorf("%v:%v failed, transitioningMessage: %v", resource.Type, resource.ID, data["transitioningMessage"]) } return data["state"] == "active", nil } 07070100000028000081A400000000000000000000000165D8C5D300000739000000000000000000000000000000000000001800000000cli-2.8.3/cmd/writer.gopackage cmd import ( "encoding/json" "os" "text/tabwriter" "github.com/ghodss/yaml" "github.com/urfave/cli" ) type TableWriter struct { quite bool HeaderFormat string ValueFormat string err error headerPrinted bool Writer *tabwriter.Writer } func NewTableWriter(values [][]string, ctx *cli.Context) *TableWriter { t := &TableWriter{ Writer: tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0), } t.HeaderFormat, t.ValueFormat = SimpleFormat(values) if ctx.Bool("quiet") { t.HeaderFormat = "" t.ValueFormat = "{{.ID}}\n" } customFormat := ctx.String("format") if customFormat == "json" { t.HeaderFormat = "" t.ValueFormat = "json" } else if customFormat == "yaml" { t.HeaderFormat = "" t.ValueFormat = "yaml" } else if customFormat != "" { t.ValueFormat = customFormat + "\n" t.HeaderFormat = "" } return t } func (t *TableWriter) Err() error { return t.err } func (t *TableWriter) writeHeader() { if t.HeaderFormat != "" && !t.headerPrinted { t.headerPrinted = true t.err = printTemplate(t.Writer, t.HeaderFormat, struct{}{}) if t.err != nil { return } } } func (t *TableWriter) Write(obj interface{}) { if t.err != nil { return } t.writeHeader() if t.err != nil { return } if t.ValueFormat == "json" { content, err := json.Marshal(obj) t.err = err if t.err != nil { return } _, t.err = t.Writer.Write(append(content, byte('\n'))) } else if t.ValueFormat == "yaml" { content, err := yaml.Marshal(obj) t.err = err if t.err != nil { return } _, t.err = t.Writer.Write(append(content, byte('\n'))) } else { t.err = printTemplate(t.Writer, t.ValueFormat, obj) } } func (t *TableWriter) Close() error { if t.err != nil { return t.err } t.writeHeader() if t.err != nil { return t.err } return t.Writer.Flush() } 07070100000029000041ED00000000000000000000000265D8C5D300000000000000000000000000000000000000000000001100000000cli-2.8.3/config0707010000002A000081A400000000000000000000000165D8C5D300000DDB000000000000000000000000000000000000001B00000000cli-2.8.3/config/config.gopackage config import ( "encoding/json" "fmt" "net/url" "os" "path" "strings" "github.com/sirupsen/logrus" "k8s.io/client-go/tools/clientcmd/api" ) // Config holds the main config for the user type Config struct { Servers map[string]*ServerConfig //Path to the config file Path string `json:"path,omitempty"` // CurrentServer the user has in focus CurrentServer string } // ServerConfig holds the config for each server the user has setup type ServerConfig struct { AccessKey string `json:"accessKey"` SecretKey string `json:"secretKey"` TokenKey string `json:"tokenKey"` URL string `json:"url"` Project string `json:"project"` CACerts string `json:"cacert"` KubeCredentials map[string]*ExecCredential `json:"kubeCredentials"` KubeConfigs map[string]*api.Config `json:"kubeConfigs"` } // LoadFromPath attempts to load a config from the given file path. If the file // doesn't exist, an empty config is returned. func LoadFromPath(path string) (Config, error) { cf := Config{ Path: path, Servers: make(map[string]*ServerConfig), } content, err := os.ReadFile(path) if err != nil { // it's okay if the file is empty, we still return a valid config if os.IsNotExist(err) { return cf, nil } return cf, err } if err := json.Unmarshal(content, &cf); err != nil { return cf, fmt.Errorf("unmarshaling %s: %w", path, err) } cf.Path = path return cf, nil } // GetFilePermissionWarnings returns the following warnings based on the file permission: // - one warning if the file is group-readable // - one warning if the file is world-readable // We want this because configuration may have sensitive information (eg: creds). // A nil error is returned if the file doesn't exist. func GetFilePermissionWarnings(path string) ([]string, error) { info, err := os.Stat(path) if err != nil { if os.IsNotExist(err) { return []string{}, nil } return []string{}, fmt.Errorf("get file info: %w", err) } var warnings []string if info.Mode()&0040 > 0 { warnings = append(warnings, fmt.Sprintf("Rancher configuration file %s is group-readable. This is insecure.", path)) } if info.Mode()&0004 > 0 { warnings = append(warnings, fmt.Sprintf("Rancher configuration file %s is world-readable. This is insecure.", path)) } return warnings, nil } func (c Config) Write() error { err := os.MkdirAll(path.Dir(c.Path), 0700) if err != nil { return err } logrus.Infof("Saving config to %s", c.Path) p := c.Path c.Path = "" output, err := os.OpenFile(p, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return err } defer output.Close() return json.NewEncoder(output).Encode(c) } func (c Config) FocusedServer() *ServerConfig { return c.Servers[c.CurrentServer] } func (c ServerConfig) FocusedCluster() string { return strings.Split(c.Project, ":")[0] } func (c ServerConfig) KubeToken(key string) *ExecCredential { return c.KubeCredentials[key] } func (c ServerConfig) EnvironmentURL() (string, error) { url, err := baseURL(c.URL) if err != nil { return "", err } return url, nil } func baseURL(fullURL string) (string, error) { idx := strings.LastIndex(fullURL, "/v3") if idx == -1 { u, err := url.Parse(fullURL) if err != nil { return "", err } newURL := url.URL{ Scheme: u.Scheme, Host: u.Host, } return newURL.String(), nil } return fullURL[:idx], nil } 0707010000002B000081A400000000000000000000000165D8C5D30000127E000000000000000000000000000000000000002000000000cli-2.8.3/config/config_test.gopackage config import ( "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" ) const ( validFile = ` { "Servers": { "rancherDefault": { "accessKey": "the-access-key", "secretKey": "the-secret-key", "tokenKey": "the-token-key", "url": "https://example.com", "project": "cluster-id:project-id", "cacert": "", "kubeCredentials": null, "kubeConfigs": null } }, "CurrentServer": "rancherDefault" }` invalidFile = `invalid config file` ) func Test_GetFilePermissionWarnings(t *testing.T) { t.Parallel() tests := []struct { name string mode os.FileMode expectedWarnings int }{ { name: "neither group-readable nor world-readable", mode: os.FileMode(0600), expectedWarnings: 0, }, { name: "group-readable and world-readable", mode: os.FileMode(0644), expectedWarnings: 2, }, { name: "group-readable", mode: os.FileMode(0640), expectedWarnings: 1, }, { name: "world-readable", mode: os.FileMode(0604), expectedWarnings: 1, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() assert := assert.New(t) dir, err := os.MkdirTemp("", "rancher-cli-test-*") assert.NoError(err) defer os.RemoveAll(dir) path := filepath.Join(dir, "cli2.json") err = os.WriteFile(path, []byte(validFile), tt.mode) assert.NoError(err) warnings, err := GetFilePermissionWarnings(path) assert.NoError(err) assert.Len(warnings, tt.expectedWarnings) }) } } func Test_Permission(t *testing.T) { t.Parallel() // New config files should have 0600 permissions t.Run("new config file", func(t *testing.T) { t.Parallel() assert := assert.New(t) dir, err := os.MkdirTemp("", "rancher-cli-test-*") assert.NoError(err) defer os.RemoveAll(dir) path := filepath.Join(dir, "cli2.json") conf, err := LoadFromPath(path) assert.NoError(err) err = conf.Write() assert.NoError(err) info, err := os.Stat(path) assert.NoError(err) assert.Equal(os.FileMode(0600), info.Mode()) // make sure new file doesn't create permission warnings warnings, err := GetFilePermissionWarnings(path) assert.NoError(err) assert.Len(warnings, 0) }) // Already existing config files should keep their current permissions t.Run("existing config file", func(t *testing.T) { t.Parallel() assert := assert.New(t) dir, err := os.MkdirTemp("", "rancher-cli-test-*") assert.NoError(err) defer os.RemoveAll(dir) path := filepath.Join(dir, "cli2.json") err = os.WriteFile(path, []byte(validFile), 0700) assert.NoError(err) conf, err := LoadFromPath(path) assert.NoError(err) err = conf.Write() assert.NoError(err) info, err := os.Stat(path) assert.NoError(err) assert.Equal(os.FileMode(0700), info.Mode()) }) } func Test_LoadFromPath(t *testing.T) { t.Parallel() tests := []struct { name string content string expectedConf Config expectedErr bool }{ { name: "valid config", content: validFile, expectedConf: Config{ Servers: map[string]*ServerConfig{ "rancherDefault": { AccessKey: "the-access-key", SecretKey: "the-secret-key", TokenKey: "the-token-key", URL: "https://example.com", Project: "cluster-id:project-id", CACerts: "", }, }, CurrentServer: "rancherDefault", }, }, { name: "invalid config", content: invalidFile, expectedConf: Config{ Servers: map[string]*ServerConfig{}, }, expectedErr: true, }, { name: "non existing file", content: "", expectedConf: Config{ Servers: map[string]*ServerConfig{}, CurrentServer: "", }, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() assert := assert.New(t) dir, err := os.MkdirTemp("", "rancher-cli-test-*") assert.NoError(err) defer os.RemoveAll(dir) path := filepath.Join(dir, "cli2.json") // make sure the path points to the temp dir created in the test tt.expectedConf.Path = path if tt.content != "" { err = os.WriteFile(path, []byte(tt.content), 0600) assert.NoError(err) } conf, err := LoadFromPath(path) if tt.expectedErr { assert.Error(err) // We kept the old behavior of returning a valid config even in // case of an error so we assert it here. If you change this // behavior, make sure there aren't any regressions. assert.Equal(tt.expectedConf, conf) return } assert.NoError(err) assert.Equal(tt.expectedConf, conf) }) } } 0707010000002C000081A400000000000000000000000165D8C5D300000B38000000000000000000000000000000000000002000000000cli-2.8.3/config/kube_config.gopackage config import "time" // ExecCredential is used by exec-based plugins to communicate credentials to // HTTP transports. //v1beta1/types.go type ExecCredential struct { TypeMeta `json:",inline"` // Spec holds information passed to the plugin by the transport. This contains // request and runtime specific information, such as if the session is interactive. Spec ExecCredentialSpec `json:"spec,omitempty"` // Status is filled in by the plugin and holds the credentials that the transport // should use to contact the API. // +optional Status *ExecCredentialStatus `json:"status,omitempty"` } // ExecCredentialSpec holds request and runtime specific information provided by // the transport. type ExecCredentialSpec struct{} // ExecCredentialStatus holds credentials for the transport to use. // Token and ClientKeyData are sensitive fields. This data should only be // transmitted in-memory between client and exec plugin process. Exec plugin // itself should at least be protected via file permissions. type ExecCredentialStatus struct { // ExpirationTimestamp indicates a time when the provided credentials expire. // +optional ExpirationTimestamp *Time `json:"expirationTimestamp,omitempty"` // Token is a bearer token used by the client for request authentication. Token string `json:"token,omitempty"` // PEM-encoded client TLS certificates (including intermediates, if any). ClientCertificateData string `json:"clientCertificateData,omitempty"` // PEM-encoded private key for the above certificate. ClientKeyData string `json:"clientKeyData,omitempty"` } // TypeMeta describes an individual object in an API response or request // with strings representing the type of the object and its API schema version. // Structures that are versioned or persisted should inline TypeMeta. type TypeMeta struct { // Kind is a string value representing the REST resource this object represents. // Servers may infer this from the endpoint the client submits requests to. // Cannot be updated. // In CamelCase. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional Kind string `json:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"` // APIVersion defines the versioned schema of this representation of an object. // Servers should convert recognized schemas to the latest internal value, and // may reject unrecognized values. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources // +optional APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,2,opt,name=apiVersion"` } // Time is a wrapper around time.Time which supports correct // marshaling to YAML and JSON. Wrappers are provided for many // of the factory methods that the time package offers. type Time struct { time.Time `protobuf:"-"` } 0707010000002D000041ED00000000000000000000000265D8C5D300000000000000000000000000000000000000000000001200000000cli-2.8.3/contrib0707010000002E000081ED00000000000000000000000165D8C5D3000001C9000000000000000000000000000000000000001A00000000cli-2.8.3/contrib/rancher#!/bin/bash [[ -d ~/.rancher ]] || mkdir -p ~/.rancher [[ -d ~/.ssh ]] || mkdir -p ~/.ssh [[ -e ~/.ssh/known_hosts ]] || touch ~/.ssh/known_hosts [[ -e ~/.rancher/cli.json ]] || echo "{"accessKey":"","secretKey":"","url":"","environment":""}" > ~/.rancher/cli.json IMAGE=${IMAGE:-rancher/cli} exec docker run --rm -it --net host -v ~/.rancher/cli.json:/root/.rancher/cli.json -v ~/.ssh/known_hosts:/root/.ssh/known_hosts -v $(pwd):/mnt ${IMAGE} "$@" 0707010000002F000081A400000000000000000000000165D8C5D300000A91000000000000000000000000000000000000001100000000cli-2.8.3/go.modmodule github.com/rancher/cli go 1.20 replace k8s.io/client-go => k8s.io/client-go v0.20.1 require ( github.com/c-bata/go-prompt v0.2.6 github.com/docker/docker v1.6.1 github.com/ghodss/yaml v1.0.0 github.com/gorilla/websocket v1.4.2 github.com/grantae/certinfo v0.0.0-20170412194111-59d56a35515b github.com/hashicorp/go-version v1.2.1 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 github.com/rancher/norman v0.0.0-20200820172041-261460ee9088 github.com/rancher/rancher/pkg/client v0.0.0-20211110212758-cc2b8beb1473 github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.0 github.com/urfave/cli v1.22.5 golang.org/x/crypto v0.11.0 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/yaml.v2 v2.4.0 k8s.io/client-go v12.0.0+incompatible ) require ( github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v0.4.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.4.3 // indirect github.com/google/go-cmp v0.5.2 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/imdario/mergo v0.3.7 // indirect github.com/json-iterator/go v1.1.10 // indirect github.com/kr/pretty v0.2.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-colorable v0.1.7 // indirect github.com/mattn/go-isatty v0.0.12 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mattn/go-tty v0.0.3 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/pkg/term v1.2.0-beta.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rancher/wrangler v0.7.4-security1 // indirect github.com/russross/blackfriday/v2 v2.0.1 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/net v0.12.0 // indirect golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect golang.org/x/sys v0.10.0 // indirect golang.org/x/term v0.10.0 // indirect golang.org/x/text v0.11.0 // indirect golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect google.golang.org/appengine v1.6.5 // indirect google.golang.org/protobuf v1.25.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.0 // indirect k8s.io/apimachinery v0.21.0 // indirect k8s.io/klog/v2 v2.8.0 // indirect k8s.io/utils v0.0.0-20201110183641-67b214c5f920 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.1.0 // indirect sigs.k8s.io/yaml v1.2.0 // indirect ) 07070100000030000081A400000000000000000000000165D8C5D30001590B000000000000000000000000000000000000001100000000cli-2.8.3/go.sumcloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/c-bata/go-prompt v0.2.6 h1:POP+nrHE+DfLYx370bedwNhsqmpCUynWPxuHi0C5vZI= github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v1.6.1 h1:4xYASHy5cScPkLD7PO0uTmnVc860m9NarPN1X8zeMe8= github.com/docker/docker v1.6.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e/go.mod h1:CgNC6SGbT+Xb8wGGvzilttZL1mc5sQ/5KkcxsZttMIk= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b/go.mod h1:NAJj0yf/KaRKURN6nyi7A9IZydMivZEm9oQLWNjfKDc= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho= github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grantae/certinfo v0.0.0-20170412194111-59d56a35515b h1:NGgE5ELokSf2tZ/bydyDUKrvd/jP8lrAoPNeBuMOTOk= github.com/grantae/certinfo v0.0.0-20170412194111-59d56a35515b/go.mod h1:zT/uzhdQGTqlwTq7Lpbj3JoJQWfPfIJ1tE0OidAmih8= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/maruel/panicparse v0.0.0-20171209025017-c0182c169410/go.mod h1:nty42YY5QByNC5MM7q/nj938VbgPU7avs45z6NClpxI= github.com/maruel/ut v1.0.0/go.mod h1:I68ffiAt5qre9obEVTy7S2/fj2dJku2NYLvzPuY0gqE= github.com/matryer/moq v0.0.0-20200607124540-4638a53893e6/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/paulmach/orb v0.1.3/go.mod h1:VFlX/8C+IQ1p6FTRRKzKoOPJnvEtA5G0Veuqwbu//Vk= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/term v1.2.0-beta.2 h1:L3y/h2jkuBVFdWiJvNfYfKmzcCnILw7mJWm2JQuMppw= github.com/pkg/term v1.2.0-beta.2/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d/go.mod h1:7DPO4domFU579Ga6E61sB9VFNaniPVwJP5C4bBCu3wA= github.com/rancher/lasso v0.0.0-20200515155337-a34e1e26ad91/go.mod h1:G6Vv2aj6xB2YjTVagmu4NkhBvbE8nBcGykHRENH6arI= github.com/rancher/lasso v0.0.0-20200820172840-0e4cc0ef5cb0/go.mod h1:OhBBBO1pBwYp0hacWdnvSGOj+XE9yMLOLnaypIlic18= github.com/rancher/lasso v0.0.0-20200905045615-7fcb07d6a20b/go.mod h1:OhBBBO1pBwYp0hacWdnvSGOj+XE9yMLOLnaypIlic18= github.com/rancher/norman v0.0.0-20200820172041-261460ee9088 h1:7Y2H44mpeA3ZCiGkdauhRo7EN+B4a3bbBg9dkNY4HO8= github.com/rancher/norman v0.0.0-20200820172041-261460ee9088/go.mod h1:W9LfZ96OfjkWSGTy2DUqYPt47Jpzrs7eM0i3AAx6fOI= github.com/rancher/rancher/pkg/client v0.0.0-20211110212758-cc2b8beb1473 h1:ApjHsieVqHAZB8NZAt1/ggKu91QCpUo0Wp/DiWHYSkc= github.com/rancher/rancher/pkg/client v0.0.0-20211110212758-cc2b8beb1473/go.mod h1:UKAFuyRUIHtnss0+u3/21KZzsD4F/1IaHCBazsHKaww= github.com/rancher/wrangler v0.6.2-0.20200515155908-1923f3f8ec3f/go.mod h1:NmtmlLkchboIksYJuBemwcP4RBfv8FpeyhVoWXB9Wdc= github.com/rancher/wrangler v0.6.2-0.20200820173016-2068de651106/go.mod h1:iKqQcYs4YSDjsme52OZtQU4jHPmLlIiM93aj2c8c/W8= github.com/rancher/wrangler v0.7.4-security1 h1:RQR56EZVQ/ow96pK9otKjSqs1jEQeVl2Mb2yElPbkHA= github.com/rancher/wrangler v0.7.4-security1/go.mod h1:goezjesEKwMxHLfltdjg9DW0xWV7txQee6vOuSDqXAI= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.3-0.20181224173747-660f15d67dbb/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.starlark.net v0.0.0-20190528202925-30ae18b8564f/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180117170059-2c42eef0765b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.0.0/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0HRNyQ+8KTEERVsK0PW48= k8s.io/api v0.0.0-20191214185829-ca1d04f8b0d3/go.mod h1:itOjKREfmUTvcjantxOsyYU5mbFsU7qUnyUuRfF5+5M= k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4= k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8= k8s.io/api v0.18.8/go.mod h1:d/CXqwWv+Z2XEG1LgceeDmHQwpUJhROPx16SlxJgERY= k8s.io/api v0.20.1 h1:ud1c3W3YNzGd6ABJlbFfKXBKXO+1KdGfcgGGNgFR03E= k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY= k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs= k8s.io/apiextensions-apiserver v0.18.0/go.mod h1:18Cwn1Xws4xnWQNC00FLq1E350b9lUF+aOdIWDOZxgo= k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4= k8s.io/apimachinery v0.0.0-20191214185652-442f8fb2f03a/go.mod h1:Ng1IY8TS7sC44KJxT/WUR6qFRfWwahYYYpNXyYRKOCY= k8s.io/apimachinery v0.0.0-20191216025728-0ee8b4573e3a/go.mod h1:Ng1IY8TS7sC44KJxT/WUR6qFRfWwahYYYpNXyYRKOCY= k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.21.0 h1:3Fx+41if+IRavNcKOz09FwEXDBG6ORh6iMsTSelhkMA= k8s.io/apimachinery v0.21.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY= k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg= k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo= k8s.io/apiserver v0.18.0/go.mod h1:3S2O6FeBBd6XTo0njUrLxiqk8GNy6wWOftjhJcXYnjw= k8s.io/cli-runtime v0.0.0-20191214191754-e6dc6d5c8724/go.mod h1:wzlq80lvjgHW9if6MlE4OIGC86MDKsy5jtl9nxz/IYY= k8s.io/cli-runtime v0.17.2/go.mod h1:aa8t9ziyQdbkuizkNLAw3qe3srSyWh9zlSB7zTqRNPI= k8s.io/client-go v0.20.1 h1:Qquik0xNFbK9aUG92pxHYsyfea5/RPO9o9bSywNor+M= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE= k8s.io/code-generator v0.0.0-20191214185510-0b9b3c99f9f2/go.mod h1:BjGKcoq1MRUmcssvHiSxodCco1T6nVIt4YeCT5CMSao= k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= k8s.io/component-base v0.0.0-20190918160511-547f6c5d7090/go.mod h1:933PBGtQFJky3TEwYx4aEPZ4IxqhWh3R6DCmzqIn1hA= k8s.io/component-base v0.0.0-20191214190519-d868452632e2/go.mod h1:wupxkh1T/oUDqyTtcIjiEfpbmIHGm8By/vqpSKC6z8c= k8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs= k8s.io/component-base v0.18.0/go.mod h1:u3BCg0z1uskkzrnAKFzulmYaEpZF7XC9Pf/uFyb1v2c= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.8.0 h1:Q3gmuM9hKEjefWFFYF0Mat+YyFJvsUyYuwyNNJ5C9Ts= k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/kube-aggregator v0.18.0/go.mod h1:ateewQ5QbjMZF/dihEFXwaEwoA4v/mayRvzfmvb6eqI= k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd/go.mod h1:9ehGcuUGjXVZh0qbYSB0vvofQw2JQe6c6cO0k4wu/Oo= k8s.io/metrics v0.0.0-20191214191643-6b1944c9f765/go.mod h1:5V7rewilItwK0cz4nomU0b3XCcees2Ka5EBYWS1HBeM= k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= sigs.k8s.io/cli-utils v0.16.0/go.mod h1:9Jqm9K2W6ShhCxsEuaz6HSRKKOXigPUx3ZfypGgxBLY= sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns= sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= sigs.k8s.io/kustomize/kyaml v0.4.0/go.mod h1:XJL84E6sOFeNrQ7CADiemc1B0EjIxHo3OhW4o1aJYNw= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA= sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.1.0 h1:C4r9BgJ98vrKnnVCjwCSXcWjWe0NKcUQkmzDXZXGwH8= sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= 07070100000031000081A400000000000000000000000165D8C5D300000FF7000000000000000000000000000000000000001200000000cli-2.8.3/main.gopackage main import ( "os" "regexp" "strings" "github.com/pkg/errors" "github.com/rancher/cli/cmd" "github.com/rancher/cli/config" rancherprompt "github.com/rancher/cli/rancher_prompt" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) var VERSION = "dev" var AppHelpTemplate = `{{.Usage}} Usage: {{.Name}} {{if .Flags}}[OPTIONS] {{end}}COMMAND [arg...] Version: {{.Version}} {{if .Flags}} Options: {{range .Flags}}{{if .Hidden}}{{else}}{{.}} {{end}}{{end}}{{end}} Commands: {{range .Commands}}{{.Name}}{{with .Aliases}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} {{end}} Run '{{.Name}} COMMAND --help' for more information on a command. ` var CommandHelpTemplate = `{{.Usage}} {{if .Description}}{{.Description}}{{end}} Usage: {{.HelpName}} {{if .Flags}}[OPTIONS] {{end}}{{if ne "None" .ArgsUsage}}{{if ne "" .ArgsUsage}}{{.ArgsUsage}}{{else}}[arg...]{{end}}{{end}} {{if .Flags}}Options:{{range .Flags}} {{.}}{{end}}{{end}} ` var SubcommandHelpTemplate = `{{.Usage}} {{if .Description}}{{.Description}}{{end}} Usage: {{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} Commands:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{end}}{{range .VisibleCommands}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}} {{end}}{{if .VisibleFlags}} Options: {{range .VisibleFlags}}{{.}} {{end}}{{end}} ` func main() { if err := mainErr(); err != nil { logrus.Fatal(err) } } func mainErr() error { cli.AppHelpTemplate = AppHelpTemplate cli.CommandHelpTemplate = CommandHelpTemplate cli.SubcommandHelpTemplate = SubcommandHelpTemplate app := cli.NewApp() app.Name = "rancher" app.Usage = "Rancher CLI, managing containers one UTF-8 character at a time" app.Before = func(ctx *cli.Context) error { if ctx.GlobalBool("debug") { logrus.SetLevel(logrus.DebugLevel) } path := cmd.GetConfigPath(ctx) warnings, err := config.GetFilePermissionWarnings(path) if err != nil { // We don't want to block the execution of the CLI in that case logrus.Errorf("Unable to verify config file permission: %s. Continuing.", err) } for _, warning := range warnings { logrus.Warning(warning) } return nil } app.Version = VERSION app.Author = "Rancher Labs, Inc." app.Email = "" app.Flags = []cli.Flag{ cli.BoolFlag{ Name: "debug", Usage: "Debug logging", }, cli.StringFlag{ Name: "config, c", Usage: "Path to rancher config", EnvVar: "RANCHER_CONFIG_DIR", Value: os.ExpandEnv("${HOME}/.rancher"), }, } app.Commands = []cli.Command{ cmd.AppCommand(), cmd.CatalogCommand(), cmd.ClusterCommand(), cmd.ContextCommand(), cmd.GlobalDNSCommand(), cmd.InspectCommand(), cmd.KubectlCommand(), cmd.LoginCommand(), cmd.MachineCommand(), cmd.MultiClusterAppCommand(), cmd.NamespaceCommand(), cmd.NodeCommand(), cmd.ProjectCommand(), cmd.PsCommand(), cmd.ServerCommand(), cmd.SettingsCommand(), cmd.SSHCommand(), cmd.UpCommand(), cmd.WaitCommand(), cmd.CredentialCommand(), } for _, com := range app.Commands { rancherprompt.Commands[com.Name] = com rancherprompt.Commands[com.ShortName] = com } rancherprompt.Flags = app.Flags parsed, err := parseArgs(os.Args) if err != nil { logrus.Error(err) os.Exit(1) } return app.Run(parsed) } var singleAlphaLetterRegxp = regexp.MustCompile("[a-zA-Z]") func parseArgs(args []string) ([]string, error) { result := []string{} for _, arg := range args { if strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") && len(arg) > 1 { for i, c := range arg[1:] { if string(c) == "=" { if i < 1 { return nil, errors.New("invalid input with '-' and '=' flag") } result[len(result)-1] = result[len(result)-1] + arg[i+1:] break } else if singleAlphaLetterRegxp.MatchString(string(c)) { result = append(result, "-"+string(c)) } else { return nil, errors.Errorf("invalid input %v in flag", string(c)) } } } else { result = append(result, arg) } } return result, nil } 07070100000032000081A400000000000000000000000165D8C5D30000055B000000000000000000000000000000000000001700000000cli-2.8.3/main_test.gopackage main import ( "testing" "gopkg.in/check.v1" ) // Hook up gocheck into the "go test" runner. func Test(t *testing.T) { check.TestingT(t) } type MainTestSuite struct { } var _ = check.Suite(&MainTestSuite{}) func (m *MainTestSuite) SetUpSuite(c *check.C) { } func (m *MainTestSuite) TestParseArgs(c *check.C) { input := [][]string{ {"rancher", "run", "--debug", "-itd"}, {"rancher", "run", "--debug", "-itf=b"}, {"rancher", "run", "--debug", "-itd#"}, {"rancher", "run", "--debug", "-f=b"}, {"rancher", "run", "--debug", "-=b"}, {"rancher", "run", "--debug", "-"}, } r0, err := parseArgs(input[0]) if err != nil { c.Fatal(err) } c.Assert(r0, check.DeepEquals, []string{"rancher", "run", "--debug", "-i", "-t", "-d"}) r1, err := parseArgs(input[1]) if err != nil { c.Fatal(err) } c.Assert(r1, check.DeepEquals, []string{"rancher", "run", "--debug", "-i", "-t", "-f=b"}) _, err = parseArgs(input[2]) if err == nil { c.Fatal("should raise error") } r3, err := parseArgs(input[3]) if err != nil { c.Fatal(err) } c.Assert(r3, check.DeepEquals, []string{"rancher", "run", "--debug", "-f=b"}) _, err = parseArgs(input[4]) if err == nil { c.Fatal("should raise error") } r5, err := parseArgs(input[5]) if err != nil { c.Fatal(err) } c.Assert(r5, check.DeepEquals, []string{"rancher", "run", "--debug", "-"}) } 07070100000033000041ED00000000000000000000000265D8C5D300000000000000000000000000000000000000000000001200000000cli-2.8.3/monitor07070100000034000081A400000000000000000000000165D8C5D300000C59000000000000000000000000000000000000001D00000000cli-2.8.3/monitor/monitor.gopackage monitor import ( "encoding/json" "fmt" "sync" "time" "github.com/gorilla/websocket" "github.com/patrickmn/go-cache" "github.com/rancher/cli/cliclient" "github.com/sirupsen/logrus" ) type Event struct { Name string `json:"name"` ResourceType string `json:"resourceType"` ResourceID string `json:"resourceId"` Data map[string]interface{} `json:"data"` } type Monitor struct { sync.Mutex c *cliclient.MasterClient cache *cache.Cache subCounter int subscriptions map[int]*Subscription } func (m *Monitor) Subscribe() *Subscription { m.Lock() defer m.Unlock() m.subCounter++ sub := &Subscription{ id: m.subCounter, C: make(chan *Event, 1024), } m.subscriptions[sub.id] = sub return sub } func (m *Monitor) Unsubscribe(sub *Subscription) { m.Lock() defer m.Unlock() close(sub.C) delete(m.subscriptions, sub.id) } type Subscription struct { id int C chan *Event } func New(c *cliclient.MasterClient) *Monitor { return &Monitor{ c: c, cache: cache.New(5*time.Minute, 30*time.Second), subscriptions: map[int]*Subscription{}, } } //func (m *Monitor) Start() error { // schema, err := m.c.ManagementClient.DynamicSchema.ByID("subscribe") // if nil != err { // return fmt.Errorf("not authorized to subscribe") // } // // urlString := schema.Links["collection"] // u, err := url.Parse(urlString) // if err != nil { // return err // } // // switch u.Scheme { // case "http": // u.Scheme = "ws" // case "https": // u.Scheme = "wss" // } // // q := u.Query() // q.Add("eventNames", "resource.change") // q.Add("eventNames", "service.kubernetes.change") // // u.RawQuery = q.Encode() // // conn, resp, err := m.c.Websocket(u.String(), nil) // if err != nil { // return err // } // // if resp.StatusCode != 101 { // return fmt.Errorf("Bad status code: %d %s", resp.StatusCode, resp.Status) // } // // logrus.Debugf("Connected to: %s", u.String()) // // return m.watch(conn) //} func (m *Monitor) Get(resourceType, resourceID string, obj interface{}) (bool, error) { val, ok := m.cache.Get(key(resourceType, resourceID)) if !ok { return ok, nil } if val == nil { return true, nil } content, err := json.Marshal(val) if err != nil { return ok, err } return true, json.Unmarshal(content, obj) } func key(a, b string) string { return fmt.Sprintf("%s:%s", a, b) } func (m *Monitor) put(resourceType, resourceID string, event *Event) { if resourceType == "" && resourceID == "" { return } m.cache.Replace(key(resourceType, resourceID), event.Data["resource"], cache.DefaultExpiration) m.Lock() defer m.Unlock() for _, sub := range m.subscriptions { sub.C <- event } } func (m *Monitor) watch(conn *websocket.Conn) error { for { v := Event{} _, r, err := conn.NextReader() if err != nil { return err } if err := json.NewDecoder(r).Decode(&v); err != nil { logrus.Errorf("Failed to parse json in message") continue } logrus.Debugf("Event: %s %s %s %v", v.Name, v.ResourceType, v.ResourceID, v.Data) m.put(v.ResourceType, v.ResourceID, &v) } } 07070100000035000041ED00000000000000000000000265D8C5D300000000000000000000000000000000000000000000001200000000cli-2.8.3/package07070100000036000081A400000000000000000000000165D8C5D300000214000000000000000000000000000000000000001D00000000cli-2.8.3/package/DockerfileFROM registry.suse.com/bci/bci-base:15.5 ARG user=cli RUN zypper -n update && \ zypper -n install ca-certificates openssh-clients && \ zypper clean -a && rm -rf /tmp/* /var/tmp/* /usr/share/doc/packages/* /usr/share/doc/manual/* /var/log/* RUN echo "$user:x:1000:1000::/home/$user:/bin/bash" >> /etc/passwd && \ echo "$user:x:1000:" >> /etc/group && \ mkdir /home/$user && \ chown -R $user:$user /home/$user COPY rancher /usr/bin/ WORKDIR /home/$user USER 1000:1000 ENTRYPOINT ["rancher"] CMD ["--help"] 07070100000037000041ED00000000000000000000000265D8C5D300000000000000000000000000000000000000000000001900000000cli-2.8.3/rancher_prompt07070100000038000081A400000000000000000000000165D8C5D300000016000000000000000000000000000000000000002300000000cli-2.8.3/rancher_prompt/client.gopackage rancherprompt 07070100000039000081A400000000000000000000000165D8C5D300001357000000000000000000000000000000000000002600000000cli-2.8.3/rancher_prompt/completer.gopackage rancherprompt import ( "strings" "github.com/c-bata/go-prompt" "github.com/urfave/cli" ) // thanks for the idea from github.com/c-bata/kube-prompt var ( Commands = map[string]cli.Command{} Flags = []cli.Flag{} ) func Completer(d prompt.Document) []prompt.Suggest { if d.TextBeforeCursor() == "" { return []prompt.Suggest{} } args := strings.Split(d.TextBeforeCursor(), " ") w := d.GetWordBeforeCursor() // If PIPE is in text before the cursor, returns empty suggestions. for i := range args { if args[i] == "|" { return []prompt.Suggest{} } } // If word before the cursor starts with "-", returns CLI flag options. if strings.HasPrefix(w, "-") { return optionCompleter(args, strings.HasPrefix(w, "--")) } return argumentsCompleter(excludeOptions(args)) } func argumentsCompleter(args []string) []prompt.Suggest { suggests := []prompt.Suggest{} for name, command := range Commands { if command.Name != "prompt" { suggests = append(suggests, prompt.Suggest{ Text: name, Description: command.Usage, }) } } if len(args) <= 1 { return prompt.FilterHasPrefix(suggests, args[0], true) } switch args[0] { case "docker": if len(args) == 3 { subcommands := []prompt.Suggest{ {Text: "attach", Description: "Attach local standard input, output, and error streams to a running container"}, {Text: "build", Description: "Build an image from a Dockerfile"}, {Text: "commit", Description: "Create a new image from a container’s changes"}, {Text: "cp", Description: "Copy files/folders between a container and the local filesystem"}, {Text: "create", Description: "Create a new container"}, {Text: "events", Description: "Get real time events from the server"}, {Text: "exec", Description: "Run a command in a running container"}, {Text: "export", Description: "Export a container’s filesystem as a tar archive"}, {Text: "image", Description: "Manage images"}, {Text: "images", Description: "List images"}, {Text: "import", Description: "Import the contents from a tarball to create a filesystem image"}, {Text: "info", Description: "Display system-wide information"}, {Text: "inspect", Description: "Return low-level information on Docker objects"}, {Text: "kill", Description: "Kill one or more running containers"}, {Text: "load", Description: "Load an image from a tar archive or STDIN"}, {Text: "login", Description: "Log in to a Docker registry"}, {Text: "logout", Description: "Log out from a Docker registry"}, {Text: "logs", Description: "Fetch the logs of a container"}, {Text: "network", Description: "Manage networks"}, {Text: "pause", Description: "Pause all processes within one or more containers"}, {Text: "plugin", Description: "Manage plugins"}, {Text: "port", Description: "List port mappings or a specific mapping for the container"}, {Text: "ps", Description: "List containers"}, {Text: "pull", Description: "Pull an image or a repository from a registry"}, {Text: "push", Description: "Push an image or a repository to a registry"}, {Text: "rename", Description: "Rename a container"}, {Text: "restart", Description: "Restart one or more containers"}, {Text: "rm", Description: "Remove one or more containers"}, {Text: "rmi", Description: "Remove one or more images"}, {Text: "run", Description: "Run a command in a new container"}, {Text: "save", Description: "Save one or more images to a tar archive (streamed to STDOUT by default)"}, {Text: "search", Description: "Search the Docker Hub for images"}, {Text: "start", Description: "Start one or more stopped containers"}, {Text: "stats", Description: "Display a live stream of container(s) resource usage statistics"}, {Text: "stop", Description: "Stop one or more running containers"}, {Text: "tag", Description: "Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE"}, {Text: "top", Description: "Display the running processes of a container"}, {Text: "unpause", Description: "Unpause all processes within one or more containers"}, {Text: "update", Description: "Update configuration of one or more containers"}, {Text: "version", Description: "Show the Docker version information"}, {Text: "volume", Description: "Manage volumes"}, {Text: "wait", Description: "Block until one or more containers stop, then print their exit codes"}, } return prompt.FilterHasPrefix(subcommands, args[2], true) } default: if len(args) == 2 { return prompt.FilterHasPrefix(getSubcommandSuggest(args[0]), args[1], true) } } return []prompt.Suggest{} } func getSubcommandSuggest(name string) []prompt.Suggest { subcommands := []prompt.Suggest{} for _, com := range Commands[name].Subcommands { subcommands = append(subcommands, prompt.Suggest{ Text: com.Name, Description: com.Usage, }) } return subcommands } 0707010000003A000081A400000000000000000000000165D8C5D3000002E7000000000000000000000000000000000000002500000000cli-2.8.3/rancher_prompt/executor.gopackage rancherprompt import ( "fmt" "os" "os/exec" "strings" ) func Executor(s string) { s = strings.TrimSpace(s) if s == "" { return } if s == "exit" { os.Exit(0) return } //hack for rancher docker // docker --host 1h1 ps -> --host 1h1 docker ps if strings.HasPrefix(s, "docker ") { parts := strings.Split(s, " ") if len(parts) > 2 && (parts[1] == "--host" || parts[1] == "-host") { t := parts[0] parts[0] = parts[1] parts[1] = parts[2] parts[2] = t s = strings.Join(parts, " ") } } cmd := exec.Command("/bin/sh", "-c", "rancher "+s) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { fmt.Printf("Got error: %s\n", err.Error()) } return } 0707010000003B000081A400000000000000000000000165D8C5D300000A27000000000000000000000000000000000000002400000000cli-2.8.3/rancher_prompt/options.gopackage rancherprompt import ( "strings" "github.com/c-bata/go-prompt" "github.com/urfave/cli" ) func optionCompleter(args []string, long bool) []prompt.Suggest { l := len(args) if l <= 1 { if long { return prompt.FilterHasPrefix(optionHelp, "--", false) } return optionHelp } flagGlobal := getGlobalFlag() var suggests []prompt.Suggest commandArgs := excludeOptions(args) if command, ok := Commands[commandArgs[0]]; ok { if len(commandArgs) > 1 && len(command.Subcommands) > 0 { for _, sub := range command.Subcommands { if sub.Name == commandArgs[1] { suggests = append(getFlagsSuggests(sub), flagGlobal...) break } } } else { suggests = append(getFlagsSuggests(command), flagGlobal...) } } if long { return prompt.FilterContains( prompt.FilterHasPrefix(suggests, "--", false), strings.TrimLeft(args[l-1], "--"), true, ) } return prompt.FilterHasPrefix(suggests, strings.TrimLeft(args[l-1], "-"), true) } var optionHelp = []prompt.Suggest{ {Text: "-h", Description: "Help Command"}, {Text: "--help", Description: "Help Command"}, } func excludeOptions(args []string) []string { ret := make([]string, 0, len(args)) for i := range args { if !strings.HasPrefix(args[i], "-") { ret = append(ret, args[i]) } } return ret } func getGlobalFlag() []prompt.Suggest { suggests := []prompt.Suggest{} for _, flag := range Flags { name := flag.GetName() parts := strings.Split(name, ",") for _, part := range parts { prefix := "--" if len(parts) == 1 { prefix = "-" } suggests = append(suggests, prompt.Suggest{ Text: prefix + strings.TrimSpace(part), Description: getUsageForFlag(flag), }) } } suggests = append(suggests, optionHelp...) return suggests } func getFlagsSuggests(command cli.Command) []prompt.Suggest { suggests := []prompt.Suggest{} for _, f := range command.Flags { name := f.GetName() parts := strings.Split(name, ",") for _, part := range parts { prefix := "--" if len(parts) == 1 { prefix = "-" } suggests = append(suggests, prompt.Suggest{ Text: prefix + strings.TrimSpace(part), Description: getUsageForFlag(f), }) } } return suggests } func getUsageForFlag(flag cli.Flag) string { if v, ok := flag.(cli.StringFlag); ok { return v.Usage } if v, ok := flag.(cli.StringSliceFlag); ok { return v.Usage } if v, ok := flag.(cli.IntFlag); ok { return v.Usage } if v, ok := flag.(cli.IntSliceFlag); ok { return v.Usage } if v, ok := flag.(cli.BoolFlag); ok { return v.Usage } return "" } 0707010000003C000041ED00000000000000000000000265D8C5D300000000000000000000000000000000000000000000001200000000cli-2.8.3/scripts0707010000003D000081ED00000000000000000000000165D8C5D30000036D000000000000000000000000000000000000001800000000cli-2.8.3/scripts/build#!/bin/bash -e source $(dirname $0)/version cd $(dirname $0)/.. declare -A OS_ARCH_ARG OS_PLATFORM_ARG=(linux windows darwin) OS_ARCH_ARG[linux]="amd64 arm s390x" OS_ARCH_ARG[windows]="386 amd64" OS_ARCH_ARG[darwin]="amd64" CGO_ENABLED=0 go build -ldflags="-w -s -X main.VERSION=$VERSION -extldflags -static" -o bin/rancher if [ -n "$CROSS" ]; then rm -rf build/bin mkdir -p build/bin for OS in ${OS_PLATFORM_ARG[@]}; do for ARCH in ${OS_ARCH_ARG[${OS}]}; do OUTPUT_BIN="build/bin/rancher_$OS-$ARCH" if test "$OS" = "windows"; then OUTPUT_BIN="${OUTPUT_BIN}.exe" fi echo "Building binary for $OS/$ARCH..." GOARCH=$ARCH GOOS=$OS CGO_ENABLED=0 go build \ -ldflags="-w -X main.VERSION=$VERSION" \ -o ${OUTPUT_BIN} ./ done done fi 0707010000003E000081ED00000000000000000000000165D8C5D30000004A000000000000000000000000000000000000001500000000cli-2.8.3/scripts/ci#!/bin/bash set -e cd $(dirname $0) ./build ./test ./validate ./package 0707010000003F000081ED00000000000000000000000165D8C5D30000005D000000000000000000000000000000000000002100000000cli-2.8.3/scripts/copy-latest.sh#!/bin/bash gsutil -m rsync -r dist/artifacts/latest/ gs://releases.rancher.com/cli/latest 07070100000040000081ED00000000000000000000000165D8C5D30000004D000000000000000000000000000000000000002200000000cli-2.8.3/scripts/copy-release.sh#!/bin/bash gsutil -m cp -r dist/artifacts/v* gs://releases.rancher.com/cli 07070100000041000081ED00000000000000000000000165D8C5D3000000A1000000000000000000000000000000000000001800000000cli-2.8.3/scripts/entry#!/bin/bash set -e trap "chown -R $DAPPER_UID:$DAPPER_GID ." exit mkdir -p bin build/bin dist if [ -e ./scripts/$1 ]; then ./scripts/"$@" else "$@" fi 07070100000042000081ED00000000000000000000000165D8C5D300000775000000000000000000000000000000000000001A00000000cli-2.8.3/scripts/package#!/bin/bash set -e source $(dirname $0)/version cd $(dirname $0)/.. DIST=$(pwd)/dist/artifacts mkdir -p $DIST/${VERSION} $DIST/latest for i in build/bin/*; do if [ ! -e $i ]; then continue fi BASE=build/archive DIR=${BASE}/rancher-${VERSION} rm -rf $BASE mkdir -p $BASE $DIR EXT= if [[ $i =~ .*windows.* ]]; then EXT=.exe fi cp $i ${DIR}/rancher${EXT} arch=$(echo $i | cut -f2 -d_) mkdir -p $DIST/${VERSION}/binaries/$arch mkdir -p $DIST/latest/binaries/$arch cp $i $DIST/${VERSION}/binaries/$arch/rancher${EXT} if [ -z "${EXT}" ]; then gzip -c $i > $DIST/${VERSION}/binaries/$arch/rancher.gz xz -c $i > $DIST/${VERSION}/binaries/$arch/rancher.xz fi rm -rf $DIST/latest/binaries/$arch mkdir -p $DIST/latest/binaries cp -rf $DIST/${VERSION}/binaries/$arch $DIST/latest/binaries ( cd $BASE NAME=$(basename $i | sed 's/_/-/g') if [ -z "$EXT" ]; then tar cvzf $DIST/${VERSION}/${NAME}-${VERSION}.tar.gz . cp $DIST/${VERSION}/${NAME}-${VERSION}.tar.gz $DIST/latest/${NAME}.tar.gz tar cvJf $DIST/${VERSION}/${NAME}-${VERSION}.tar.xz . cp $DIST/${VERSION}/${NAME}-${VERSION}.tar.xz $DIST/latest/${NAME}.tar.xz else NAME=$(echo $NAME | sed 's/'${EXT}'//g') zip -r $DIST/${VERSION}/${NAME}-${VERSION}.zip * cp $DIST/${VERSION}/${NAME}-${VERSION}.zip $DIST/latest/${NAME}.zip fi ) done ARCH=${ARCH:-"amd64"} SUFFIX="" [ "${ARCH}" != "amd64" ] && SUFFIX="_${ARCH}" cd package TAG=${TAG:-${VERSION}${SUFFIX}} REPO=${REPO:-rancher} if echo $TAG | grep -q dirty; then TAG=dev fi if [ -n "$DRONE_TAG" ]; then TAG=$DRONE_TAG fi cp ../bin/rancher . docker build -t ${REPO}/cli:${TAG} . echo ${REPO}/cli:${TAG} > ../dist/images echo Built ${REPO}/cli:${TAG} 07070100000043000081ED00000000000000000000000165D8C5D300000023000000000000000000000000000000000000001A00000000cli-2.8.3/scripts/release#!/bin/bash exec $(dirname $0)/ci 07070100000044000081ED00000000000000000000000165D8C5D300000063000000000000000000000000000000000000001700000000cli-2.8.3/scripts/test#!/bin/bash set -e cd $(dirname $0)/.. echo Running tests go test -race -cover -tags=test ./... 07070100000045000081ED00000000000000000000000165D8C5D300000110000000000000000000000000000000000000001B00000000cli-2.8.3/scripts/validate#!/bin/bash set -e cd $(dirname $0)/.. echo Running: golangci-lint golangci-lint run echo Tidying up modules go mod tidy echo Verifying modules go mod verify if [ -n "$(git status --porcelain --untracked-files=no)" ]; then echo "Encountered dirty repo!" exit 1 fi07070100000046000081ED00000000000000000000000165D8C5D300000134000000000000000000000000000000000000001A00000000cli-2.8.3/scripts/version#!/bin/bash if [ -n "$(git status --porcelain --untracked-files=no)" ]; then DIRTY="-dirty" fi COMMIT=$(git rev-parse --short HEAD) GIT_TAG=${DRONE_TAG:-$(git tag -l --contains HEAD | head -n 1)} if [[ -z "$DIRTY" && -n "$GIT_TAG" ]]; then VERSION=$GIT_TAG else VERSION="${COMMIT}${DIRTY}" fi 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!768 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor