├── main.go ├── test ├── bats │ ├── products.bats │ ├── majorversions.bats │ ├── versions.bats │ ├── subproducts.bats │ ├── eula.bats │ ├── test_helpers.bash │ ├── files.bats │ └── download.bats ├── test_manifest_large.yml ├── test_manifest.yml └── test_tanzu.yml ├── api ├── helpers_test.go ├── products_test.go ├── versions.go ├── major_versions.go ├── major_versions_test.go ├── subproducts.go ├── products.go ├── eula.go ├── download.go ├── subproducts_test.go ├── versions_test.go ├── eula_test.go ├── login.go ├── files.go ├── files_test.go └── download_test.go ├── .gitignore ├── NOTICE ├── cmd ├── manifest_example.go ├── get.go ├── products.go ├── major_versions.go ├── root.go ├── versions.go ├── logout.go ├── eula.go ├── error_handler.go ├── subproducts.go ├── docs.go ├── validators.go ├── files.go └── download.go ├── manifest ├── manifest_test.go └── manifest.go ├── presenters └── table.go ├── scripts └── build.sh ├── go.mod ├── .github └── workflows │ └── tests.yml ├── downloader └── downloader.go ├── CONTRIBUTING.md ├── README.md ├── CODE_OF_CONDUCT.md ├── LICENSE └── go.sum /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package main 5 | 6 | import "github.com/vmware-labs/vmware-customer-connect-cli/cmd" 7 | 8 | func main() { 9 | cmd.Execute() 10 | } 11 | -------------------------------------------------------------------------------- /test/bats/products.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helpers 4 | 5 | setup() { 6 | setup_command 7 | } 8 | 9 | @test "get products successfully" { 10 | run $VCC_CMD get products 11 | echo $output 12 | [[ "$output" == *"vmware_tools"* ]] 13 | [ "$status" -eq 0 ] 14 | } -------------------------------------------------------------------------------- /test/test_manifest_large.yml: -------------------------------------------------------------------------------- 1 | --- 2 | product: vmware_tanzu_kubernetes_grid 3 | subproduct: tkg 4 | version: "1.3*" 5 | filename_globs: 6 | - "photon*.ova" 7 | --- 8 | product: vmware_tanzu_kubernetes_grid 9 | subproduct: tkg 10 | version: "1.3.0" 11 | filename_globs: 12 | - "ubuntu*.ova" 13 | --- -------------------------------------------------------------------------------- /test/test_manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | product: vmware_tools 3 | subproduct: vmtools 4 | version: "11.*" 5 | filename_globs: 6 | - "VMware-Tools-darwin-*.tar.gz" 7 | - "VMware-Tools-darwin-*.zip" 8 | --- 9 | product: vmware_tools 10 | subproduct: vmtools 11 | version: "10.*" 12 | filename_globs: 13 | - "VMware-Tools-other-*.tar.gz" 14 | --- -------------------------------------------------------------------------------- /api/helpers_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package api 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | ) 10 | 11 | var err error 12 | 13 | func mustEnv(k string) string { 14 | if v, ok := os.LookupEnv(k); ok { 15 | return v 16 | } 17 | fmt.Println("Environment variables not set correctly") 18 | os.Exit(1) 19 | return "" 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | .envrc 18 | tmp/* 19 | settings.json 20 | builds/* 21 | __debug_bin -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2023 VMware, Inc. 2 | 3 | This product is licensed to you under the Apache License, V2.0 (the "License"). You may not use this product except in compliance with the License. 4 | 5 | This product may include a number of subcomponents with separate copyright notices and license terms. Your use of these subcomponents is subject to the terms and conditions of the subcomponent's license, as noted in the LICENSE file. -------------------------------------------------------------------------------- /api/products_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package api 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestGetProducts(t *testing.T) { 14 | var products [][]string 15 | products, err := ListProducts() 16 | require.Nil(t, err) 17 | assert.Greater(t, len(products), 80, "Expected response to contain at least 80 items") 18 | } 19 | -------------------------------------------------------------------------------- /test/test_tanzu.yml: -------------------------------------------------------------------------------- 1 | --- 2 | product: vmware_tanzu_kubernetes_grid 3 | subproduct: tkg 4 | version: "1.3.1" 5 | filename_globs: 6 | - "ubuntu-*-kube-v1.2*.ova" # Files can be globbed in any way to select files 7 | - "tanzu-cli-bundle-linux-amd64.tar" # Or just called out explicitly 8 | --- 9 | product: vmware_tanzu_kubernetes_grid 10 | subproduct: tkg 11 | version: "*" # Globbing of version is also supported 12 | filename_globs: 13 | - "tanzu-cli-bundle*linux-amd64.tar" 14 | --- 15 | -------------------------------------------------------------------------------- /api/versions.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package api 5 | 6 | import ( 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | func ListVersions(slug, subProduct, dlgType string) (data string, err error) { 12 | versionArray, err := basicClient.GetVersionSlice(slug, subProduct, dlgType) 13 | if err != nil { 14 | return 15 | } 16 | 17 | data = strings.Join(versionArray[:], "' '") 18 | data = fmt.Sprintf("'%s'", data) 19 | 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /api/major_versions.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package api 5 | 6 | import ( 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | func GetMajorVersionsString(slug string) (majorVersionString string, err error) { 12 | var majorVersionSlice []string 13 | majorVersionSlice, err = basicClient.GetMajorVersionsSlice(slug) 14 | if err != nil { 15 | return 16 | } 17 | majorVersionString = fmt.Sprintf("'%s'", strings.Join(majorVersionSlice,"', '")) 18 | return 19 | } -------------------------------------------------------------------------------- /test/bats/majorversions.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helpers 4 | 5 | setup() { 6 | setup_command 7 | export_errors 8 | } 9 | 10 | @test "get major versions successfully" { 11 | run $VCC_CMD get majorversions -p vmware_vsphere 12 | echo $output 13 | [[ "$output" == *"8_0"* ]] 14 | [ "$status" -eq 0 ] 15 | } 16 | 17 | @test "get major versions with invalid product" { 18 | run $VCC_CMD get majorversions -p vmware_open_shift 19 | echo $output 20 | [[ "$output" == "$ERRORINVALIDSLUG"* ]] 21 | [ "$status" -eq 1 ] 22 | } 23 | -------------------------------------------------------------------------------- /api/major_versions_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package api 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | // "github.com/vmware-labs/vmware-customer-connect-sdk/sdk" 12 | ) 13 | 14 | func TestGetGetMajorVersionsString(t *testing.T) { 15 | var majorVersions string 16 | majorVersions, err := GetMajorVersionsString("vmware_vsphere") 17 | require.Nil(t, err) 18 | assert.Contains(t, majorVersions, "8_0") 19 | } 20 | -------------------------------------------------------------------------------- /api/subproducts.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package api 5 | 6 | import ( 7 | "github.com/vmware-labs/vmware-customer-connect-sdk/sdk" 8 | ) 9 | 10 | func ListSubProducts(slug, dlgType, majorVersion string) (data [][]string, err error) { 11 | var subProducts []sdk.SubProductDetails 12 | subProducts, err = basicClient.GetSubProductsSlice(slug, dlgType, majorVersion) 13 | if err != nil { 14 | return 15 | } 16 | for _, v := range subProducts { 17 | line := []string{v.ProductCode, v.ProductName} 18 | data = append(data, line) 19 | } 20 | 21 | return 22 | } -------------------------------------------------------------------------------- /api/products.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package api 5 | 6 | import ( 7 | "net/http" 8 | "strings" 9 | 10 | "github.com/vmware-labs/vmware-customer-connect-sdk/sdk" 11 | ) 12 | 13 | var basicClient = sdk.Client{HttpClient: &http.Client{}} 14 | 15 | func ListProducts() (data [][]string, err error) { 16 | products, err := basicClient.GetProductsSlice() 17 | if err != nil { 18 | return 19 | } 20 | 21 | for _, v := range products { 22 | slug := strings.Split(v.MajorProductEntities[0].Target, "/")[4] 23 | line := []string{slug, v.Name} 24 | data = append(data, line) 25 | } 26 | return 27 | } 28 | -------------------------------------------------------------------------------- /cmd/manifest_example.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // productsCmd represents the products command 13 | var manifestExampleCmd = &cobra.Command{ 14 | Use: "manifestexample", 15 | Short: "Display an example download manifest", 16 | Long: "Display an example download manifest", 17 | Run: func(cmd *cobra.Command, args []string) { 18 | fmt.Println(exampleManifest) 19 | 20 | }, 21 | DisableFlagsInUseLine: true, 22 | } 23 | 24 | func init() { 25 | getCmd.AddCommand(manifestExampleCmd) 26 | manifestExampleCmd.ResetFlags() 27 | } 28 | -------------------------------------------------------------------------------- /api/eula.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package api 5 | 6 | import ( 7 | "github.com/vmware-labs/vmware-customer-connect-sdk/sdk" 8 | ) 9 | 10 | 11 | func GetEula(slug, subProduct, version, username, password, dlgType string) (data string, err error) { 12 | var productID string 13 | var apiVersions sdk.APIVersions 14 | productID, apiVersions, err = basicClient.GetDlgProduct(slug, subProduct, version, dlgType) 15 | if err != nil { 16 | return 17 | } 18 | if err = EnsureLogin(username, password); err != nil { 19 | return 20 | } 21 | 22 | data, err = authenticatedClient.FetchEulaUrl(apiVersions.Code, productID) 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /cmd/get.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | // "fmt" 8 | 9 | "fmt" 10 | 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | // listCmd represents the list command 15 | var getCmd = &cobra.Command{ 16 | Use: "get", 17 | Aliases: []string{"g"}, 18 | Short: "Display responses", 19 | Long: `Display responses`, 20 | Example: fmt.Sprintf("%s\n\n%s\n\n%s\n\n%s\n\n%s", getProductsUsage, getSubProductsUsage, getVersions, getFiles, getManifestExample), 21 | DisableFlagsInUseLine: true, 22 | } 23 | 24 | func init() { 25 | rootCmd.AddCommand(getCmd) 26 | } 27 | -------------------------------------------------------------------------------- /test/bats/versions.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helpers 4 | 5 | setup() { 6 | setup_command 7 | export_errors 8 | } 9 | 10 | @test "get versions successfully" { 11 | run $VCC_CMD get versions -p vmware_tools -s vmtools 12 | echo $output 13 | [[ "$output" == *"11.0.0"* ]] 14 | [ "$status" -eq 0 ] 15 | } 16 | 17 | @test "get versions with invalid product" { 18 | run $VCC_CMD get versions -p INCORRECT -s vmtools 19 | echo $output 20 | [[ "$output" == "$ERRORINVALIDSLUG"* ]] 21 | [ "$status" -eq 1 ] 22 | } 23 | 24 | @test "get versions with invalid subproduct" { 25 | run $VCC_CMD get versions -p vmware_tools -s INCORRECT 26 | echo $output 27 | [[ "$output" == "$ERRORINVALIDSUBPRODUCT"* ]] 28 | [ "$status" -eq 1 ] 29 | } -------------------------------------------------------------------------------- /manifest/manifest_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package manifest 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestEnsureInitialized(t *testing.T) { 13 | var testSpec = ManifestSpec{ 14 | Slug: "test", 15 | SubProduct: "test", 16 | Version: "test", 17 | FilenameGlobs: []string{"test"}, 18 | } 19 | err := ensureInitialised(testSpec, 0) 20 | assert.Nil(t, err) 21 | } 22 | 23 | func TestEnsureInitializedInvalid(t *testing.T) { 24 | var testSpec = ManifestSpec{ 25 | Slug: "test", 26 | SubProduct: "test", 27 | Version: "test", 28 | } 29 | err := ensureInitialised(testSpec, 0) 30 | assert.ErrorIs(t, err, ErrorInvalidSpec) 31 | } 32 | -------------------------------------------------------------------------------- /presenters/table.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package presenters 5 | 6 | import ( 7 | "os" 8 | 9 | "github.com/olekukonko/tablewriter" 10 | ) 11 | 12 | func RenderTable(headings []string, data [][]string) { 13 | table := tablewriter.NewWriter(os.Stdout) 14 | table.SetHeader(headings) 15 | table.SetAutoWrapText(false) 16 | table.SetAutoFormatHeaders(true) 17 | table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) 18 | table.SetAlignment(tablewriter.ALIGN_LEFT) 19 | table.SetCenterSeparator("") 20 | table.SetColumnSeparator("") 21 | table.SetRowSeparator("") 22 | table.SetHeaderLine(false) 23 | table.SetBorder(false) 24 | table.SetNoWhiteSpace(true) 25 | table.SetTablePadding("\t") 26 | table.AppendBulk(data) 27 | table.Render() 28 | } 29 | -------------------------------------------------------------------------------- /cmd/products.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "github.com/spf13/cobra" 8 | "github.com/vmware-labs/vmware-customer-connect-cli/api" 9 | "github.com/vmware-labs/vmware-customer-connect-cli/presenters" 10 | ) 11 | 12 | // productsCmd represents the products command 13 | var productsCmd = &cobra.Command{ 14 | Use: "products", 15 | Aliases: []string{"p"}, 16 | Short: "List of available products", 17 | Long: "List of available products", 18 | Example: getProductsUsage, 19 | Run: func(cmd *cobra.Command, args []string) { 20 | 21 | products, err := api.ListProducts() 22 | handleErrors(err) 23 | headings := []string{"Product code", "Product description"} 24 | presenters.RenderTable(headings, products) 25 | }, 26 | } 27 | 28 | func init() { 29 | getCmd.AddCommand(productsCmd) 30 | } 31 | -------------------------------------------------------------------------------- /api/download.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package api 5 | 6 | import ( 7 | "github.com/vmware-labs/vmware-customer-connect-sdk/sdk" 8 | ) 9 | 10 | func FetchDownloadPayload(slug, subProduct, version, fileName, username, password, dlgType string, acceptEula bool) (data []sdk.DownloadPayload, err error) { 11 | if err = EnsureLogin(username, password); err != nil { 12 | return 13 | } 14 | 15 | data, err = authenticatedClient.GenerateDownloadPayload(slug, subProduct, version, fileName, dlgType, acceptEula) 16 | if err != nil { 17 | return 18 | } 19 | return 20 | } 21 | 22 | func FetchDownloadLink(downloadPayload sdk.DownloadPayload, username, password string) (data sdk.AuthorizedDownload, err error) { 23 | err = EnsureLogin(username, password) 24 | if err != nil { 25 | return 26 | } 27 | data, err = authenticatedClient.FetchDownloadLink(downloadPayload) 28 | return 29 | } 30 | -------------------------------------------------------------------------------- /api/subproducts_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package api 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | "github.com/vmware-labs/vmware-customer-connect-sdk/sdk" 12 | ) 13 | 14 | func TestGetSubProducts(t *testing.T) { 15 | var products [][]string 16 | products, err := ListSubProducts("vmware_tools", "PRODUCT_BINARY", "") 17 | require.Nil(t, err) 18 | assert.NotEmpty(t, products) 19 | } 20 | 21 | func TestGetSubProductsDriversMajorVersion(t *testing.T) { 22 | var products [][]string 23 | products, err := ListSubProducts("vmware_vsphere", "DRIVERS_TOOLS", "8_0") 24 | require.Nil(t, err) 25 | assert.NotEmpty(t, products) 26 | assert.LessOrEqual(t, len(products), 400) 27 | } 28 | 29 | func TestGetSubProductsInvalidSlug(t *testing.T) { 30 | products, err := ListSubProducts("tools", "PRODUCT_BINARY", "") 31 | assert.ErrorIs(t, err, sdk.ErrorInvalidSlug) 32 | assert.Empty(t, products, "Expected response to be empty") 33 | } 34 | -------------------------------------------------------------------------------- /api/versions_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package api 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | "github.com/vmware-labs/vmware-customer-connect-sdk/sdk" 12 | ) 13 | 14 | func TestGetVersions(t *testing.T) { 15 | versions, err := ListVersions("vmware_tools", "vmtools", "PRODUCT_BINARY") 16 | require.Nil(t, err) 17 | assert.Greater(t, len(versions), 10, "Expected response to contain at least 10 items") 18 | } 19 | 20 | func TestGetVersionsInvalidSlug(t *testing.T) { 21 | versions, err := ListVersions("tools", "vmtools", "PRODUCT_BINARY") 22 | assert.ErrorIs(t, err, sdk.ErrorInvalidSlug) 23 | assert.Empty(t, versions, "Expected response to be empty") 24 | } 25 | 26 | func TestGetVersionsInvalidSubProduct(t *testing.T) { 27 | versions, err := ListVersions("vmware_tools", "tools", "PRODUCT_BINARY") 28 | assert.ErrorIs(t, err, sdk.ErrorInvalidSubProduct) 29 | assert.Empty(t, versions, "Expected response to be empty") 30 | } 31 | -------------------------------------------------------------------------------- /cmd/major_versions.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/spf13/cobra" 10 | "github.com/vmware-labs/vmware-customer-connect-cli/api" 11 | ) 12 | 13 | var majorVersionsSlug string 14 | 15 | // versionsCmd represents the versions command 16 | var MajorVersionsCmd = &cobra.Command{ 17 | Use: "majorversions", 18 | Aliases: []string{"v"}, 19 | Short: "List available majors versions (for driver_tools, custom_iso, addons)", 20 | Long: "List available major versions to help query drives_tools, custom_isos and addons", 21 | Example: getMajorVersions, 22 | Run: func(cmd *cobra.Command, args []string) { 23 | majorVersions, err := api.GetMajorVersionsString(majorVersionsSlug) 24 | handleErrors(err) 25 | fmt.Println(majorVersions) 26 | }, 27 | } 28 | 29 | func init() { 30 | getCmd.AddCommand(MajorVersionsCmd) 31 | MajorVersionsCmd.Flags().StringVarP(&majorVersionsSlug, "product", "p", "", "Product code") 32 | MajorVersionsCmd.MarkFlagRequired("product") 33 | } 34 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2022 VMware, Inc. 3 | # SPDX-License-Identifier: Apache 2.0 4 | 5 | set -eu 6 | 7 | [ -z "${1:-}" ] && echo 'The version must be passed in as a arguement. E.g. build.sh 0.1.0' && exit 1 8 | 9 | readonly verison=$1 10 | readonly script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/.." 11 | 12 | env GOOS=linux GOARCH=amd64 go build \ 13 | -ldflags="-X 'github.com/vmware-labs/vmware-customer-connect-cli/cmd.cliVersion=${verison}'" \ 14 | -o ${script_dir}/builds/vcc-linux-amd64-v${verison} 15 | 16 | env GOOS=darwin GOARCH=amd64 go build \ 17 | -ldflags="-X github.com/vmware-labs/vmware-customer-connect-cli/cmd.cliVersion=${verison}" \ 18 | -o ${script_dir}/builds/vcc-darwin-amd64-v${verison} 19 | 20 | env GOOS=darwin GOARCH=arm64 go build \ 21 | -ldflags="-X github.com/vmware-labs/vmware-customer-connect-cli/cmd.cliVersion=${verison}" \ 22 | -o ${script_dir}/builds/vcc-darwin-arm64-v${verison} 23 | 24 | env GOOS=windows GOARCH=amd64 go build \ 25 | -ldflags="-X github.com/vmware-labs/vmware-customer-connect-cli/cmd.cliVersion=${verison}" \ 26 | -o ${script_dir}/builds/vcc-windows-amd64-v${verison}.exe 27 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vmware-labs/vmware-customer-connect-cli 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/dustin/go-humanize v1.0.1 7 | github.com/olekukonko/tablewriter v0.0.5 8 | github.com/orirawlings/persistent-cookiejar v0.3.2 9 | github.com/spf13/cobra v1.8.0 10 | github.com/stretchr/testify v1.8.1 11 | github.com/vmware-labs/vmware-customer-connect-sdk v0.0.0-20231212133750-ad52df1677f3 12 | gopkg.in/yaml.v3 v3.0.1 13 | ) 14 | 15 | require ( 16 | github.com/andybalholm/cascadia v1.3.2 // indirect 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/google/go-cmp v0.5.9 // indirect 19 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 20 | github.com/kr/pretty v0.3.1 // indirect 21 | github.com/mattn/go-runewidth v0.0.15 // indirect 22 | github.com/pmezard/go-difflib v1.0.0 // indirect 23 | github.com/rivo/uniseg v0.4.4 // indirect 24 | github.com/spf13/pflag v1.0.5 // indirect 25 | go4.org v0.0.0-20230225012048-214862532bf5 // indirect 26 | golang.org/x/net v0.19.0 // indirect 27 | golang.org/x/sys v0.15.0 // indirect 28 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 29 | gopkg.in/retry.v1 v1.0.3 // indirect 30 | ) 31 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var username string 13 | var password string 14 | var cliVersion string 15 | 16 | // rootCmd represents the base command when called without any subcommands 17 | var rootCmd = &cobra.Command{ 18 | Version: cliVersion, 19 | Use: "vcc", 20 | Short: "Download binaries from customerconnect.vmware.com", 21 | Long: "vcc downloads binaries from customerconnect.vmware.com", 22 | Example: fmt.Sprintf("%s\n\n%s\n\n%s\n\n%s\n\n%s\n\n%s", downloadUsage, getProductsUsage, getSubProductsUsage, getVersions, getFiles, getManifestExample), 23 | } 24 | 25 | // Execute adds all child commands to the root command and sets flags appropriately. 26 | // This is called by main.main(). It only needs to happen once to the rootCmd. 27 | func Execute() { 28 | cobra.CheckErr(rootCmd.Execute()) 29 | } 30 | 31 | func init() { 32 | rootCmd.PersistentFlags().StringVar(&username, "user", "", "Username used to authenticate [$VCC_USER]") 33 | rootCmd.PersistentFlags().StringVar(&password, "pass", "", "Password used to authenticate [$VCC_PASS]") 34 | } 35 | -------------------------------------------------------------------------------- /test/bats/subproducts.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helpers 4 | 5 | setup() { 6 | setup_command 7 | export_errors 8 | } 9 | 10 | @test "get subproducts successfully" { 11 | run $VCC_CMD get subproducts -p vmware_tools 12 | echo $output 13 | [[ "$output" == *"VMware Tools"* ]] 14 | [ "$status" -eq 0 ] 15 | } 16 | 17 | @test "get subproducts successfully - drivers" { 18 | run $VCC_CMD get subproducts -p vmware_vsphere -t drivers_tools 19 | echo $output 20 | [[ "$output" == *"ESX"* ]] 21 | [ "$status" -eq 0 ] 22 | } 23 | 24 | @test "get subproducts drivers with version successfully" { 25 | run $VCC_CMD get subproducts -p vmware_vsphere -t drivers_tool 26 | echo $output 27 | [[ "$output" == *"Supported types are:"* ]] 28 | [ "$status" -eq 1 ] 29 | } 30 | 31 | @test "get subproducts drivers with version invalid type" { 32 | run $VCC_CMD get subproducts -p vmware_vsphere -t drivers_tools -m 8_0 33 | echo $output 34 | [[ "$output" == *"ESX"* ]] 35 | [ "$status" -eq 0 ] 36 | } 37 | 38 | @test "get subproducts with invalid product" { 39 | run $VCC_CMD get subproducts -p INCORRECT 40 | echo $output 41 | [[ "$output" == *"$ERRORINVALIDSLUG"* ]] 42 | [ "$status" -eq 1 ] 43 | } -------------------------------------------------------------------------------- /api/eula_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package api 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/vmware-labs/vmware-customer-connect-sdk/sdk" 11 | ) 12 | 13 | func TestGetEula(t *testing.T) { 14 | eulaUrl, err := GetEula("vmware_tools", "vmtools", "11.1.1", testing_user, testing_pass, "PRODUCT_BINARY") 15 | assert.Nil(t, err) 16 | assert.NotEmpty(t, eulaUrl) 17 | } 18 | 19 | func TestGetEulaInvalidSlug(t *testing.T) { 20 | eulaUrl, err := GetEula("tools", "vmtools", "", testing_user, testing_pass, "PRODUCT_BINARY") 21 | assert.ErrorIs(t, err, sdk.ErrorInvalidSlug) 22 | assert.Empty(t, eulaUrl) 23 | } 24 | 25 | func TestGetEulaInvalidSubProduct(t *testing.T) { 26 | eulaUrl, err := GetEula("vmware_tools", "tools", "", testing_user, testing_pass, "PRODUCT_BINARY") 27 | assert.ErrorIs(t, err, sdk.ErrorInvalidSubProduct) 28 | assert.Empty(t, eulaUrl) 29 | } 30 | 31 | func TestGetEulaInvalidVersion(t *testing.T) { 32 | eulaUrl, err := GetEula("vmware_tools", "vmtools", "666", testing_user, testing_pass, "PRODUCT_BINARY") 33 | assert.ErrorIs(t, err, sdk.ErrorInvalidVersion) 34 | assert.Empty(t, eulaUrl) 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: tests 3 | 4 | on: 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | 10 | env: 11 | VCC_USER: ${{ secrets.VCC_USER }} 12 | VCC_PASS: ${{ secrets.VCC_PASS }} 13 | GOPROXY: direct 14 | 15 | jobs: 16 | tests: 17 | strategy: 18 | matrix: 19 | os: [ubuntu-latest, macos-latest, windows-latest] 20 | runs-on: ${{ matrix.os }} 21 | steps: 22 | - name: Set up Go 23 | uses: actions/setup-go@v3 24 | with: 25 | go-version: 1.21.4 26 | 27 | - name: Checkout Code 28 | uses: actions/checkout@v3 29 | 30 | - name: Run Golang Tests 31 | run: go test -v ./... 32 | 33 | - name: Setup BATS 34 | if: runner.os != 'windows' 35 | run: | 36 | set -e 37 | 38 | if [ -n "$GITHUB_RUN_ID" ]; then 39 | export GITHUB_API_TOKEN="${{ secrets.GITHUB_TOKEN }}" 40 | fi 41 | 42 | git clone https://github.com/bats-core/bats-core.git && bats-core/install.sh $HOME 43 | 44 | - name: Run BATS Tests 45 | if: runner.os != 'windows' 46 | run: | 47 | export PATH=${PATH}:/home/runner/bin 48 | # Retry hack to attempt test 3 times 49 | bats test/bats 50 | -------------------------------------------------------------------------------- /test/bats/eula.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helpers 4 | 5 | setup() { 6 | setup_command 7 | export_errors 8 | } 9 | 10 | @test "get eula successfully" { 11 | run $VCC_CMD get eula -p vmware_tools -s vmtools -v 11.3.0 12 | echo $output 13 | [[ "$output" == *"Open the URL in your browser: http://www.vmware.com"* ]] 14 | [ "$status" -eq 0 ] 15 | } 16 | 17 | @test "get eula with invalid product" { 18 | run $VCC_CMD get eula -p INVALID -s vmtools -v 11.3.0 19 | echo $output 20 | [[ "$output" == *"$ERRORINVALIDSLUG"* ]] 21 | [ "$status" -eq 1 ] 22 | } 23 | 24 | @test "get eula with invalid subproduct" { 25 | run $VCC_CMD get eula -p vmware_tools -s INVALID -v 11.3.0 26 | echo $output 27 | [[ "$output" == *"$ERRORINVALIDSUBPRODUCT"* ]] 28 | [ "$status" -eq 1 ] 29 | } 30 | 31 | @test "get eula with invalid version" { 32 | run $VCC_CMD get eula -p vmware_tools -s vmtools -v INVALID 33 | echo $output 34 | [[ "$output" == *"$ERRORINVALIDVERSION"* ]] 35 | [ "$status" -eq 1 ] 36 | } 37 | 38 | @test "get eula with invalid credentials" { 39 | $VCC_CMD logout 40 | run $VCC_CMD get eula -p vmware_tools -s vmtools -v 11.3.0 --user invalid --pass invalid 41 | echo $output 42 | [[ "$output" == *"$ERRORAUTHENTICATIONFAILURE"* ]] 43 | [ "$status" -eq 1 ] 44 | } -------------------------------------------------------------------------------- /cmd/versions.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/spf13/cobra" 10 | "github.com/vmware-labs/vmware-customer-connect-cli/api" 11 | ) 12 | 13 | var subProduct string 14 | 15 | // versionsCmd represents the versions command 16 | var versionsCmd = &cobra.Command{ 17 | Use: "versions", 18 | Aliases: []string{"v"}, 19 | Short: "List available versions", 20 | Long: "List available versions of a sub-product", 21 | Example: getVersions, 22 | Run: func(cmd *cobra.Command, args []string) { 23 | dlgType = validateDlgType(dlgType) 24 | versionString, err := api.ListVersions(slug, subProduct, dlgType) 25 | handleErrors(err) 26 | fmt.Println(versionString) 27 | }, 28 | } 29 | 30 | func init() { 31 | getCmd.AddCommand(versionsCmd) 32 | versionsCmd.Flags().StringVarP(&slug, "product", "p", "", "Product code") 33 | versionsCmd.Flags().StringVarP(&subProduct, "subproduct", "s", "", "Sub Product code") 34 | versionsCmd.MarkFlagRequired("product") 35 | versionsCmd.MarkFlagRequired("sub-product") 36 | versionsCmd.Flags().StringVarP(&dlgType, "type", "t", "product_binary", "(optional) Download type. One of: (product_binary, drivers_tools, custom_iso, addons). Default: product_binary") 37 | } 38 | -------------------------------------------------------------------------------- /api/login.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package api 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "path/filepath" 10 | "runtime" 11 | 12 | "github.com/vmware-labs/vmware-customer-connect-sdk/sdk" 13 | "github.com/orirawlings/persistent-cookiejar" 14 | ) 15 | 16 | var authenticatedClient *sdk.Client 17 | var jar *cookiejar.Jar 18 | 19 | func EnsureLogin(username, password string) (err error) { 20 | if authenticatedClient == nil { 21 | // Store cookies under the user profile 22 | jar, err = cookiejar.New(&cookiejar.Options{ 23 | Filename: filepath.Join(homeDir(), ".vcc.cookies"), 24 | PersistSessionCookies: true, 25 | }) 26 | if err != nil { 27 | return 28 | } 29 | fmt.Fprintf(os.Stderr, "Logging in...\n") 30 | authenticatedClient, err = sdk.Login(username, password, jar) 31 | if err == nil { 32 | err = jar.Save() 33 | } 34 | } else { 35 | // If tokens are still valid leave existing authenticatedClient in place 36 | err = authenticatedClient.CheckLoggedIn() 37 | if err == nil { 38 | return 39 | } 40 | 41 | authenticatedClient, err = sdk.Login(username, password, jar) 42 | if err == nil { 43 | err = jar.Save() 44 | } 45 | } 46 | return 47 | } 48 | 49 | // homeDir returns the OS-specific home path as specified in the environment. 50 | func homeDir() string { 51 | if runtime.GOOS == "windows" { 52 | return filepath.Join(os.Getenv("HOMEDRIVE"), os.Getenv("HOMEPATH")) 53 | } 54 | return os.Getenv("HOME") 55 | } 56 | -------------------------------------------------------------------------------- /cmd/logout.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | // "fmt" 8 | 9 | "fmt" 10 | "os" 11 | "path/filepath" 12 | 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | // listCmd represents the list command 17 | var logoutCmd = &cobra.Command{ 18 | Use: "logout", 19 | Short: "Remove all session cookies", 20 | Long: "Remove all session cookies by deleting .vcc.cookies", 21 | Run: func(cmd *cobra.Command, args []string) { 22 | 23 | cookieFile := filepath.Join(homeDir(), ".vcc.cookies") 24 | if _, fileErr := os.Stat(cookieFile); os.IsNotExist(fileErr) { 25 | fmt.Println("No sessions cookies to delete") 26 | os.Exit(0) 27 | } 28 | err := os.Remove(cookieFile) 29 | if err != nil { 30 | fmt.Fprintf(os.Stderr, "ERROR: Unable to delete cookie file [%s].\n", cookieFile) 31 | fmt.Fprintf(os.Stderr, "%e", err) 32 | os.Exit(1) 33 | } 34 | fmt.Println("Deleted all session cookies") 35 | }, 36 | DisableFlagsInUseLine: true, 37 | } 38 | 39 | func init() { 40 | rootCmd.AddCommand(logoutCmd) 41 | 42 | // Here you will define your flags and configuration settings. 43 | 44 | // Cobra supports Persistent Flags which will work for this command 45 | // and all subcommands, e.g.: 46 | // listCmd.PersistentFlags().String("foo", "", "A help for foo") 47 | 48 | // Cobra supports local flags which will only run when this command 49 | // is called directly, e.g.: 50 | // listCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 51 | } 52 | -------------------------------------------------------------------------------- /cmd/eula.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/spf13/cobra" 10 | "github.com/vmware-labs/vmware-customer-connect-cli/api" 11 | ) 12 | 13 | // filesCmd represents the files command 14 | var eulaCmd = &cobra.Command{ 15 | Use: "eula", 16 | Aliases: []string{"e"}, 17 | Short: "Display the Eula of a product", 18 | Long: `Display the eula of a version of a sub-product 19 | 20 | Either VCC_USER and VCC_PASS environment variable must be set 21 | or the --user and --pass flags should be added`, 22 | Example: getFiles, 23 | Run: func(cmd *cobra.Command, args []string) { 24 | dlgType = validateDlgType(dlgType) 25 | validateCredentials(cmd) 26 | eula, err := api.GetEula(slug, subProduct, version, username, password, dlgType) 27 | handleErrors(err) 28 | fmt.Printf("Open the URL in your browser: %s\n", eula) 29 | }, 30 | } 31 | 32 | func init() { 33 | getCmd.AddCommand(eulaCmd) 34 | eulaCmd.Flags().StringVarP(&slug, "product", "p", "", "Product code") 35 | eulaCmd.Flags().StringVarP(&subProduct, "subproduct", "s", "", "Sub Product code") 36 | eulaCmd.Flags().StringVarP(&version, "version", "v", "", "Version string") 37 | eulaCmd.MarkFlagRequired("product") 38 | eulaCmd.MarkFlagRequired("sub-product") 39 | eulaCmd.MarkFlagRequired("version") 40 | eulaCmd.Flags().StringVarP(&dlgType, "type", "t", "product_binary", "(optional) Download type. One of: (product_binary, drivers_tools, custom_iso, addons). Default: product_binary") 41 | } 42 | -------------------------------------------------------------------------------- /api/files.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package api 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | 10 | "github.com/vmware-labs/vmware-customer-connect-sdk/sdk" 11 | ) 12 | 13 | type Availability struct { 14 | EulaAccepted bool 15 | EligibleToDownload bool 16 | } 17 | 18 | func ListFiles(slug, subProduct, version, username, password, dlgType string) (dlgDetails sdk.DlgDetails, apiVersions sdk.APIVersions, err error) { 19 | if err = EnsureLogin(username, password); err != nil { 20 | return 21 | } 22 | 23 | var productID string 24 | productID, apiVersions, err = authenticatedClient.GetDlgProduct(slug, subProduct, version, dlgType) 25 | if err != nil { 26 | return 27 | } 28 | 29 | fmt.Fprintf(os.Stderr, "Getting DLG Details\n") 30 | dlgDetails, err = authenticatedClient.GetDlgDetails(apiVersions.Code, productID) 31 | if err != nil { 32 | return 33 | } 34 | 35 | return 36 | } 37 | 38 | func ListFilesArray(slug, subProduct, version, username, password, dlgType string) (data [][]string, availability Availability, apiVersions sdk.APIVersions, err error) { 39 | dlgDetails, apiVersions, err := ListFiles(slug, subProduct, version, username, password, dlgType) 40 | if err != nil { 41 | return 42 | } 43 | for _, v := range dlgDetails.DownloadDetails { 44 | if v.FileName != "" { 45 | line := []string{v.FileName, v.FileSize, v.Build, v.Title} 46 | data = append(data, line) 47 | } 48 | } 49 | availability = Availability{ 50 | EulaAccepted: dlgDetails.EulaResponse.EulaAccepted, 51 | EligibleToDownload: dlgDetails.EligibilityResponse.EligibleToDownload, 52 | } 53 | return 54 | } -------------------------------------------------------------------------------- /test/bats/test_helpers.bash: -------------------------------------------------------------------------------- 1 | setup_command() { 2 | SCRIPT_DIR="$( cd "$( dirname "$BATS_TEST_FILENAME" )" >/dev/null 2>&1 && pwd )/../.." 3 | export VCC_CMD="go run $SCRIPT_DIR/main.go" 4 | } 5 | 6 | export_errors() { 7 | export ERRORINVALIDSLUG="Invalid slug provided" 8 | export ERRORINVALIDSUBPRODUCT="Invalid sub-product provided" 9 | export ERRORINVALIDVERSION="Invalid version provided" 10 | export ERRORNOMATCHINGVERSIONS="Version glob did not match any files" 11 | export ERRORNOMATCHINGFILES="No matching files for provided glob" 12 | export ERRORMULTIPLEMATCHINGFILES="Glob matches multiple files, must be restricted to match a single file" 13 | export ERROREULAUNACCEPTED="Eula has not been accepted for this sub-product" 14 | export ERRORNOTENTITLED="You are not entitled to download this sub-product" 15 | export ERRORNOVERSINGLOB="No version glob provided" 16 | export ERRORMULTIPLEVERSIONGLOB="Multiple version globs not supported" 17 | export ERRORAUTHENTICATIONFAILURE="Authentication failure. Check your username and/or password!" 18 | } 19 | 20 | export_yamls() { 21 | export INVALID_YAML_MISSING_FIELD="--- 22 | product: vmware_tools 23 | subproduct: vmtools 24 | version: \"11.*\" 25 | " 26 | 27 | export INVALID_YAML_INVALID_TYPE="--- 28 | product: vmware_tools 29 | subproduct: vmtools 30 | version: 11.0 31 | filename_globs: INVALID 32 | " 33 | 34 | export VALID_YAML="--- 35 | product: vmware_tools 36 | subproduct: vmtools 37 | version: \"11.*\" 38 | filename_globs: 39 | - VMware-Tools-darwin-*.tar.gz 40 | - VMware-Tools-darwin-*.zip 41 | --- 42 | product: vmware_tools 43 | subproduct: vmtools 44 | version: \"10.*\" 45 | filename_globs: 46 | - VMware-Tools-other-*.tar.gz 47 | ---" 48 | } 49 | -------------------------------------------------------------------------------- /test/bats/files.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helpers 4 | 5 | setup() { 6 | setup_command 7 | export_errors 8 | } 9 | 10 | @test "get files successfully_text" { 11 | run $VCC_CMD get files -p vmware_tools -s vmtools -v 11.3.0 12 | echo $output 13 | [[ "$output" == *" 11.3.0"* ]] 14 | [[ "$output" == *"Eula Accepted:"* ]] 15 | [[ "$output" == *"Eligable to Download: true"* ]] 16 | [[ "$output" == *"VMware-Tools-windows-11.3.0-18090558.zip"* ]] 17 | [ "$status" -eq 0 ] 18 | } 19 | 20 | @test "get files successfully_json" { 21 | run $VCC_CMD get files -p vmware_tools -s vmtools -v 11.3.0 --format json 22 | echo $output 23 | [[ "$output" == *"eula_accepted"* ]] 24 | [[ "$output" == *"eligible_to_download"* ]] 25 | [[ "$output" == *"sha256checksum"* ]] 26 | [ "$status" -eq 0 ] 27 | } 28 | 29 | @test "get files with invalid product" { 30 | run $VCC_CMD get files -p INVALID -s vmtools -v 11.3.0 31 | echo $output 32 | [[ "$output" == *"$ERRORINVALIDSLUG"* ]] 33 | [ "$status" -eq 1 ] 34 | } 35 | 36 | @test "get files with invalid subproduct" { 37 | run $VCC_CMD get files -p vmware_tools -s INVALID -v 11.3.0 38 | echo $output 39 | [[ "$output" == *"$ERRORINVALIDSUBPRODUCT"* ]] 40 | [ "$status" -eq 1 ] 41 | } 42 | 43 | @test "get files with invalid version" { 44 | run $VCC_CMD get files -p vmware_tools -s vmtools -v INVALID 45 | echo $output 46 | [[ "$output" == *"$ERRORINVALIDVERSION"* ]] 47 | [ "$status" -eq 1 ] 48 | } 49 | 50 | @test "get files with invalid credentials" { 51 | $VCC_CMD logout 52 | run $VCC_CMD get files -p vmware_tools -s vmtools -v INVALID --user invalid --pass invalid 53 | echo $output 54 | [[ "$output" == *"$ERRORAUTHENTICATIONFAILURE"* ]] 55 | [ "$status" -eq 1 ] 56 | } -------------------------------------------------------------------------------- /cmd/error_handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | 10 | "github.com/vmware-labs/vmware-customer-connect-sdk/sdk" 11 | ) 12 | 13 | func handleErrors(err error) { 14 | if err != nil { 15 | if err == sdk.ErrorInvalidSlug { 16 | fmt.Fprintln(os.Stderr, "Invalid slug provided") 17 | } else if err == sdk.ErrorInvalidSubProduct { 18 | fmt.Fprintln(os.Stderr, "Invalid sub-product provided") 19 | } else if err == sdk.ErrorInvalidVersion { 20 | fmt.Fprintln(os.Stderr, "Invalid version provided") 21 | } else if err == sdk.ErrorNoMatchingVersions { 22 | fmt.Fprintln(os.Stderr, "Version glob did not match any files") 23 | } else if err == sdk.ErrorNoMatchingFiles { 24 | fmt.Fprintln(os.Stderr, "No matching files for provided glob") 25 | } else if err == sdk.ErrorMultipleMatchingFiles { 26 | fmt.Fprintln(os.Stderr, "Glob matches multiple files, must be restricted to match a single file") 27 | } else if err == sdk.ErrorEulaUnaccepted { 28 | fmt.Fprintln(os.Stderr, "Eula has not been accepted for this sub-product") 29 | } else if err == sdk.ErrorNotEntitled { 30 | fmt.Fprintln(os.Stderr, "You are not entitled to download this sub-product") 31 | } else if err == sdk.ErrorNoVersinGlob { 32 | fmt.Fprintln(os.Stderr, "No version glob provided") 33 | } else if err == sdk.ErrorMultipleVersionGlob { 34 | fmt.Fprintln(os.Stderr, "Multiple version globs not supported") 35 | } else if err == sdk.ErrorAuthenticationFailure { 36 | fmt.Fprintln(os.Stderr, "Authentication failure. Check your username and/or password!") 37 | } else if err == sdk.ErrorConnectionFailure { 38 | fmt.Fprintln(os.Stderr, "Unable to connect to customerconnect.vmware.com. Check your proxy settings.") 39 | } else { 40 | fmt.Fprintf(os.Stderr, "%e\n", err) 41 | } 42 | os.Exit(1) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /cmd/subproducts.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | 10 | "github.com/spf13/cobra" 11 | "github.com/vmware-labs/vmware-customer-connect-cli/api" 12 | "github.com/vmware-labs/vmware-customer-connect-cli/presenters" 13 | ) 14 | 15 | var dlgType, majorVersion, slug string 16 | 17 | 18 | 19 | // subproductsCmd represents the subproducts command 20 | var subproductsCmd = &cobra.Command{ 21 | Use: "subproducts", 22 | Aliases: []string{"s"}, 23 | Short: "List sub-products", 24 | Long: "List sub-products for a specified product", 25 | Example: getSubProductsUsage, 26 | Run: func(cmd *cobra.Command, args []string) { 27 | dlgType = validateDlgType(dlgType) 28 | products, err := api.ListSubProducts(slug, dlgType, majorVersion) 29 | handleErrors(err) 30 | headings := []string{"Sub-Product Code", "Description"} 31 | presenters.RenderTable(headings, products) 32 | 33 | if dlgType == "drivers_tools" && majorVersion != "" && len(products) > 200 { 34 | majorVersions, err := api.GetMajorVersionsString(slug) 35 | handleErrors(err) 36 | fmt.Println("\nDue to the high number of results it's recommended to provide the major version to cut down results.") 37 | fmt.Fprintf(os.Stdout, "Major Versions: %s\n", majorVersions) 38 | fmt.Println("\nE.g. vcc get subproducts -p vmware_vsphere -t drivers_tools -m '7_0'") 39 | } 40 | }, 41 | } 42 | 43 | func init() { 44 | getCmd.AddCommand(subproductsCmd) 45 | subproductsCmd.Flags().StringVarP(&slug, "product", "p", "", "Product code") 46 | subproductsCmd.MarkFlagRequired("product") 47 | subproductsCmd.Flags().StringVarP(&dlgType, "type", "t", "product_binary", "(optional) Download type. Allowed: product_binary, drivers_tools, custom_iso, addons. (Default: product_binary)") 48 | subproductsCmd.Flags().StringVarP(&majorVersion, "majorversion", "m", "", "(optional) Reduce the number of results by passing in the major verion.\nGet versions with `vcc get majorversions -p vmware_vsphere`") 49 | } 50 | -------------------------------------------------------------------------------- /api/files_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package api 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/vmware-labs/vmware-customer-connect-sdk/sdk" 11 | ) 12 | 13 | var testing_user = mustEnv("VCC_USER") 14 | var testing_pass = mustEnv("VCC_PASS") 15 | 16 | func TestGetFiles(t *testing.T) { 17 | files, availability, apiVersions, err := ListFilesArray("vmware_tools", "vmtools", "11.1.1", testing_user, testing_pass, "PRODUCT_BINARY") 18 | assert.Nil(t, err) 19 | assert.Greater(t, len(files), 5, "Expected response to contain at least 5 items") 20 | assert.Equal(t, apiVersions.MinorVersion, "11.1.1") 21 | assert.True(t, availability.EligibleToDownload) 22 | } 23 | 24 | func TestGetFilesInvalidSlug(t *testing.T) { 25 | files, _, _, err := ListFilesArray("tools", "vmtools", "", testing_user, testing_pass, "PRODUCT_BINARY") 26 | assert.ErrorIs(t, err, sdk.ErrorInvalidSlug) 27 | assert.Empty(t, files, "Expected response to be empty") 28 | } 29 | 30 | func TestGetFilesInvalidSubProduct(t *testing.T) { 31 | files, _, _, err := ListFilesArray("vmware_tools", "tools", "", testing_user, testing_pass, "PRODUCT_BINARY") 32 | assert.ErrorIs(t, err, sdk.ErrorInvalidSubProduct) 33 | assert.Empty(t, files, "Expected response to be empty") 34 | } 35 | 36 | func TestGetFilesInvalidVersion(t *testing.T) { 37 | files, _, _, err := ListFilesArray("vmware_tools", "vmtools", "666", testing_user, testing_pass, "PRODUCT_BINARY") 38 | assert.ErrorIs(t, err, sdk.ErrorInvalidVersion) 39 | assert.Empty(t, files, "Expected response to be empty") 40 | } 41 | 42 | func TestGetFilesNotEntitled(t *testing.T) { 43 | files, availability, apiVersions, err := ListFilesArray("vmware_nsx_t_data_center", "nsx-t", "3.2.3.1", testing_user, testing_pass, "PRODUCT_BINARY") 44 | assert.Nil(t, err) 45 | assert.Greater(t, len(files), 5, "Expected response to contain at least 5 items") 46 | assert.Equal(t, apiVersions.MinorVersion, "3.2.3.1") 47 | assert.False(t, availability.EligibleToDownload) 48 | } 49 | -------------------------------------------------------------------------------- /manifest/manifest.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package manifest 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "io" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | 14 | "gopkg.in/yaml.v3" 15 | ) 16 | 17 | type ManifestSpec struct { 18 | Slug string `yaml:"product"` 19 | SubProduct string `yaml:"subproduct"` 20 | Version string `yaml:"version"` 21 | FilenameGlobs []string `yaml:"filename_globs"` 22 | } 23 | 24 | var ( 25 | ErrorFileDoesNotExist = errors.New("manifest file does not exist") 26 | ErrorInvalidSpec = errors.New("manifest file has invalid section") 27 | ) 28 | 29 | func ProcessFile(manifestFile string) (downloads []ManifestSpec, err error) { 30 | if !strings.Contains(manifestFile, "/") { 31 | cwd, _ := filepath.Abs("./") 32 | fmt.Println(cwd) 33 | manifestFile = cwd + "/" + manifestFile 34 | } 35 | 36 | if _, fileErr := os.Stat(manifestFile); os.IsNotExist(fileErr) { 37 | err = errors.New("manifest file not found") 38 | os.Exit(2) 39 | } 40 | 41 | f, err := os.Open(manifestFile) 42 | if err != nil { 43 | return 44 | } 45 | d := yaml.NewDecoder(f) 46 | 47 | entry := 0 48 | for { 49 | // create new spec here 50 | download := new(ManifestSpec) 51 | // pass a reference to spec reference 52 | err = d.Decode(&download) 53 | // check it was parsed 54 | if download == nil { 55 | continue 56 | } 57 | 58 | // break the loop in case of EOF 59 | if errors.Is(err, io.EOF) { 60 | err = nil 61 | break 62 | } 63 | if err != nil { 64 | return 65 | } 66 | 67 | err = ensureInitialised(*download, entry) 68 | if err != nil { 69 | return 70 | } 71 | entry++ 72 | downloads = append(downloads, *download) 73 | } 74 | return 75 | } 76 | 77 | func ensureInitialised(dl ManifestSpec, entry int) (err error) { 78 | if (dl.Slug == "") || (dl.SubProduct == "") || (dl.Version == "") || (len(dl.FilenameGlobs) == 0) { 79 | fmt.Fprintf(os.Stderr, "Manifest entry %d does not have the 4 required keys!\n", entry) 80 | err = ErrorInvalidSpec 81 | } 82 | return 83 | } 84 | -------------------------------------------------------------------------------- /cmd/docs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package cmd 5 | 6 | // Usage Section 7 | const ( 8 | getProductsUsage = ` # List of available products 9 | vcc get products` 10 | 11 | getSubProductsUsage = ` # List of available sub-products of product vmware_tools 12 | vcc get subproducts -p vmware_tools` 13 | 14 | getVersions = ` # List of available versions of sub-products vmtools of vmware_tools 15 | vcc get versions -p vmware_tools -s vmtools` 16 | 17 | getMajorVersions = ` # List of available major versions of product vmware_tools. 18 | # Only used with download types: drivers_tools, custom_isos and addons 19 | vcc get majorversions -p vmware_tools` 20 | 21 | getFiles = ` # List of available files of version 11.3.0 of vmware_tools 22 | vcc get files -p vmware_tools -s vmtools -v 11.3.0` 23 | 24 | getManifestExample = ` # Display example manifest file 25 | vcc get manifestexample` 26 | 27 | downloadUsage = ` # Download the latest version of release 11 with a file matching the pattern 28 | # If using a * in the filename value, make sure to wrap the text in single quotes on linux/macos 29 | vcc download -p vmware_tools -s vmtools -v 11.* -f 'VMware-Tools-darwin-*.zip' --accepteula 30 | 31 | # To download from from drivers_tools, custom_iso and addons, you must add -t . 32 | # Version can be globbed, as 1 subproduct maps to 1 version, but make sure to wrap the * in a speech mark. 33 | vcc download -p vmware_vsphere -t drivers_tools -s vs-mgmt-sdk80u2 -v '*' -f VMware-vSphere-SDK-*.zip --accepteula 34 | 35 | # Download files using a manifest file 36 | # Show an example manifest using 'vcc get manifestexample' 37 | vcc download -m manifest.yml --accepteula` 38 | ) 39 | 40 | const exampleManifest = `--- 41 | # This section will download the latest version of vmware_tools 42 | # Each glob pattern will download a single file each 43 | product: vmware_tools 44 | subproduct: vmtools 45 | version: "*" 46 | filename_globs: 47 | - "VMware-Tools-darwin-*.tar.gz" 48 | - "VMware-Tools-darwin-*.zip" 49 | --- 50 | # This section will download the latest minor release from major version 10 51 | # The single glob pattern will download 2 files 52 | product: vmware_tools 53 | subproduct: vmtools 54 | version: "10.*" 55 | filename_globs: 56 | - "VMware-Tools-other-*" 57 | ---` 58 | -------------------------------------------------------------------------------- /downloader/downloader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package downloader 5 | 6 | // Credit https://golangcode.com/download-a-file-with-progress/ 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "io" 12 | "net/http" 13 | "os" 14 | "strings" 15 | 16 | "github.com/dustin/go-humanize" 17 | "github.com/vmware-labs/vmware-customer-connect-sdk/sdk" 18 | ) 19 | 20 | var ErrorGeneric = errors.New("download: non 200 response") 21 | 22 | type WriteCounter struct { 23 | Total uint64 24 | } 25 | 26 | func (wc *WriteCounter) Write(p []byte) (int, error) { 27 | n := len(p) 28 | wc.Total += uint64(n) 29 | wc.PrintProgress() 30 | return n, nil 31 | } 32 | 33 | func (wc WriteCounter) PrintProgress() { 34 | // Clear the line by using a character return to go back to the start and remove 35 | // the remaining characters by filling it with spaces 36 | fmt.Printf("\r%s", strings.Repeat(" ", 35)) 37 | 38 | // Return again and print current status of download 39 | // We use the humanize to print the bytes in a meaningful way (e.g. 10 MB) 40 | fmt.Printf("\rDownloading... %s complete", humanize.Bytes(wc.Total)) 41 | } 42 | 43 | func TriggerDownload(authorizedDownload sdk.AuthorizedDownload) (err error) { 44 | fmt.Printf("\nDownload started to %s\n", authorizedDownload.FileName) 45 | 46 | err = DownloadFile(authorizedDownload.DownloadURL, authorizedDownload.FileName) 47 | if err != nil { 48 | return 49 | } 50 | 51 | fmt.Printf("Download finished\n") 52 | return 53 | } 54 | 55 | func DownloadFile(url string, fileName string) error { 56 | 57 | // Create the file, but give it a tmp file extension, this means we won't overwrite a 58 | // file until it's downloaded, but we'll remove the tmp extension once downloaded. 59 | out, err := os.Create(fileName + ".tmp") 60 | if err != nil { 61 | return err 62 | } 63 | 64 | resp, err := http.Get(url) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | if err != nil { 70 | out.Close() 71 | return err 72 | } 73 | defer resp.Body.Close() 74 | 75 | // Create our progress reporter and pass it to be used alongside our writer 76 | counter := &WriteCounter{} 77 | if _, err = io.Copy(out, io.TeeReader(resp.Body, counter)); err != nil { 78 | out.Close() 79 | return err 80 | } 81 | 82 | // The progress use the same line so print a new line once it's finished downloading 83 | fmt.Print("\n") 84 | 85 | // Close the file without defer so it can happen before Rename() 86 | out.Close() 87 | 88 | if resp.StatusCode != 200 { 89 | os.Remove(fileName + ".tmp") 90 | return ErrorGeneric 91 | } 92 | 93 | if err = os.Rename(fileName+".tmp", fileName); err != nil { 94 | return err 95 | } 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to vmware-customer-connect-cli 2 | 3 | We welcome contributions from the community and first want to thank you for taking the time to contribute! 4 | 5 | Please familiarize yourself with the [Code of Conduct](https://github.com/vmware/.github/blob/main/CODE_OF_CONDUCT.md) before contributing. 6 | 7 | Before you start working with vmware-customer-connect-cli, please read and sign our Contributor License Agreement [CLA](https://cla.vmware.com/cla/1/preview). If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will prompt you to do so when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ]([https://cla.vmware.com/faq](https://cla.vmware.com/faq)). 8 | 9 | ## Ways to contribute 10 | 11 | We welcome many different types of contributions and not all of them need a Pull request. Contributions may include: 12 | 13 | * New features and proposals 14 | * Documentation 15 | * Bug fixes 16 | * Issue Triage 17 | * Answering questions and giving feedback 18 | * Helping to onboard new contributors 19 | * Other related activities 20 | 21 | 22 | ## Contribution Flow 23 | 24 | This is a rough outline of what a contributor's workflow looks like: 25 | 26 | * Make a fork of the repository within your GitHub account 27 | * Create a topic branch in your fork from where you want to base your work 28 | * Make commits of logical units 29 | * Make sure your commit messages are with the proper format, quality and descriptiveness (see below) 30 | * Push your changes to the topic branch in your fork 31 | * Create a pull request containing that commit 32 | 33 | We follow the GitHub workflow and you can find more details on the [GitHub flow documentation](https://docs.github.com/en/get-started/quickstart/github-flow). 34 | 35 | 36 | ### Pull Request Checklist 37 | 38 | Before submitting your pull request, we advise you to use the following: 39 | 40 | 1. Check if your code changes will pass both code linting checks and unit tests. 41 | 2. Ensure your commit messages are descriptive. We follow the conventions on [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/). Be sure to include any related GitHub issue references in the commit message. See [GFM syntax](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown) for referencing issues and commits. 42 | 3. Check the commits and commits messages and ensure they are free from typos. 43 | 44 | ## Reporting Bugs and Creating Issues 45 | 46 | For specifics on what to include in your report, please follow the guidelines in the issue and pull request templates when available. 47 | 48 | 49 | ## Ask for Help 50 | 51 | For all issues please raise a Github issue. 52 | 53 | -------------------------------------------------------------------------------- /cmd/validators.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "path/filepath" 10 | "runtime" 11 | "slices" 12 | "strings" 13 | 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | func validateDlgType(dlgType string) (returnDlgType string) { 18 | returnDlgType = strings.ToUpper(dlgType) 19 | allowedFlags := []string{"PRODUCT_BINARY", "DRIVERS_TOOLS", "CUSTOM_ISO", "ADDONS"} 20 | if !slices.Contains(allowedFlags, returnDlgType) { 21 | fmt.Fprintf(os.Stderr, "\n--type '%s' invalid. Supported types are: (product_binary, drivers_tools, custom_iso, addons)\n", dlgType) 22 | os.Exit(1) 23 | } 24 | return 25 | } 26 | 27 | func validateOutputDir() { 28 | if outputDir == "" { 29 | home := homeDir() 30 | homeDownloadDir := filepath.Join(home, "vcc-downloads") 31 | if _, dir_err := os.Stat(homeDownloadDir); os.IsNotExist(dir_err) { 32 | fmt.Printf("%s does not exist. Creating...\n", homeDownloadDir) 33 | mkdir_err := os.MkdirAll(homeDownloadDir, os.ModePerm) 34 | if mkdir_err != nil { 35 | fmt.Fprintf(os.Stderr, "ERROR: Unable to create Downloads directory under [%s].\n", home) 36 | os.Exit(1) 37 | } 38 | } 39 | fmt.Printf("No output directory set. Downloading to %s\n", homeDownloadDir) 40 | outputDir = homeDownloadDir 41 | } else { 42 | if _, dir_err := os.Stat(outputDir); os.IsNotExist(dir_err) { 43 | fmt.Fprintf(os.Stderr, "ERROR: Output directory [%s] does not exist.\n", outputDir) 44 | os.Exit(1) 45 | } 46 | } 47 | } 48 | 49 | // homeDir returns the OS-specific home path as specified in the environment. 50 | func homeDir() string { 51 | if runtime.GOOS == "windows" { 52 | return filepath.Join(os.Getenv("HOMEDRIVE"), os.Getenv("HOMEPATH")) 53 | } 54 | return os.Getenv("HOME") 55 | } 56 | 57 | // ensure credentials are passed in and assign env vars if used 58 | func validateCredentials(cmd *cobra.Command) { 59 | user_ok := validateVarEnv(&username, "VCC_USER") 60 | pass_ok := validateVarEnv(&password, "VCC_PASS") 61 | if !user_ok || !pass_ok { 62 | fmt.Fprintln(os.Stderr, "Credentials not provided!") 63 | fmt.Fprintln(os.Stderr, "You must either provide the username and password as arguements") 64 | fmt.Fprintf(os.Stderr, "or you must export them as VCC_USER and VCC_PASS environment variables.\n\n") 65 | cmd.Usage() 66 | os.Exit(1) 67 | } 68 | } 69 | 70 | // Check if param is set and if not retrieve env var if set 71 | func validateVarEnv(param *string, key string) bool { 72 | if *param == "" { 73 | if value, ok := os.LookupEnv(key); ok { 74 | *param = value 75 | } else { 76 | return false 77 | } 78 | } 79 | return true 80 | } 81 | 82 | func validateDownloadFlags(cmd *cobra.Command) (manifestWorkflow bool) { 83 | if manifestFile != "" { 84 | manifestWorkflow = true 85 | return 86 | } else if (slug != "") && (subProduct != "") && 87 | (version != "") && (fileName != "") { 88 | return 89 | } 90 | fmt.Fprintln(os.Stderr, "Incorrect usage!") 91 | fmt.Fprintln(os.Stderr, "Either --manifest should be passed") 92 | fmt.Fprintf(os.Stderr, "or --product, --subproduct, --version and --filename should be passed\n\n") 93 | cmd.Usage() 94 | os.Exit(2) 95 | 96 | return 97 | } 98 | -------------------------------------------------------------------------------- /cmd/files.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | "os" 10 | 11 | "github.com/spf13/cobra" 12 | "github.com/vmware-labs/vmware-customer-connect-cli/api" 13 | "github.com/vmware-labs/vmware-customer-connect-cli/presenters" 14 | "github.com/vmware-labs/vmware-customer-connect-sdk/sdk" 15 | ) 16 | 17 | type JsonOutput struct { 18 | EulaAccepted bool `json:"eula_accepted"` 19 | EligibleToDownload bool `json:"eligible_to_download"` 20 | Version string `json:"version"` 21 | Files []sdk.DownloadDetails `json:"files"` 22 | } 23 | 24 | var version, outputFormat string 25 | 26 | // filesCmd represents the files command 27 | var filesCmd = &cobra.Command{ 28 | Use: "files", 29 | Aliases: []string{"f"}, 30 | Short: "List available files", 31 | Long: `List available files of a version of a sub-product 32 | 33 | Either VCC_USER and VCC_PASS environment variable must be set 34 | or the --user and --pass flags should be added`, 35 | Example: getFiles, 36 | Run: func(cmd *cobra.Command, args []string) { 37 | dlgType = validateDlgType(dlgType) 38 | if !(outputFormat == "text" || outputFormat == "json") { 39 | fmt.Fprintf(os.Stderr, "Format type %s is not supported\n", outputFormat) 40 | os.Exit(128) 41 | } 42 | 43 | validateCredentials(cmd) 44 | if outputFormat == "text" { 45 | files, availability, apiVersions, err := api.ListFilesArray(slug, subProduct, version, username, password, dlgType) 46 | handleErrors(err) 47 | printText(apiVersions, availability, files) 48 | } else if outputFormat == "json" { 49 | dlgDetails, apiVersions, err := api.ListFiles(slug, subProduct, version, username, password, dlgType) 50 | handleErrors(err) 51 | printJson(dlgDetails, apiVersions) 52 | } 53 | }, 54 | } 55 | 56 | func printText(apiVersions sdk.APIVersions, availability api.Availability, files [][]string) { 57 | fmt.Printf("\nVersion: %s\n", apiVersions.MinorVersion) 58 | fmt.Printf("Eula Accepted: %t\n", availability.EulaAccepted) 59 | fmt.Printf("Eligable to Download: %t\n\n", availability.EligibleToDownload) 60 | 61 | headings := []string{"Filename", "Size", "Build number", "Description"} 62 | presenters.RenderTable(headings, files) 63 | } 64 | 65 | func printJson(dlgDetails sdk.DlgDetails, apiVersions sdk.APIVersions) { 66 | var jsonOutput JsonOutput 67 | jsonOutput.Version = apiVersions.MinorVersion 68 | jsonOutput.EligibleToDownload = dlgDetails.EligibilityResponse.EligibleToDownload 69 | jsonOutput.EulaAccepted = dlgDetails.EulaResponse.EulaAccepted 70 | jsonOutput.Files = dlgDetails.DownloadDetails 71 | 72 | var p []byte 73 | // var err := error 74 | p, err := json.MarshalIndent(jsonOutput, "", "\t") 75 | if err != nil { 76 | fmt.Println(err) 77 | return 78 | } 79 | fmt.Printf("%s \n", p) 80 | } 81 | 82 | func init() { 83 | getCmd.AddCommand(filesCmd) 84 | filesCmd.Flags().StringVarP(&slug, "product", "p", "", "Product code") 85 | filesCmd.Flags().StringVarP(&subProduct, "subproduct", "s", "", "Sub Product code") 86 | filesCmd.Flags().StringVarP(&version, "version", "v", "", "Version string") 87 | filesCmd.Flags().StringVarP(&outputFormat, "format", "", "text", "Format of command output. Options [text, json]") 88 | filesCmd.MarkFlagRequired("product") 89 | filesCmd.MarkFlagRequired("sub-product") 90 | filesCmd.MarkFlagRequired("version") 91 | filesCmd.Flags().StringVarP(&dlgType, "type", "t", "product_binary", "(optional) Download type. One of: (product_binary, drivers_tools, custom_iso, addons). Default: product_binary") 92 | } 93 | -------------------------------------------------------------------------------- /api/download_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package api 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | "github.com/vmware-labs/vmware-customer-connect-sdk/sdk" 13 | ) 14 | 15 | func TestFetchDownloadLinkVersionGlob(t *testing.T) { 16 | var downloadPayload []sdk.DownloadPayload 17 | downloadPayload, err = FetchDownloadPayload("vmware_tools", "vmtools", "11.*", "VMware-Tools-darwin-*.tar.gz", testing_user, testing_pass, "PRODUCT_BINARY", true) 18 | require.Nil(t, err) 19 | require.NotEmpty(t, downloadPayload) 20 | assert.NotEmpty(t, downloadPayload[0].DlgType, "Expected response not to be empty") 21 | 22 | var authorizedDownload sdk.AuthorizedDownload 23 | authorizedDownload, _ = authenticatedClient.FetchDownloadLink(downloadPayload[0]) 24 | assert.Nil(t, err) 25 | assert.NotEmpty(t, authorizedDownload.DownloadURL, "Expected response not to be empty") 26 | 27 | t.Logf(fmt.Sprintf("download_details: %+v\n", authorizedDownload)) 28 | } 29 | 30 | func TestFetchDownloadPayloadVersionGlobMultiple(t *testing.T) { 31 | var downloadPayload []sdk.DownloadPayload 32 | downloadPayload, err = FetchDownloadPayload("vmware_tools", "vmtools", "11.*", "VMware-Tools-*", testing_user, testing_pass, "PRODUCT_BINARY", true) 33 | require.Nil(t, err) 34 | require.NotEmpty(t, downloadPayload) 35 | assert.NotEmpty(t, downloadPayload[0].DlgType, "Expected response not to be empty") 36 | assert.Greater(t, len(downloadPayload), 3) 37 | } 38 | 39 | func TestFetchDownloadLinkInvalidVersion(t *testing.T) { 40 | var downloadPayload []sdk.DownloadPayload 41 | downloadPayload, err = FetchDownloadPayload("vmware_tools", "vmtools", "666", "VMware-Tools-darwin-*.tar.gz", testing_user, testing_pass, "PRODUCT_BINARY", true) 42 | assert.ErrorIs(t, err, sdk.ErrorInvalidVersion) 43 | assert.Empty(t, downloadPayload, "Expected response to be empty") 44 | } 45 | 46 | func TestFetchDownloadLinkNeedEula(t *testing.T) { 47 | var downloadPayload []sdk.DownloadPayload 48 | downloadPayload, err = FetchDownloadPayload("vmware_tools", "vmtools", "11.1.0", "VMware-Tools-darwin-*.tar.gz", testing_user, testing_pass, "PRODUCT_BINARY", false) 49 | assert.ErrorIs(t, err, sdk.ErrorEulaUnaccepted) 50 | assert.Empty(t, downloadPayload, "Expected response to be empty") 51 | } 52 | 53 | func TestFetchDownloadLinkNotEntitled(t *testing.T) { 54 | var downloadPayload []sdk.DownloadPayload 55 | downloadPayload, err = FetchDownloadPayload("vmware_nsx_t_data_center", "nsx-t", "3.2.3.1", "nsx-unified-appliance-secondary-*.qcow2", testing_user, testing_pass, "PRODUCT_BINARY", true) 56 | assert.ErrorIs(t, err, sdk.ErrorNotEntitled) 57 | assert.Empty(t, downloadPayload, "Expected response to be empty") 58 | } 59 | 60 | func TestGenerateDownloadInvalidVersionGlob(t *testing.T) { 61 | var downloadPayload []sdk.DownloadPayload 62 | downloadPayload, err = FetchDownloadPayload("vmware_tools", "vmtools", "666.*", "VMware-Tools-darwin-*.tar.gz", testing_user, testing_pass, "PRODUCT_BINARY", true) 63 | assert.ErrorIs(t, err, sdk.ErrorNoMatchingVersions) 64 | assert.Empty(t, downloadPayload, "Expected response to be empty") 65 | } 66 | 67 | func TestGenerateDownloadDoubleVersion(t *testing.T) { 68 | var downloadPayload []sdk.DownloadPayload 69 | downloadPayload, err = FetchDownloadPayload("vmware_tools", "vmtools", "*.*", "VMware-Tools-darwin-*.tar.gz", testing_user, testing_pass, "PRODUCT_BINARY", true) 70 | assert.ErrorIs(t, err, sdk.ErrorMultipleVersionGlob) 71 | assert.Empty(t, downloadPayload, "Expected response to be empty") 72 | } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VMware Customer Connect CLI 2 | The VMware Customer Connect CLI (`vcc`) allows users to download binaries from [customerconnect.vmware.com](customerconnect.vmware.com). It uses the [vmware-customer-connect-sdk](https://github.com/vmware-labs/vmware-customer-connect-sdk) which was heavily inspired by [vmw-sdk](https://github.com/apnex/vmw-sdk) by [apnex](https://github.com/apnex). 3 | 4 | **WARNING:** This CLI is experimental, with no guarantee of future API stability. 5 | 6 | 7 | ## Installation 8 | 9 | `vcc` is a go binary and can be downloaded from the [releases](https://github.com/vmware-labs/vmware-customer-connect-cli/releases) page. 10 | 11 | On Linux/Mac the file just needs to be made executable. 12 | 13 | To make it available to all users `sudo mv vcc-- /usr/local/bin/vcc` 14 | 15 | 16 | ## Authentication 17 | The Customer Connect username and password can either be passed in as command arguments using `--user` and `--pass` or can be exported as environmental variables, making sure to encase passwords in a single quote in case of special characters. 18 | 19 | Example below for Linux/Mac: 20 | 21 | ``` 22 | export VCC_USER='email@email.com' 23 | export VCC_PASS='##' 24 | ``` 25 | 26 | 27 | ## Usage 28 | The examples below assume that the credentials have been exported as environmental variables. 29 | 30 | ``` 31 | # Download the latest version of release 11 with a file matching the pattern 32 | vcc download -p vmware_tools -s vmtools -v 11.* -f VMware-Tools-darwin-*.zip --accepteula 33 | 34 | # Download files using a manifest file 35 | vcc download -m .yml --accepteula 36 | 37 | # List of available products 38 | vcc get products 39 | 40 | # List of available sub-products of product vmware_tools 41 | vcc get subproducts -p vmware_tools 42 | 43 | # List of available versions of sub-products vmtools of vmware_tools 44 | vcc get versions -p vmware_tools -s vmtools 45 | 46 | # List of available files of version 11.3.0 of vmware_tools 47 | vcc get files -p vmware_tools -s vmtools -v 11.3.0 48 | 49 | # Display example manifest file 50 | vcc get manifestexample 51 | ``` 52 | 53 | Multiple downloads can be specified in a manifest file as below and downloaded using `vcc download -m .yml` 54 | 55 | ``` yaml 56 | --- 57 | # This section will download the latest version of vmware_tools 58 | # Each glob pattern will download a single file each 59 | product: vmware_tools 60 | subproduct: vmtools 61 | version: "*" 62 | filename_globs: 63 | - "VMware-Tools-darwin-*.tar.gz" 64 | - "VMware-Tools-darwin-*.zip" 65 | --- 66 | # This section will download the latest minor release from major version 10 67 | # The single glob pattern will download 2 files 68 | product: vmware_tools 69 | subproduct: vmtools 70 | version: "10.*" 71 | filename_globs: 72 | - "VMware-Tools-other-*" 73 | --- 74 | ``` 75 | 76 | 77 | ## Known Issues 78 | 79 | - When working in a shell if you add a * to the filename argument of the download command and you are in a directory where a file matches the pattern, your shell will replace the * to pass in the full file name. This can be worked around by wrapping the file name in single quotes, or by defining the download in a manifest yaml. 80 | - Some products such as horizon will not return the latest version when only a glob is provided. This is because the product switched naming standards meaning it breaks the sort of the version. 81 | - Some product descriptions don't display fully. This is especially true for the horizon products as they are inconsistently named, meaning it's difficult to extract the version number without taking out part of the product name. 82 | 83 | 84 | ## Testing 85 | Tests assume that you have exported credentials as environmental variables and are run from the root of the repo. 86 | 87 | - Run go tests `go test ./...`
88 | - Run [BATS](https://github.com/bats-core/bats-core) tests with `bats test/bats` 89 | - To run commands against source use `alias vcc="go run main.go"` 90 | 91 | 92 | ## Development 93 | 94 | Run `GOPROXY=direct go get -u ./...` to pull in the latest dependencies, including the SDK. 95 | 96 | Ensure that your IDE exports `VCC_USER` and `VCC_PASS` to be able to run tests and debug. 97 | 98 | 99 | ## Contributing 100 | 101 | Please see our [Code of Conduct](CODE-OF-CONDUCT.md) and [Contributors guide](CONTRIBUTING.md). 102 | 103 | The vmware-customer-connect-cli project team welcomes contributions from the community. Before you start working with vmware-customer-connect-cli, please read and sign our Contributor License Agreement [CLA](https://cla.vmware.com/cla/1/preview). If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will prompt you to do so when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ]([https://cla.vmware.com/faq](https://cla.vmware.com/faq)). 104 | 105 | ## License 106 | Apache License 107 | -------------------------------------------------------------------------------- /cmd/download.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 VMware, Inc. 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "crypto/md5" 8 | "fmt" 9 | "io" 10 | "os" 11 | "path/filepath" 12 | 13 | "github.com/spf13/cobra" 14 | "github.com/vmware-labs/vmware-customer-connect-cli/api" 15 | "github.com/vmware-labs/vmware-customer-connect-cli/downloader" 16 | "github.com/vmware-labs/vmware-customer-connect-cli/manifest" 17 | "github.com/vmware-labs/vmware-customer-connect-sdk/sdk" 18 | ) 19 | 20 | var ( 21 | acceptEula bool 22 | fileName string 23 | forceDownload bool 24 | outputDir string 25 | manifestFile string 26 | ) 27 | 28 | // downloadCmd represents the download command 29 | var downloadCmd = &cobra.Command{ 30 | Use: "download", 31 | Aliases: []string{"d"}, 32 | Short: "Download file from VMware", 33 | Long: `Download one or more files 34 | 35 | Either VCC_USER and VCC_PASS environment variable must be set 36 | or the --user and --pass flags should be added`, 37 | Example: downloadUsage, 38 | Run: func(cmd *cobra.Command, args []string) { 39 | dlgType = validateDlgType(dlgType) 40 | validateCredentials(cmd) 41 | validateOutputDir() 42 | manifestWorkflow := validateDownloadFlags(cmd) 43 | err := api.EnsureLogin(username, password) 44 | handleErrors(err) 45 | if manifestWorkflow { 46 | downloadFromManifest() 47 | } else { 48 | fmt.Println("Collecting download payload") 49 | downloadPayloads, err := api.FetchDownloadPayload(slug, subProduct, version, fileName, username, password, dlgType, acceptEula) 50 | handleErrors(err) 51 | downloadFiles(downloadPayloads) 52 | } 53 | }, 54 | } 55 | 56 | func downloadFromManifest() { 57 | fmt.Printf("Opening manifest file: %s\n", manifestFile) 58 | manifestArray, err := manifest.ProcessFile(manifestFile) 59 | if err == manifest.ErrorFileDoesNotExist { 60 | fmt.Fprintf(os.Stderr, "File %s does not exist\n", manifestFile) 61 | os.Exit(1) 62 | } else if err == manifest.ErrorInvalidSpec { 63 | os.Exit(1) 64 | } else if err != nil { 65 | fmt.Fprintf(os.Stderr, "Parsing file failed with error: %e\n", err) 66 | os.Exit(1) 67 | } 68 | 69 | var allPayloads [][]sdk.DownloadPayload 70 | for _, manifestSpec := range manifestArray { 71 | for _, glob := range manifestSpec.FilenameGlobs { 72 | fmt.Printf("Collecting download payload for [%s] [%s] [%s] [%s]\n", manifestSpec.Slug, manifestSpec.SubProduct, 73 | manifestSpec.Version, glob) 74 | downloadPayloads, err := api.FetchDownloadPayload(manifestSpec.Slug, manifestSpec.SubProduct, manifestSpec.Version, 75 | glob, username, password, dlgType, acceptEula) 76 | handleErrors(err) 77 | allPayloads = append(allPayloads, downloadPayloads) 78 | } 79 | } 80 | 81 | for _, downloadPayloads := range allPayloads { 82 | downloadFiles(downloadPayloads) 83 | } 84 | 85 | } 86 | 87 | func downloadFiles(downloadPayloads []sdk.DownloadPayload) { 88 | for _, downloadPayload := range downloadPayloads { 89 | authorizedDownload, err := api.FetchDownloadLink(downloadPayload, username, password) 90 | handleErrors(err) 91 | authorizedDownload.FileName = filepath.Join(outputDir, authorizedDownload.FileName) 92 | if forceDownload || checkToDownload(authorizedDownload.FileName, downloadPayload.Md5checksum) { 93 | err = downloader.TriggerDownload(authorizedDownload) 94 | handleErrors(err) 95 | } 96 | } 97 | } 98 | 99 | func checkToDownload(fileName string, expectedMD5 string) bool { 100 | if fileExists(fileName) { 101 | fmt.Printf("Found file %s, calculating MD5 checksum to validate\n", fileName) 102 | file, err := os.Open(fileName) 103 | handleErrors(err) 104 | defer file.Close() 105 | 106 | // Create a hash instance and pass the file through it 107 | hash := md5.New() 108 | _, err = io.Copy(hash, file) 109 | handleErrors(err) 110 | // Usage for Sprintf needed as a standard string conversation broke some strings 111 | calculatedMD5 := fmt.Sprintf("%x", hash.Sum(nil)) 112 | 113 | if expectedMD5 != calculatedMD5 { 114 | fmt.Printf("Expected checksum of [%s], but found [%s].\nAttempting to re-download.\n", expectedMD5, calculatedMD5) 115 | return true 116 | } else { 117 | fmt.Println("Checksum validate completed successfully. No need to re-download.") 118 | return false 119 | } 120 | } 121 | return true 122 | } 123 | 124 | func fileExists(filename string) bool { 125 | info, err := os.Stat(filename) 126 | if os.IsNotExist(err) { 127 | return false 128 | } 129 | return !info.IsDir() 130 | } 131 | 132 | func init() { 133 | rootCmd.AddCommand(downloadCmd) 134 | downloadCmd.Flags().StringVarP(&slug, "product", "p", "", "Product code") 135 | downloadCmd.Flags().StringVarP(&subProduct, "subproduct", "s", "", "Sub Product code") 136 | downloadCmd.Flags().StringVarP(&version, "version", "v", "", "Version string. Can contain a glob.") 137 | downloadCmd.Flags().StringVarP(&fileName, "filename", "f", "", "Filename string. Can contain one or more globs. When using * wrap the text in single quotes.") 138 | downloadCmd.Flags().StringVarP(&manifestFile, "manifest", "m", "", "Filename of the manifest containing details of what to download") 139 | downloadCmd.Flags().StringVarP(&outputDir, "output", "o", "", "Directory to download files to") 140 | downloadCmd.Flags().BoolVarP(&acceptEula, "accepteula", "a", false, "Filename string") 141 | downloadCmd.Flags().BoolVarP(&forceDownload, "forcedownload", "d", false, "(optional) Force a file to be re-downloaded even if it already exists") 142 | downloadCmd.Flags().StringVarP(&dlgType, "type", "t", "product_binary", "(optional) Download type. One of: (product_binary, drivers_tools, custom_iso, addons). Default: product_binary") 143 | } 144 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in vmware-customer-connect-cli project and our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at oss-coc@vmware.com. 63 | All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series 85 | of actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or 92 | permanent ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within 112 | the community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.0, available at 118 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 119 | 120 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 121 | enforcement ladder](https://github.com/mozilla/diversity). 122 | 123 | [homepage]: https://www.contributor-covenant.org 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | https://www.contributor-covenant.org/faq. Translations are available at 127 | https://www.contributor-covenant.org/translations. 128 | -------------------------------------------------------------------------------- /test/bats/download.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helpers 4 | 5 | setup() { 6 | setup_command 7 | export_errors 8 | export_yamls 9 | export TEMP_DIR="$(mktemp -dt bats.XXXXX)" 10 | } 11 | 12 | teardown() { 13 | rm -rf $TEMP_DIR 14 | rm -f ${HOME}/vcc-downloads/VMware-Tools-*.zip 15 | rm -f ${HOME}/vcc-downloads/nsx-lcp-*-le.zip 16 | echo "" 17 | } 18 | 19 | @test "download driver file successfully to temp" { 20 | $VCC_CMD logout 21 | rm -f $TEMP_DIR/* 22 | # command cannot be stored as a variable because bats does not properly process the speech mark needed for the -v flag 23 | run $VCC_CMD download -p vmware_vsphere -t drivers_tools -s vs-mgmt-sdk80u2 -v '*' -f VMware-vSphere-SDK-*.zip --accepteula -o $TEMP_DIR 24 | echo "$output" 25 | [[ "$output" != *"No output directory set."* ]] 26 | [[ "$output" == *"Collecting download payload"* ]] 27 | [[ "$output" == *"Download started to"* ]] 28 | [[ "$output" == *"Download finished"* ]] 29 | [ "$status" -eq 0 ] 30 | [ -f $TEMP_DIR/VMware-vSphere-SDK-*.zip ] 31 | } 32 | 33 | @test "download iso file successfully to temp" { 34 | $VCC_CMD logout 35 | rm -f $TEMP_DIR/* 36 | # command cannot be stored as a variable because bats does not properly process the speech mark needed for the -v flag 37 | run $VCC_CMD download -p vmware_vsphere -t custom_iso -s oem-esxi80u2-hitachi -v '*' -f VMware-ESXi-*.iso --accepteula -o $TEMP_DIR 38 | echo "$output" 39 | [[ "$output" != *"No output directory set."* ]] 40 | [[ "$output" == *"Collecting download payload"* ]] 41 | [[ "$output" == *"Download started to"* ]] 42 | [[ "$output" == *"Download finished"* ]] 43 | [ "$status" -eq 0 ] 44 | [ -f $TEMP_DIR/VMware-ESXi-*.iso ] 45 | } 46 | 47 | @test "download addon file successfully to temp" { 48 | $VCC_CMD logout 49 | rm -f $TEMP_DIR/* 50 | # command cannot be stored as a variable because bats does not properly process the speech mark needed for the -v flag 51 | run $VCC_CMD download -p vmware_vsphere -t addons -s addon_esxi80u2_hitachi -v '*' -f VMware-ESXi-8.0*.zip --accepteula -o $TEMP_DIR 52 | echo "$output" 53 | [[ "$output" != *"No output directory set."* ]] 54 | [[ "$output" == *"Collecting download payload"* ]] 55 | [[ "$output" == *"Download started to"* ]] 56 | [[ "$output" == *"Download finished"* ]] 57 | [ "$status" -eq 0 ] 58 | [ -f $TEMP_DIR/VMware-ESXi-8.0*.zip ] 59 | } 60 | 61 | @test "download single file successfully to temp" { 62 | $VCC_CMD logout 63 | rm -f $TEMP_DIR/* 64 | local cmd="$VCC_CMD download -p vmware_horizon_clients -s cart+andrd_x8632 -v 2106 -f VMware-Horizon-Client-AndroidOS-x86-*-store.apk --accepteula -o $TEMP_DIR" 65 | echo $cmd 66 | run $cmd 67 | echo "$output" 68 | [[ "$output" != *"No output directory set."* ]] 69 | [[ "$output" == *"Collecting download payload"* ]] 70 | [[ "$output" == *"Download started to"* ]] 71 | [[ "$output" == *"Download finished"* ]] 72 | [ "$status" -eq 0 ] 73 | [ -f $TEMP_DIR/VMware-Horizon-Client-*.apk ] 74 | } 75 | 76 | 77 | @test "re-download single file successfully to temp" { 78 | $VCC_CMD logout 79 | rm -f $TEMP_DIR/* 80 | local cmd="$VCC_CMD download -p vmware_tools -s vmtools -v 11.3.0 -f VMware-Tools-darwin-*.gz --accepteula -o $TEMP_DIR" 81 | echo "$cmd" 82 | run $cmd 83 | run $cmd 84 | echo "$output" 85 | [[ "$output" != *"No output directory set."* ]] 86 | [[ "$output" == *"Collecting download payload"* ]] 87 | [[ "$output" == *"Checksum validate completed successfully. No need to re-download."* ]] 88 | [ "$status" -eq 0 ] 89 | [ -f $TEMP_DIR/VMware-Tools-darwin-*.gz ] 90 | local cmd="$VCC_CMD download -p vmware_tools -s vmtools -v 11.3.0 -f VMware-Tools-darwin-*.gz --accepteula -o $TEMP_DIR --forcedownload" 91 | echo "$cmd" 92 | run $cmd 93 | echo "$output" 94 | [[ "$output" != *"No output directory set."* ]] 95 | [[ "$output" == *"Collecting download payload"* ]] 96 | [[ "$output" == *"Download started to"* ]] 97 | [[ "$output" == *"Download finished"* ]] 98 | [ "$status" -eq 0 ] 99 | [ -f $TEMP_DIR/VMware-Tools-darwin-*.gz ] 100 | } 101 | 102 | @test "download single file successfully to user vcc-downloads" { 103 | rm -f $TEMP_DIR/* 104 | local cmd="$VCC_CMD download -p vmware_tools -s vmtools -v 11.3.0 -f VMware-Tools-darwin-*.zip --accepteula" 105 | echo $cmd 106 | run $cmd 107 | echo "$output" 108 | [[ "$output" == *"No output directory set."* ]] 109 | [[ "$output" == *"Collecting download payload"* ]] 110 | [[ "$output" == *"Download started to"* ]] 111 | [[ "$output" == *"Download finished"* ]] 112 | [ "$status" -eq 0 ] 113 | [ -f $HOME/vcc-downloads/VMware-Tools-darwin-11.3.0-*.zip ] 114 | } 115 | 116 | @test "download multiple files successfully to temp" { 117 | rm -f $TEMP_DIR/* 118 | run $VCC_CMD download -p vmware_tools -s vmtools -v 11.3.0 -f VMware-Tools-darwin-* --accepteula -o $TEMP_DIR 119 | echo "$output" 120 | [[ "$output" != *"No output directory set."* ]] 121 | [[ "$output" == *"Collecting download payload"* ]] 122 | [[ "$output" == *"Download started to"* ]] 123 | [[ "$output" == *"Download finished"* ]] 124 | [ "$status" -eq 0 ] 125 | [ -f $TEMP_DIR/VMware-Tools-darwin-*.zip ] 126 | [ -f $TEMP_DIR/VMware-Tools-darwin-*.tar.gz ] 127 | } 128 | 129 | @test "download from manifest" { 130 | rm -f $TEMP_DIR/* 131 | run $VCC_CMD download -m <(echo "$VALID_YAML") --accepteula -o $TEMP_DIR 132 | echo "$output" 133 | [[ "$output" == *"Opening manifest file:"* ]] 134 | [[ "$output" == *"Collecting download payload"* ]] 135 | [[ "$output" == *"Download started to"* ]] 136 | [[ "$output" == *"Download finished"* ]] 137 | [ "$status" -eq 0 ] 138 | ls -l $TEMP_DIR 139 | [ -f $TEMP_DIR/VMware-Tools-darwin-*.zip ] 140 | [ -f $TEMP_DIR/VMware-Tools-darwin-*.tar.gz ] 141 | [ -f $TEMP_DIR/VMware-Tools-other-*.tar.gz ] 142 | } 143 | 144 | @test "download from manifest missing field" { 145 | run $VCC_CMD download -m <(echo "$INVALID_YAML_MISSING_FIELD") --accepteula -o $TEMP_DIR 146 | echo "$output" 147 | [[ "$output" == *"Opening manifest file:"* ]] 148 | [[ "$output" == *"Manifest entry 0 does not have the 4 required keys!"* ]] 149 | [[ "$output" != *"Collecting download payload"* ]] 150 | [[ "$output" != *"Download started to"* ]] 151 | [[ "$output" != *"Download finished"* ]] 152 | [ "$status" -eq 1 ] 153 | } 154 | 155 | @test "download from manifest invalid type" { 156 | run $VCC_CMD download -m <(echo "$INVALID_YAML_INVALID_TYPE") --accepteula -o $TEMP_DIR 157 | echo "$output" 158 | [[ "$output" == *"Opening manifest file:"* ]] 159 | [[ "$output" == *"Parsing file failed with error:"* ]] 160 | [[ "$output" != *"Collecting download payload"* ]] 161 | [[ "$output" != *"Download started to"* ]] 162 | [[ "$output" != *"Download finished"* ]] 163 | [ "$status" -eq 1 ] 164 | } 165 | 166 | @test "download with invalid product" { 167 | run $VCC_CMD download -p INVALID -s vmtools -v 11.3.0 -f VMware-Tools-darwin-*.zip --accepteula 168 | echo "$output" 169 | [[ "$output" == *"$ERRORINVALIDSLUG"* ]] 170 | [ "$status" -eq 1 ] 171 | } 172 | 173 | @test "download with invalid subproduct" { 174 | run $VCC_CMD download -p vmware_tools -s INVALID -v 11.3.0 -f VMware-Tools-darwin-*.zip --accepteula 175 | echo "$output" 176 | [[ "$output" == *"$ERRORINVALIDSUBPRODUCT"* ]] 177 | [ "$status" -eq 1 ] 178 | } 179 | 180 | @test "download with invalid version" { 181 | run $VCC_CMD download -p vmware_tools -s vmtools -v INVALID -f VMware-Tools-darwin-*.zip --accepteula 182 | echo "$output" 183 | [[ "$output" == *"$ERRORINVALIDVERSION"* ]] 184 | [ "$status" -eq 1 ] 185 | } 186 | 187 | @test "download with invalid credentials" { 188 | $VCC_CMD logout 189 | run $VCC_CMD download -p vmware_tools -s vmtools -v 11.3.0 -f VMware-Tools-darwin-*.zip --accepteula --user invalid --pass invalid 190 | echo "$output" 191 | [[ "$output" == *"$ERRORAUTHENTICATIONFAILURE"* ]] 192 | [ "$status" -eq 1 ] 193 | } 194 | 195 | @test "download when not entitled" { 196 | $VCC_CMD logout 197 | rm -f ${HOME}/vcc-downloads/VMware-VMvisor-Installer-.7*.iso 198 | run $VCC_CMD download -p vmware_nsx -s nsx_le -v '4.*' -f nsx-lcp-*-le.zip --accepteula 199 | echo "$output" 200 | [[ "$output" == *"$ERRORNOTENTITLED"* ]] 201 | [ "$status" -eq 1 ] 202 | } 203 | 204 | @test "download with invalid output directory" { 205 | run $VCC_CMD download -p vmware_tools -s vmtools -v 11.3.0 -f VMware-Tools-darwin-*.zip --accepteula -o /tmp/stilton/on/toast 206 | echo "$output" 207 | [[ "$output" == *"ERROR: Output directory"* ]] 208 | [ "$status" -eq 1 ] 209 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by the 14 | copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all other 17 | entities that control, are controlled by, or are under common control 18 | with that entity. For the purposes of this definition, "control" means 19 | (i) the power, direct or indirect, to cause the direction or management 20 | of such entity, whether by contract or otherwise, or (ii) ownership 21 | of fifty percent (50%) or more of the outstanding shares, or (iii) 22 | beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity exercising 25 | permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation source, 29 | and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical transformation 32 | or translation of a Source form, including but not limited to compiled 33 | object code, generated documentation, and conversions to other media 34 | types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a copyright 38 | notice that is included in or attached to the work (an example is provided 39 | in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object form, 42 | that is based on (or derived from) the Work and for which the editorial 43 | revisions, annotations, elaborations, or other modifications represent, 44 | as a whole, an original work of authorship. For the purposes of this 45 | License, Derivative Works shall not include works that remain separable 46 | from, or merely link (or bind by name) to the interfaces of, the Work 47 | and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including the 50 | original version of the Work and any modifications or additions to 51 | that Work or Derivative Works thereof, that is intentionally submitted 52 | to Licensor for inclusion in the Work by the copyright owner or by an 53 | individual or Legal Entity authorized to submit on behalf of the copyright 54 | owner. For the purposes of this definition, "submitted" means any form of 55 | electronic, verbal, or written communication sent to the Licensor or its 56 | representatives, including but not limited to communication on electronic 57 | mailing lists, source code control systems, and issue tracking systems 58 | that are managed by, or on behalf of, the Licensor for the purpose of 59 | discussing and improving the Work, but excluding communication that is 60 | conspicuously marked or otherwise designated in writing by the copyright 61 | owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. 68 | Subject to the terms and conditions of this License, each Contributor 69 | hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, 70 | royalty-free, irrevocable copyright license to reproduce, prepare 71 | Derivative Works of, publicly display, publicly perform, sublicense, and 72 | distribute the Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. 75 | Subject to the terms and conditions of this License, each Contributor 76 | hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, 77 | royalty- free, irrevocable (except as stated in this section) patent 78 | license to make, have made, use, offer to sell, sell, import, and 79 | otherwise transfer the Work, where such license applies only to those 80 | patent claims licensable by such Contributor that are necessarily 81 | infringed by their Contribution(s) alone or by combination of 82 | their Contribution(s) with the Work to which such Contribution(s) 83 | was submitted. If You institute patent litigation against any entity 84 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 85 | Work or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses granted 87 | to You under this License for that Work shall terminate as of the date 88 | such litigation is filed. 89 | 90 | 4. Redistribution. 91 | You may reproduce and distribute copies of the Work or Derivative Works 92 | thereof in any medium, with or without modifications, and in Source or 93 | Object form, provided that You meet the following conditions: 94 | 95 | a. You must give any other recipients of the Work or Derivative Works 96 | a copy of this License; and 97 | 98 | b. You must cause any modified files to carry prominent notices stating 99 | that You changed the files; and 100 | 101 | c. You must retain, in the Source form of any Derivative Works that 102 | You distribute, all copyright, patent, trademark, and attribution 103 | notices from the Source form of the Work, excluding those notices 104 | that do not pertain to any part of the Derivative Works; and 105 | 106 | d. If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one of 111 | the following places: within a NOTICE text file distributed as part 112 | of the Derivative Works; within the Source form or documentation, 113 | if provided along with the Derivative Works; or, within a display 114 | generated by the Derivative Works, if and wherever such third-party 115 | notices normally appear. The contents of the NOTICE file are for 116 | informational purposes only and do not modify the License. You 117 | may add Your own attribution notices within Derivative Works that 118 | You distribute, alongside or as an addendum to the NOTICE text 119 | from the Work, provided that such additional attribution notices 120 | cannot be construed as modifying the License. You may add Your own 121 | copyright statement to Your modifications and may provide additional 122 | or different license terms and conditions for use, reproduction, or 123 | distribution of Your modifications, or for any such Derivative Works 124 | as a whole, provided Your use, reproduction, and distribution of the 125 | Work otherwise complies with the conditions stated in this License. 126 | 127 | 5. Submission of Contributions. 128 | Unless You explicitly state otherwise, any Contribution intentionally 129 | submitted for inclusion in the Work by You to the Licensor shall be 130 | under the terms and conditions of this License, without any additional 131 | terms or conditions. Notwithstanding the above, nothing herein shall 132 | supersede or modify the terms of any separate license agreement you may 133 | have executed with Licensor regarding such Contributions. 134 | 135 | 6. Trademarks. 136 | This License does not grant permission to use the trade names, trademarks, 137 | service marks, or product names of the Licensor, except as required for 138 | reasonable and customary use in describing the origin of the Work and 139 | reproducing the content of the NOTICE file. 140 | 141 | 7. Disclaimer of Warranty. 142 | Unless required by applicable law or agreed to in writing, Licensor 143 | provides the Work (and each Contributor provides its Contributions) on 144 | an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 145 | express or implied, including, without limitation, any warranties or 146 | conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR 147 | A PARTICULAR PURPOSE. You are solely responsible for determining the 148 | appropriateness of using or redistributing the Work and assume any risks 149 | associated with Your exercise of permissions under this License. 150 | 151 | 8. Limitation of Liability. 152 | In no event and under no legal theory, whether in tort (including 153 | negligence), contract, or otherwise, unless required by applicable law 154 | (such as deliberate and grossly negligent acts) or agreed to in writing, 155 | shall any Contributor be liable to You for damages, including any direct, 156 | indirect, special, incidental, or consequential damages of any character 157 | arising as a result of this License or out of the use or inability to 158 | use the Work (including but not limited to damages for loss of goodwill, 159 | work stoppage, computer failure or malfunction, or any and all other 160 | commercial damages or losses), even if such Contributor has been advised 161 | of the possibility of such damages. 162 | 163 | 9. Accepting Warranty or Additional Liability. 164 | While redistributing the Work or Derivative Works thereof, You may 165 | choose to offer, and charge a fee for, acceptance of support, warranty, 166 | indemnity, or other liability obligations and/or rights consistent with 167 | this License. However, in accepting such obligations, You may act only 168 | on Your own behalf and on Your sole responsibility, not on behalf of 169 | any other Contributor, and only if You agree to indemnify, defend, and 170 | hold each Contributor harmless for any liability incurred by, or claims 171 | asserted against, such Contributor by reason of your accepting any such 172 | warranty or additional liability. 173 | 174 | END OF TERMS AND CONDITIONS 175 | 176 | APPENDIX: How to apply the Apache License to your work. 177 | 178 | To apply the Apache License to your work, attach the following 179 | boilerplate notice, with the fields enclosed by brackets "[]" 180 | replaced with your own identifying information. (Don't include 181 | the brackets!) The text should be enclosed in the appropriate 182 | comment syntax for the file format. We also recommend that a 183 | file or class name and description of purpose be included on the 184 | same "printed page" as the copyright notice for easier 185 | identification within third-party archives. 186 | 187 | Copyright [yyyy] [name of copyright owner] 188 | 189 | Licensed under the Apache License, Version 2.0 (the "License"); 190 | you may not use this file except in compliance with the License. 191 | You may obtain a copy of the License at 192 | 193 | http://www.apache.org/licenses/LICENSE-2.0 194 | 195 | Unless required by applicable law or agreed to in writing, software 196 | distributed under the License is distributed on an "AS IS" BASIS, 197 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 198 | See the License for the specific language governing permissions and 199 | limitations under the License. -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 10 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 11 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 12 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 13 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 14 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 15 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 16 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 17 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 18 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 19 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 20 | github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= 21 | github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= 22 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 23 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 24 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 25 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 26 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 27 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 28 | github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 29 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 30 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 31 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 32 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 33 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 34 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 35 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 36 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 37 | github.com/frankban/quicktest v1.2.2 h1:xfmOhhoH5fGPgbEAlhLpJH9p0z/0Qizio9osmvn9IUY= 38 | github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20= 39 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 40 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 41 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 42 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 43 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 44 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 45 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 46 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 47 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 48 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 49 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 50 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 51 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 52 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 53 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 54 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 55 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 56 | github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 57 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 58 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 59 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 60 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 61 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 62 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 63 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 64 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 65 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 66 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 67 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 68 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 69 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 70 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 71 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 72 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 73 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 74 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 75 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 76 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 77 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 78 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 79 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 80 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 81 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 82 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 83 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 84 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 85 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 86 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 87 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 88 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 89 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 90 | github.com/orirawlings/persistent-cookiejar v0.3.2 h1:6SZYaE3s8H+x1Xdw+E8mslO1OXLme6HZMv95pjiwEPU= 91 | github.com/orirawlings/persistent-cookiejar v0.3.2/go.mod h1:mndFQuhWIsBaloT/dZPibBxlWfFA9R1EraeTTcSOf8A= 92 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 93 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 94 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 95 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 96 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 97 | github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= 98 | github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 99 | github.com/rogpeppe/clock v0.0.0-20190514195947-2896927a307a h1:3QH7VyOaaiUHNrA9Se4YQIRkDTCw1EJls9xTUCaCeRM= 100 | github.com/rogpeppe/clock v0.0.0-20190514195947-2896927a307a/go.mod h1:4r5QyqhjIWCcK8DO4KMclc5Iknq5qVBAlbYYzAbUScQ= 101 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 102 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 103 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 104 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 105 | github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= 106 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= 107 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 108 | github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= 109 | github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= 110 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 111 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 112 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 113 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 114 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 115 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 116 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 117 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 118 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 119 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 120 | github.com/vmware-labs/vmware-customer-connect-sdk v0.0.0-20231017102133-ac416aaab24e h1:XvRGyUNDOfQeYm/Olmm79GkRMsiY8TvnaVAoRLRn4UI= 121 | github.com/vmware-labs/vmware-customer-connect-sdk v0.0.0-20231017102133-ac416aaab24e/go.mod h1:Qrl4FgVQl4Jz2T2+i1Nij3KymeSh0eTQLM6OfVgJZrQ= 122 | github.com/vmware-labs/vmware-customer-connect-sdk v0.0.0-20231205160200-e98924b802a6 h1:6sWUT1K2cDr56/5bKkv0pDs8mtLzhzygMTg5MkwAoBU= 123 | github.com/vmware-labs/vmware-customer-connect-sdk v0.0.0-20231205160200-e98924b802a6/go.mod h1:DqQ9L5crjmVDoeoNNc4VJ4xZapfKfKcq7p2z5u9n7Os= 124 | github.com/vmware-labs/vmware-customer-connect-sdk v0.0.0-20231207154734-713cae36dd56 h1:42jqd5SW7eHDPrmgvkUsJSyqCzAYmW/aw9bDn5CwPQo= 125 | github.com/vmware-labs/vmware-customer-connect-sdk v0.0.0-20231207154734-713cae36dd56/go.mod h1:DqQ9L5crjmVDoeoNNc4VJ4xZapfKfKcq7p2z5u9n7Os= 126 | github.com/vmware-labs/vmware-customer-connect-sdk v0.0.0-20231208115351-81519e7588c7 h1:LHS5Z0/6ssDwAQQV7iK9jqDcs6R/0BCDfBmLjdRDX0k= 127 | github.com/vmware-labs/vmware-customer-connect-sdk v0.0.0-20231208115351-81519e7588c7/go.mod h1:DqQ9L5crjmVDoeoNNc4VJ4xZapfKfKcq7p2z5u9n7Os= 128 | github.com/vmware-labs/vmware-customer-connect-sdk v0.0.0-20231212133750-ad52df1677f3 h1:faIXOAHZfikmnJBUNcHj7kwIU9ByEQnOB4QwxNWNI7s= 129 | github.com/vmware-labs/vmware-customer-connect-sdk v0.0.0-20231212133750-ad52df1677f3/go.mod h1:DqQ9L5crjmVDoeoNNc4VJ4xZapfKfKcq7p2z5u9n7Os= 130 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 131 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 132 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 133 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 134 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 135 | go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= 136 | go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= 137 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 138 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 139 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 140 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 141 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 142 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 143 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 144 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 145 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 146 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 147 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 148 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 149 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 150 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 151 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 152 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 153 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 154 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 155 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 156 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 157 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 158 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 159 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 160 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 161 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 162 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 163 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 164 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 165 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 166 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 167 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 168 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 169 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 170 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 171 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 172 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 173 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 174 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 175 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 176 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 177 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 178 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 179 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 180 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 181 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 182 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 183 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 184 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 185 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 186 | golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= 187 | golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= 188 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 189 | golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= 190 | golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 191 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 192 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 193 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 194 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 195 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 196 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 197 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 198 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 199 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 200 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 201 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 202 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 203 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 204 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 205 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 206 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 207 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 208 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 209 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 210 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 211 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 212 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 213 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 214 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 215 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 216 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 217 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 218 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 219 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 220 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 221 | golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 222 | golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= 223 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 224 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= 225 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 226 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 227 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 228 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 229 | golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= 230 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 231 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 232 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 233 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 234 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 235 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 236 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 237 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 238 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 239 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 240 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 241 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 242 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 243 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 244 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 245 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 246 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 247 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 248 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 249 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 250 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 251 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 252 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 253 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 254 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 255 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 256 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 257 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 258 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 259 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 260 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 261 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 262 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 263 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 264 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 265 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 266 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 267 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 268 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 269 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 270 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 271 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 272 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 273 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 274 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 275 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 276 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 277 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 278 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 279 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 280 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 281 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 282 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 283 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 284 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 285 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 286 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 287 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 288 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 289 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 290 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 291 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 292 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 293 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 294 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 295 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 296 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 297 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 298 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 299 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 300 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 301 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 302 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 303 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 304 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 305 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 306 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 307 | gopkg.in/retry.v1 v1.0.3 h1:a9CArYczAVv6Qs6VGoLMio99GEs7kY9UzSF9+LD+iGs= 308 | gopkg.in/retry.v1 v1.0.3/go.mod h1:FJkXmWiMaAo7xB+xhvDF59zhfjDWyzmyAxiT4dB688g= 309 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 310 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 311 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 312 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 313 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 314 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 315 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 316 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 317 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 318 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 319 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 320 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 321 | --------------------------------------------------------------------------------