├── .dockerignore ├── .github ├── pull_request_template.md └── workflows │ └── ci.yml ├── .gitignore ├── .golangci.yml ├── CONTRIBUTING.md ├── LICENSE ├── OWNERS ├── OWNERS_ALIASES ├── README.md ├── SECURITY_CONTACTS ├── assets └── logo │ ├── horizontal │ ├── black │ │ ├── krew-horizontal-black.png │ │ └── krew-horizontal-black.svg │ ├── color │ │ ├── krew-horizontal-color.png │ │ └── krew-horizontal-color.svg │ └── white │ │ ├── krew-horizontal-white.png │ │ └── krew-horizontal-white.svg │ ├── icon │ ├── black │ │ ├── krew-icon-black.png │ │ └── krew-icon-black.svg │ ├── color │ │ ├── krew-icon-color.png │ │ └── krew-icon-color.svg │ └── white │ │ ├── krew-icon-white.png │ │ └── krew-icon-white.svg │ └── stacked │ ├── black │ ├── krew-stacked-black.png │ └── krew-stacked-black.svg │ ├── color │ ├── krew-stacked-color.png │ └── krew-stacked-color.svg │ └── white │ ├── krew-stacked-white.png │ └── krew-stacked-white.svg ├── cmd ├── krew │ ├── cmd │ │ ├── index.go │ │ ├── info.go │ │ ├── install.go │ │ ├── install_test.go │ │ ├── internal │ │ │ ├── fetch_tag.go │ │ │ ├── fetch_tag_test.go │ │ │ ├── security_notice.go │ │ │ ├── setup_check.go │ │ │ ├── setup_check_test.go │ │ │ └── warning.go │ │ ├── list.go │ │ ├── namingutils.go │ │ ├── namingutils_test.go │ │ ├── root.go │ │ ├── search.go │ │ ├── search_test.go │ │ ├── uninstall.go │ │ ├── update.go │ │ ├── upgrade.go │ │ └── version.go │ └── main.go └── validate-krew-manifest │ ├── main.go │ └── main_test.go ├── code-of-conduct.md ├── docs ├── CONTRIBUTOR_GUIDE.md ├── DEVELOPER_GUIDE.md ├── KREW_ARCHITECTURE.md ├── KREW_LOGO.md ├── NAMING_GUIDE.md ├── PLUGIN_LIFECYCLE.md ├── RELEASING_KREW.md ├── USER_GUIDE.md └── src │ ├── krew_general.svg │ ├── krew_installation.svg │ ├── krew_upgrade.svg │ └── krew_upgrade_self.svg ├── go.mod ├── go.sum ├── hack ├── boilerplate │ ├── boilerplate.Dockerfile.txt │ ├── boilerplate.Makefile.txt │ ├── boilerplate.go.txt │ ├── boilerplate.py │ ├── boilerplate.py.txt │ └── boilerplate.sh.txt ├── ensure-kubectl-installed.sh ├── install-gox.sh ├── krew.yaml ├── make-all.sh ├── make-binaries.sh ├── make-binary.sh ├── make-release-artifacts.sh ├── make-release-notes.sh ├── run-in-docker.sh ├── run-integration-tests.sh ├── run-lint.sh ├── run-tests.sh ├── sandboxed.Dockerfile ├── verify-boilerplate.sh ├── verify-code-patterns.sh ├── verify-index-migration.sh └── verify-installation.sh ├── integration_test ├── commandline_test.go ├── help_test.go ├── index_test.go ├── info_test.go ├── install_test.go ├── list_test.go ├── migration_test.go ├── search_test.go ├── testdata │ ├── ctx.yaml │ ├── foo.tar.gz │ └── foo.yaml ├── testutil_test.go ├── uninstall_test.go ├── update_test.go ├── upgrade_test.go └── version_test.go ├── internal ├── download │ ├── downloader.go │ ├── downloader_test.go │ ├── fetch.go │ ├── fetch_test.go │ ├── testdata │ │ ├── bash-ascii-file │ │ ├── bash-utf8-file │ │ ├── null-file │ │ ├── test-flat-hierarchy.tar.gz │ │ ├── test-flat-hierarchy.zip │ │ ├── test-with-directory-entry.tar.gz │ │ ├── test-with-directory-entry.zip │ │ ├── test-with-no-directory-entry.tar.gz │ │ ├── test-with-no-directory-entry.zip │ │ └── test │ │ │ └── foo │ ├── verifier.go │ └── verifier_test.go ├── environment │ ├── environment.go │ └── environment_test.go ├── gitutil │ └── git.go ├── index │ ├── indexoperations │ │ ├── index.go │ │ └── index_test.go │ ├── indexscanner │ │ ├── scanner.go │ │ ├── scanner_test.go │ │ └── testdata │ │ │ └── testindex │ │ │ ├── dontscan.yaml │ │ │ └── plugins │ │ │ ├── badplugin.yaml │ │ │ ├── badplugin2.yaml │ │ │ ├── bar.yaml │ │ │ ├── foo.yaml │ │ │ ├── notyaml.txt │ │ │ └── wrongname.yaml │ └── validation │ │ ├── validate.go │ │ └── validate_test.go ├── indexmigration │ ├── migration.go │ └── migration_test.go ├── installation │ ├── install.go │ ├── install_test.go │ ├── move.go │ ├── move_test.go │ ├── platform.go │ ├── platform_test.go │ ├── receipt │ │ ├── receipt.go │ │ └── receipt_test.go │ ├── semver │ │ ├── version.go │ │ └── version_test.go │ ├── testdata │ │ ├── bin │ │ │ └── kubectl-foo │ │ ├── index │ │ │ └── foo │ │ │ │ ├── AAAnotplugin │ │ │ │ └── .gitkeep │ │ │ │ └── deadbeef │ │ │ │ ├── .gitkeep │ │ │ │ └── kubectl-foo │ │ ├── plugin-foo │ │ │ └── kubectl-foo │ │ ├── testdir_A │ │ │ ├── .secret │ │ │ └── notsecret │ │ └── testdir_B │ │ │ └── .gitkeep │ ├── upgrade.go │ ├── util.go │ └── util_test.go ├── pathutil │ ├── pathutil.go │ └── pathutil_test.go ├── receiptsmigration │ ├── migration.go │ └── migration_test.go ├── testutil │ ├── plugin.go │ ├── receipt.go │ └── tempdir.go └── version │ ├── version.go │ └── version_test.go ├── netlify.toml ├── pkg ├── constants │ └── constants.go └── index │ ├── types.go │ ├── util.go │ └── util_test.go └── site ├── .gitignore ├── LICENSE ├── config.yaml ├── content ├── _index.md ├── docs │ ├── _index.md │ ├── developer-guide │ │ ├── _index.md │ │ ├── custom-indexes.md │ │ ├── develop │ │ │ ├── _index.md │ │ │ ├── best-practices.md │ │ │ ├── naming-guide.md │ │ │ └── plugin-development.md │ │ ├── distributing-with-krew.md │ │ ├── example-plugin-manifests.md │ │ ├── installing-locally.md │ │ ├── plugin-manifest.md │ │ ├── plugin-stats.md │ │ └── release │ │ │ ├── _index.md │ │ │ ├── plugin-updates.md │ │ │ ├── release-automation.md │ │ │ └── submitting-to-krew.md │ └── user-guide │ │ ├── _index.md │ │ ├── configuration.md │ │ ├── install.md │ │ ├── list.md │ │ ├── quickstart.md │ │ ├── search.md │ │ ├── setup │ │ ├── _index.md │ │ ├── install.md │ │ ├── uninstall.md │ │ └── updates.md │ │ ├── uninstall.md │ │ ├── upgrade.md │ │ └── using-custom-indexes.md └── plugins.md ├── functions ├── README.md ├── go.mod ├── go.sum └── server │ └── main.go ├── layouts ├── 404.html ├── _default │ └── single.html ├── docs │ └── section.html ├── index.html ├── partials │ ├── backbutton.html │ ├── footer.html │ ├── header.html │ ├── navbar.html │ └── toc.html ├── robots.txt └── shortcodes │ ├── output.html │ ├── prompt.html │ └── toc.html └── static ├── css └── style.css └── img ├── favicon.svg └── krew-logo.svg /.dockerignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /hack/ 3 | /out/ 4 | /OWNERS 5 | /OWNERS_ALIASES 6 | /CONTRIBUTING.md 7 | /SECURITY_CONTACTS 8 | /.travis.yml 9 | /code-of-conduct.md 10 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Fixes #... 2 | Related issue: #... 3 | 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Kubernetes-sigs/krew CI 2 | on: 3 | push: 4 | branches: 5 | - '*' 6 | tags: 7 | - 'v*.*.*' 8 | pull_request: 9 | branches: 10 | - '*' 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | 18 | - name: Set up Go ${{ matrix.goVer }} 19 | uses: actions/setup-go@v5 20 | with: 21 | go-version: '1.22' 22 | id: go 23 | 24 | - name: Check out code into the Go module directory 25 | uses: actions/checkout@v4 26 | with: 27 | fetch-depth: 0 28 | 29 | - name: Install dependencies 30 | run: go mod download 31 | 32 | - name: Install gox 33 | run: hack/install-gox.sh 34 | 35 | - name: Ensure go.mod is already tidied 36 | run: go mod tidy && git diff --no-patch --exit-code 37 | 38 | - name: Verify code patterns 39 | run: hack/verify-code-patterns.sh 40 | 41 | - name: Verify boilerplate 42 | run: hack/verify-boilerplate.sh 43 | 44 | - name: Run code lint 45 | run: hack/run-lint.sh 46 | 47 | - name: Run unit tests 48 | run: go test -short ./... 49 | 50 | - name: Make binaries && verify krew installation 51 | run: hack/make-all.sh 52 | 53 | - name: Ensure kubectl installed 54 | run: hack/ensure-kubectl-installed.sh 55 | 56 | - name: Verify installation 57 | run: hack/verify-installation.sh 58 | 59 | - name: Run integration tests 60 | run: hack/run-integration-tests.sh 61 | 62 | - name: Verify index migration from 0.3.x to 0.4.x 63 | run: hack/verify-index-migration.sh 64 | 65 | - name: Make release notes 66 | if: contains(github.ref, 'tags') 67 | run: | 68 | echo 'RELEASE_NOTES<> $GITHUB_ENV 69 | TAG=$GITHUB_REF_NAME hack/make-release-notes.sh >> $GITHUB_ENV 70 | echo 'EOF' >> $GITHUB_ENV 71 | 72 | - name: Create a new release 73 | if: contains(github.ref, 'tags') 74 | id: create_release 75 | uses: softprops/action-gh-release@v2 76 | with: 77 | name: Release ${{ github.ref_name }} 78 | files: out/krew* 79 | body: ${{ env.RELEASE_NOTES }} 80 | fail_on_unmatched_files: true 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Files generated by JetBrains IDEs, e.g. IntelliJ IDEA 9 | .idea/ 10 | 11 | # Test binary, build with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | out/ 18 | build/ 19 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | tests: true 4 | linters: 5 | default: none 6 | enable: 7 | - errcheck 8 | - gocritic 9 | - govet 10 | - ineffassign 11 | - misspell 12 | - prealloc 13 | - revive 14 | - staticcheck 15 | - unconvert 16 | - unparam 17 | - unused 18 | settings: 19 | errcheck: 20 | check-type-assertions: false 21 | check-blank: false 22 | gocritic: 23 | disabled-checks: 24 | - hugeParam 25 | - rangeValCopy 26 | - unnamedResult 27 | enabled-tags: 28 | - performance 29 | - diagnostic 30 | - style 31 | - experimental 32 | - opinionated 33 | staticcheck: 34 | checks: 35 | - all 36 | - -SA1019 37 | exclusions: 38 | generated: lax 39 | presets: 40 | - comments 41 | - common-false-positives 42 | - legacy 43 | - std-error-handling 44 | paths: 45 | - hack 46 | - docs 47 | - third_party$ 48 | - builtin$ 49 | - examples$ 50 | formatters: 51 | enable: 52 | - gofmt 53 | - goimports 54 | settings: 55 | gofmt: 56 | simplify: true 57 | goimports: 58 | local-prefixes: 59 | - sigs.k8s.io/krew 60 | exclusions: 61 | generated: lax 62 | paths: 63 | - hack 64 | - docs 65 | - third_party$ 66 | - builtin$ 67 | - examples$ 68 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://git.k8s.io/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt: 4 | 5 | _As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._ 6 | 7 | ## Getting Started 8 | 9 | We have full documentation on how to get started contributing here: 10 | 11 | 14 | 15 | - [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests 16 | - [Kubernetes Contributor Guide](https://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](https://git.k8s.io/community/contributors/guide#contributing) 17 | - [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet/README.md) - Common resources for existing developers 18 | 19 | ## Mentorship 20 | 21 | - [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers! 22 | 23 | ## Contact Information 24 | 25 | To discuss about this project, you can use the following channels. You can find 26 | Krew maintainers at: 27 | 28 | - Kubernetes Slack channel: 29 | [**#krew**](https://kubernetes.slack.com/messages/krew) or 30 | [#sig-cli](https://kubernetes.slack.com/messages/sig-cli) 31 | - [SIG CLI Mailing list](https://groups.google.com/forum/#!forum/kubernetes-sig-cli) 32 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs at https://go.k8s.io/owners 2 | 3 | approvers: 4 | - krew-maintainers 5 | -------------------------------------------------------------------------------- /OWNERS_ALIASES: -------------------------------------------------------------------------------- 1 | aliases: 2 | krew-maintainers: 3 | - ahmetb 4 | - chriskim06 5 | - corneliusweig 6 | - soltysh 7 | -------------------------------------------------------------------------------- /SECURITY_CONTACTS: -------------------------------------------------------------------------------- 1 | # Defined below are the security contacts for this repo. 2 | # 3 | # They are the contact point for the Product Security Committee to reach out 4 | # to for triaging and handling of incoming issues. 5 | # 6 | # The below names agree to abide by the 7 | # [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy) 8 | # and will be removed and replaced if they violate that agreement. 9 | # 10 | # DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE 11 | # INSTRUCTIONS AT https://kubernetes.io/security/ 12 | 13 | ahmetb 14 | corneliusweig 15 | -------------------------------------------------------------------------------- /assets/logo/horizontal/black/krew-horizontal-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/krew/b1ac5bfdfb5bba3d652323ecd17febd40eea9d27/assets/logo/horizontal/black/krew-horizontal-black.png -------------------------------------------------------------------------------- /assets/logo/horizontal/color/krew-horizontal-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/krew/b1ac5bfdfb5bba3d652323ecd17febd40eea9d27/assets/logo/horizontal/color/krew-horizontal-color.png -------------------------------------------------------------------------------- /assets/logo/horizontal/white/krew-horizontal-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/krew/b1ac5bfdfb5bba3d652323ecd17febd40eea9d27/assets/logo/horizontal/white/krew-horizontal-white.png -------------------------------------------------------------------------------- /assets/logo/icon/black/krew-icon-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/krew/b1ac5bfdfb5bba3d652323ecd17febd40eea9d27/assets/logo/icon/black/krew-icon-black.png -------------------------------------------------------------------------------- /assets/logo/icon/color/krew-icon-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/krew/b1ac5bfdfb5bba3d652323ecd17febd40eea9d27/assets/logo/icon/color/krew-icon-color.png -------------------------------------------------------------------------------- /assets/logo/icon/white/krew-icon-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/krew/b1ac5bfdfb5bba3d652323ecd17febd40eea9d27/assets/logo/icon/white/krew-icon-white.png -------------------------------------------------------------------------------- /assets/logo/stacked/black/krew-stacked-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/krew/b1ac5bfdfb5bba3d652323ecd17febd40eea9d27/assets/logo/stacked/black/krew-stacked-black.png -------------------------------------------------------------------------------- /assets/logo/stacked/color/krew-stacked-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/krew/b1ac5bfdfb5bba3d652323ecd17febd40eea9d27/assets/logo/stacked/color/krew-stacked-color.png -------------------------------------------------------------------------------- /assets/logo/stacked/white/krew-stacked-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/krew/b1ac5bfdfb5bba3d652323ecd17febd40eea9d27/assets/logo/stacked/white/krew-stacked-white.png -------------------------------------------------------------------------------- /cmd/krew/cmd/info.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "os" 21 | "regexp" 22 | "strings" 23 | "unicode" 24 | 25 | "github.com/pkg/errors" 26 | "github.com/spf13/cobra" 27 | 28 | "sigs.k8s.io/krew/internal/index/indexscanner" 29 | "sigs.k8s.io/krew/internal/installation" 30 | "sigs.k8s.io/krew/internal/pathutil" 31 | "sigs.k8s.io/krew/pkg/index" 32 | ) 33 | 34 | // infoCmd represents the info command 35 | var infoCmd = &cobra.Command{ 36 | Use: "info", 37 | Short: "Show information about an available plugin", 38 | Long: `Show detailed information about an available plugin.`, 39 | Example: ` kubectl krew info PLUGIN 40 | kubectl krew info INDEX/PLUGIN`, 41 | RunE: func(_ *cobra.Command, args []string) error { 42 | index, plugin := pathutil.CanonicalPluginName(args[0]) 43 | 44 | p, err := indexscanner.LoadPluginByName(paths.IndexPluginsPath(index), plugin) 45 | if os.IsNotExist(err) { 46 | return errors.Errorf("plugin %q not found in index %q", args[0], index) 47 | } else if err != nil { 48 | return errors.Wrap(err, "failed to load plugin manifest") 49 | } 50 | printPluginInfo(os.Stdout, index, p) 51 | return nil 52 | }, 53 | PreRunE: checkIndex, 54 | Args: cobra.ExactArgs(1), 55 | } 56 | 57 | func printPluginInfo(out io.Writer, indexName string, plugin index.Plugin) { 58 | fmt.Fprintf(out, "NAME: %s\n", plugin.Name) 59 | fmt.Fprintf(out, "INDEX: %s\n", indexName) 60 | if platform, ok, err := installation.GetMatchingPlatform(plugin.Spec.Platforms); err == nil && ok { 61 | if platform.URI != "" { 62 | fmt.Fprintf(out, "URI: %s\n", platform.URI) 63 | fmt.Fprintf(out, "SHA256: %s\n", platform.Sha256) 64 | } 65 | } 66 | if plugin.Spec.Version != "" { 67 | fmt.Fprintf(out, "VERSION: %s\n", plugin.Spec.Version) 68 | } 69 | if plugin.Spec.Homepage != "" { 70 | fmt.Fprintf(out, "HOMEPAGE: %s\n", plugin.Spec.Homepage) 71 | } 72 | if plugin.Spec.Description != "" { 73 | fmt.Fprintf(out, "DESCRIPTION: \n%s\n", plugin.Spec.Description) 74 | } 75 | if plugin.Spec.Caveats != "" { 76 | fmt.Fprintf(out, "CAVEATS:\n%s\n", indent(plugin.Spec.Caveats)) 77 | } 78 | } 79 | 80 | // indent converts strings to an indented format ready for printing. 81 | // Example: 82 | // 83 | // \ 84 | // | This plugin is great, use it with great care. 85 | // | Also, plugin will require the following programs to run: 86 | // | * jq 87 | // | * base64 88 | // / 89 | func indent(s string) string { 90 | out := "\\\n" 91 | s = strings.TrimRightFunc(s, unicode.IsSpace) 92 | out += regexp.MustCompile("(?m)^").ReplaceAllString(s, " | ") 93 | out += "\n/" 94 | return out 95 | } 96 | 97 | func init() { 98 | rootCmd.AddCommand(infoCmd) 99 | } 100 | -------------------------------------------------------------------------------- /cmd/krew/cmd/install_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "net/http" 19 | "net/http/httptest" 20 | "path/filepath" 21 | "testing" 22 | ) 23 | 24 | func Test_readPluginFromURL(t *testing.T) { 25 | server := httptest.NewServer(http.FileServer(http.Dir(filepath.FromSlash("../../../integration_test/testdata")))) 26 | defer server.Close() 27 | 28 | tests := []struct { 29 | name string 30 | url string 31 | wantName string 32 | wantErr bool 33 | }{ 34 | { 35 | name: "successful request", 36 | url: server.URL + "/ctx.yaml", 37 | wantName: "ctx", 38 | }, 39 | { 40 | name: "invalid server", 41 | url: "http://example.invalid:80/foo.yaml", 42 | wantErr: true, 43 | }, 44 | { 45 | name: "bad response", 46 | url: server.URL + "/404.yaml", 47 | wantErr: true, 48 | }, 49 | } 50 | for _, tt := range tests { 51 | t.Run(tt.name, func(t *testing.T) { 52 | got, err := readPluginFromURL(tt.url) 53 | if (err != nil) != tt.wantErr { 54 | t.Fatalf("readPluginFromURL() error = %v, wantErr %v", err, tt.wantErr) 55 | return 56 | } 57 | if got.Name != tt.wantName { 58 | t.Fatalf("readPluginFromURL() returned name=%v; want=%v", got.Name, tt.wantName) 59 | } 60 | }) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /cmd/krew/cmd/internal/fetch_tag.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package internal 16 | 17 | import ( 18 | "encoding/json" 19 | "net/http" 20 | 21 | "github.com/pkg/errors" 22 | "k8s.io/klog/v2" 23 | ) 24 | 25 | const ( 26 | githubVersionURL = "https://api.github.com/repos/kubernetes-sigs/krew/releases/latest" 27 | ) 28 | 29 | // for testing 30 | var versionURL = githubVersionURL 31 | 32 | // FetchLatestTag fetches the tag name of the latest release from GitHub. 33 | func FetchLatestTag() (string, error) { 34 | klog.V(4).Infof("Fetching latest tag from GitHub") 35 | response, err := http.Get(versionURL) 36 | if err != nil { 37 | return "", errors.Wrapf(err, "could not GET the latest release") 38 | } 39 | defer response.Body.Close() 40 | if response.StatusCode != http.StatusOK { 41 | return "", errors.Errorf("expected HTTP status 200 OK, got %s", response.Status) 42 | } 43 | 44 | var res struct { 45 | Tag string `json:"tag_name"` 46 | } 47 | klog.V(4).Infof("Parsing response from GitHub") 48 | if err := json.NewDecoder(response.Body).Decode(&res); err != nil { 49 | return "", errors.Wrapf(err, "could not parse the response from GitHub") 50 | } 51 | klog.V(4).Infof("Fetched latest tag name (%s) from GitHub", res.Tag) 52 | return res.Tag, nil 53 | } 54 | -------------------------------------------------------------------------------- /cmd/krew/cmd/internal/fetch_tag_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package internal 16 | 17 | import ( 18 | "net/http" 19 | "net/http/httptest" 20 | "testing" 21 | ) 22 | 23 | func Test_fetchLatestTag_GitHubAPI(t *testing.T) { 24 | tag, err := FetchLatestTag() 25 | if err != nil { 26 | t.Error(err) 27 | } 28 | if tag == "" { 29 | t.Errorf("Expected a latest tag in the response") 30 | } 31 | } 32 | 33 | func Test_fetchLatestTag(t *testing.T) { 34 | tests := []struct { 35 | name string 36 | expected string 37 | response string 38 | shouldErr bool 39 | }{ 40 | { 41 | name: "broken json", 42 | response: `{"tag_name"::]`, 43 | shouldErr: true, 44 | }, 45 | { 46 | name: "field missing", 47 | response: `{}`, 48 | }, 49 | { 50 | name: "should get the correct tag", 51 | response: `{"tag_name": "some_tag"}`, 52 | expected: "some_tag", 53 | }, 54 | } 55 | 56 | for _, test := range tests { 57 | t.Run(test.name, func(tt *testing.T) { 58 | server := httptest.NewServer(http.HandlerFunc( 59 | func(w http.ResponseWriter, _ *http.Request) { 60 | _, _ = w.Write([]byte(test.response)) 61 | }, 62 | )) 63 | 64 | defer server.Close() 65 | 66 | versionURL = server.URL 67 | defer func() { versionURL = githubVersionURL }() 68 | 69 | tag, err := FetchLatestTag() 70 | if test.shouldErr && err == nil { 71 | tt.Error("Expected an error but found none") 72 | } 73 | if !test.shouldErr && err != nil { 74 | tt.Errorf("Expected no error but found: %s", err) 75 | } 76 | if tag != test.expected { 77 | tt.Errorf("Expected %s, got %s", test.expected, tag) 78 | } 79 | }) 80 | } 81 | } 82 | 83 | func Test_fetchLatestTagFailure(t *testing.T) { 84 | versionURL = "http://localhost/nirvana" 85 | defer func() { versionURL = githubVersionURL }() 86 | 87 | _, err := FetchLatestTag() 88 | if err == nil { 89 | t.Error("Expected an error but found none") 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /cmd/krew/cmd/internal/security_notice.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package internal 16 | 17 | import ( 18 | "os" 19 | 20 | "sigs.k8s.io/krew/pkg/constants" 21 | ) 22 | 23 | const securityNoticeFmt = `You installed plugin %q from the krew-index plugin repository. 24 | These plugins are not audited for security by the Krew maintainers. 25 | Run them at your own risk.` 26 | 27 | func PrintSecurityNotice(plugin string) { 28 | if plugin == constants.KrewPluginName { 29 | return // do not warn for krew itself 30 | } 31 | PrintWarning(os.Stderr, securityNoticeFmt+"\n", plugin) 32 | } 33 | -------------------------------------------------------------------------------- /cmd/krew/cmd/internal/setup_check.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package internal 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "path/filepath" 21 | "strings" 22 | 23 | "k8s.io/klog/v2" 24 | 25 | "sigs.k8s.io/krew/internal/environment" 26 | "sigs.k8s.io/krew/internal/installation" 27 | ) 28 | 29 | const ( 30 | instructionWindows = `To be able to run kubectl plugins, you need to add the 31 | "%%USERPROFILE%%\.krew\bin" directory to your PATH environment variable 32 | and restart your shell.` 33 | instructionUnixTemplate = `To be able to run kubectl plugins, you need to add 34 | the following to your %s 35 | 36 | and restart your shell.` 37 | instructionZsh = `~/.zshrc: 38 | 39 | export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"` 40 | instructionBash = `~/.bash_profile or ~/.bashrc: 41 | 42 | export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"` 43 | instructionFish = `config.fish: 44 | 45 | set -q KREW_ROOT; and set -gx PATH $PATH $KREW_ROOT/.krew/bin; or set -gx PATH $PATH $HOME/.krew/bin` 46 | instructionGeneric = `~/.bash_profile, ~/.bashrc, or ~/.zshrc: 47 | 48 | export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"` 49 | ) 50 | 51 | func IsBinDirInPATH(paths environment.Paths) bool { 52 | // For the first run we don't want to show a warning. 53 | _, err := os.Stat(paths.BasePath()) 54 | if err != nil { 55 | klog.V(4).Info("Assuming this is the first run") 56 | return os.IsNotExist(err) 57 | } 58 | 59 | binPath := paths.BinPath() 60 | for _, dirInPATH := range filepath.SplitList(os.Getenv("PATH")) { 61 | normalizedDirInPATH, err := filepath.Abs(dirInPATH) 62 | if err != nil { 63 | klog.Warningf("Cannot get absolute path: %v, %v", normalizedDirInPATH, err) 64 | continue 65 | } 66 | if normalizedDirInPATH == binPath { 67 | return true 68 | } 69 | } 70 | return false 71 | } 72 | 73 | func SetupInstructions() string { 74 | if installation.IsWindows() { 75 | return instructionWindows 76 | } 77 | 78 | var instruction string 79 | switch shell := os.Getenv("SHELL"); { 80 | case strings.HasSuffix(shell, "/zsh"): 81 | instruction = instructionZsh 82 | case strings.HasSuffix(shell, "/bash"): 83 | instruction = instructionBash 84 | case strings.HasSuffix(shell, "/fish"): 85 | instruction = instructionFish 86 | default: 87 | instruction = instructionGeneric 88 | } 89 | return fmt.Sprintf(instructionUnixTemplate, instruction) 90 | } 91 | -------------------------------------------------------------------------------- /cmd/krew/cmd/internal/warning.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package internal 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | 21 | "github.com/fatih/color" 22 | ) 23 | 24 | func PrintWarning(w io.Writer, format string, a ...interface{}) { 25 | color.New(color.FgRed, color.Bold).Fprint(w, "WARNING: ") 26 | fmt.Fprintf(w, format, a...) 27 | } 28 | -------------------------------------------------------------------------------- /cmd/krew/cmd/list.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "os" 21 | "sort" 22 | "strings" 23 | "text/tabwriter" 24 | 25 | "github.com/pkg/errors" 26 | "github.com/spf13/cobra" 27 | 28 | "sigs.k8s.io/krew/internal/installation" 29 | ) 30 | 31 | func init() { 32 | // listCmd represents the list command 33 | listCmd := &cobra.Command{ 34 | Use: "list", 35 | Short: "List installed kubectl plugins", 36 | Long: `Show a list of installed kubectl plugins and their versions. 37 | 38 | Remarks: 39 | Redirecting the output of this command to a program or file will only print 40 | the names of the plugins installed. This output can be piped back to the 41 | "install" command.`, 42 | Aliases: []string{"ls"}, 43 | RunE: func(_ *cobra.Command, _ []string) error { 44 | receipts, err := installation.GetInstalledPluginReceipts(paths.InstallReceiptsPath()) 45 | if err != nil { 46 | return errors.Wrap(err, "failed to find all installed versions") 47 | } 48 | 49 | // return sorted list of plugin names when piped to other commands or file 50 | if !isTerminal(os.Stdout) { 51 | var names []string 52 | for _, r := range receipts { 53 | names = append(names, displayName(r.Plugin, indexOf(r))) 54 | } 55 | sort.Strings(names) 56 | fmt.Fprintln(os.Stdout, strings.Join(names, "\n")) 57 | return nil 58 | } 59 | 60 | // print table 61 | var rows [][]string 62 | for _, r := range receipts { 63 | rows = append(rows, []string{displayName(r.Plugin, indexOf(r)), r.Spec.Version}) 64 | } 65 | rows = sortByFirstColumn(rows) 66 | return printTable(os.Stdout, []string{"PLUGIN", "VERSION"}, rows) 67 | }, 68 | PreRunE: checkIndex, 69 | } 70 | 71 | rootCmd.AddCommand(listCmd) 72 | } 73 | 74 | func printTable(out io.Writer, columns []string, rows [][]string) error { 75 | w := tabwriter.NewWriter(out, 0, 0, 2, ' ', 0) 76 | fmt.Fprint(w, strings.Join(columns, "\t")) 77 | fmt.Fprintln(w) 78 | for _, values := range rows { 79 | fmt.Fprint(w, strings.Join(values, "\t")) 80 | fmt.Fprintln(w) 81 | } 82 | return w.Flush() 83 | } 84 | 85 | func sortByFirstColumn(rows [][]string) [][]string { 86 | sort.Slice(rows, func(a, b int) bool { 87 | return rows[a][0] < rows[b][0] 88 | }) 89 | return rows 90 | } 91 | -------------------------------------------------------------------------------- /cmd/krew/cmd/namingutils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "regexp" 19 | 20 | "sigs.k8s.io/krew/pkg/constants" 21 | "sigs.k8s.io/krew/pkg/index" 22 | ) 23 | 24 | var canonicalNameRegex = regexp.MustCompile(`^[\w-]+/[\w-]+$`) 25 | 26 | // indexOf returns the index name of a receipt. 27 | func indexOf(r index.Receipt) string { 28 | if r.Status.Source.Name == "" { 29 | return constants.DefaultIndexName 30 | } 31 | return r.Status.Source.Name 32 | } 33 | 34 | // displayName returns the display name of a Plugin. 35 | // The index name is omitted if it is the default index. 36 | func displayName(p index.Plugin, indexName string) string { 37 | if isDefaultIndex(indexName) { 38 | return p.Name 39 | } 40 | return indexName + "/" + p.Name 41 | } 42 | 43 | func isDefaultIndex(name string) bool { 44 | return name == "" || name == constants.DefaultIndexName 45 | } 46 | 47 | // canonicalName returns INDEX/NAME value for a plugin, even if 48 | // it is in the default index. 49 | func canonicalName(p index.Plugin, indexName string) string { 50 | if isDefaultIndex(indexName) { 51 | indexName = constants.DefaultIndexName 52 | } 53 | return indexName + "/" + p.Name 54 | } 55 | 56 | func isCanonicalName(s string) bool { 57 | return canonicalNameRegex.MatchString(s) 58 | } 59 | -------------------------------------------------------------------------------- /cmd/krew/cmd/search_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/google/go-cmp/cmp" 21 | ) 22 | 23 | // Test_searchByNameAndDesc tests fuzzy search 24 | // name matches are shown first, then the description matches 25 | func Test_searchByNameAndDesc(t *testing.T) { 26 | testPlugins := []struct { 27 | keyword string 28 | names []string 29 | descs []string 30 | expected []string 31 | }{ 32 | { 33 | keyword: "foo", 34 | names: []string{"foo", "bar", "foobar"}, // names match first 35 | descs: []string{ 36 | "This is the description for the first plugin, not contain keyword", 37 | "This is the description to the second plugin, not contain keyword", 38 | "This is the description for the third plugin, not contain keyword", 39 | }, 40 | expected: []string{"foo", "foobar"}, 41 | }, 42 | { 43 | keyword: "bar", 44 | names: []string{"baz", "qux", "fred"}, // names not match 45 | descs: []string{ 46 | "This is the description for the first plugin, contain keyword bar", // description match, but score < 0 47 | "This is the description for the second plugin, not contain keyword", 48 | "This is the description for the third plugin, contain ba fuzzy keyword", // fuzzy match, but score < 0 49 | }, 50 | expected: []string{}, 51 | }, 52 | { 53 | keyword: "baz", 54 | names: []string{"baz", "foo", "bar"}, // both name and description match 55 | descs: []string{ 56 | "This is the description for the first plugin, contain keyword baz", // both name and description match 57 | "This is the description for the second plugin, not contain keyword", 58 | "This is the description for the third plugin, contain bar keyword", 59 | }, 60 | expected: []string{"baz"}, 61 | }, 62 | { 63 | keyword: "", 64 | names: []string{"plugin1", "plugin2", "plugin3"}, // empty keyword, only names match 65 | descs: []string{ 66 | "Description for plugin1", 67 | "Description for plugin2", 68 | "Description for plugin3", 69 | }, 70 | expected: []string{"plugin1", "plugin2", "plugin3"}, 71 | }, 72 | } 73 | 74 | for _, tp := range testPlugins { 75 | t.Run(tp.keyword, func(t *testing.T) { 76 | searchTarget := make([]searchItem, len(tp.names)) 77 | for i, name := range tp.names { 78 | searchTarget[i] = searchItem{ 79 | name: name, 80 | description: tp.descs[i], 81 | } 82 | } 83 | result := searchByNameAndDesc(tp.keyword, searchTarget) 84 | if diff := cmp.Diff(tp.expected, result); diff != "" { 85 | t.Fatalf("expected %v does not match got %v", tp.expected, result) 86 | } 87 | }) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /cmd/krew/cmd/uninstall.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | 21 | "github.com/pkg/errors" 22 | "github.com/spf13/cobra" 23 | "k8s.io/klog/v2" 24 | 25 | "sigs.k8s.io/krew/internal/index/validation" 26 | "sigs.k8s.io/krew/internal/installation" 27 | ) 28 | 29 | // uninstallCmd represents the uninstall command 30 | var uninstallCmd = &cobra.Command{ 31 | Use: "uninstall", 32 | Short: "Uninstall plugins", 33 | Long: `Uninstall one or more plugins. 34 | 35 | Example: 36 | kubectl krew uninstall NAME [NAME...] 37 | 38 | Remarks: 39 | Failure to uninstall a plugin will result in an error and exit immediately.`, 40 | RunE: func(_ *cobra.Command, args []string) error { 41 | for _, name := range args { 42 | if isCanonicalName(name) { 43 | return errors.New("uninstall command does not support INDEX/PLUGIN syntax; just specify PLUGIN") 44 | } else if !validation.IsSafePluginName(name) { 45 | return unsafePluginNameErr(name) 46 | } 47 | klog.V(4).Infof("Going to uninstall plugin %s\n", name) 48 | if err := installation.Uninstall(paths, name); err != nil { 49 | return errors.Wrapf(err, "failed to uninstall plugin %s", name) 50 | } 51 | fmt.Fprintf(os.Stderr, "Uninstalled plugin: %s\n", name) 52 | } 53 | return nil 54 | }, 55 | PreRunE: checkIndex, 56 | Args: cobra.MinimumNArgs(1), 57 | Aliases: []string{"remove", "rm"}, 58 | } 59 | 60 | func unsafePluginNameErr(n string) error { return errors.Errorf("plugin name %q not allowed", n) } 61 | 62 | func init() { 63 | rootCmd.AddCommand(uninstallCmd) 64 | } 65 | -------------------------------------------------------------------------------- /cmd/krew/cmd/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "os" 19 | 20 | "github.com/spf13/cobra" 21 | 22 | "sigs.k8s.io/krew/internal/installation" 23 | "sigs.k8s.io/krew/internal/version" 24 | "sigs.k8s.io/krew/pkg/constants" 25 | "sigs.k8s.io/krew/pkg/index" 26 | ) 27 | 28 | // versionCmd represents the version command 29 | var versionCmd = &cobra.Command{ 30 | Use: "version", 31 | Short: "Show krew version and diagnostics", 32 | Long: `Show version information and diagnostics about krew itself. 33 | 34 | Remarks: 35 | - GitTag describes the release name krew is built from. 36 | - GitCommit describes the git revision ID which krew is built from. 37 | - DefaultIndexURI is the URI where the index is updated from. 38 | - BasePath is the root directory for krew installation. 39 | - IndexPath is the directory that stores the local copy of the index git repository. 40 | - InstallPath is the directory for plugin installations. 41 | - BinPath is the directory for the symbolic links to the installed plugin executables.`, 42 | RunE: func(_ *cobra.Command, _ []string) error { 43 | conf := [][]string{ 44 | {"GitTag", version.GitTag()}, 45 | {"GitCommit", version.GitCommit()}, 46 | {"IndexURI", index.DefaultIndex()}, 47 | {"BasePath", paths.BasePath()}, 48 | {"IndexPath", paths.IndexPath(constants.DefaultIndexName)}, 49 | {"InstallPath", paths.InstallPath()}, 50 | {"BinPath", paths.BinPath()}, 51 | {"DetectedPlatform", installation.OSArch().String()}, 52 | } 53 | return printTable(os.Stdout, []string{"OPTION", "VALUE"}, conf) 54 | }, 55 | } 56 | 57 | func init() { 58 | rootCmd.AddCommand(versionCmd) 59 | } 60 | -------------------------------------------------------------------------------- /cmd/krew/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "k8s.io/klog/v2" 19 | 20 | "sigs.k8s.io/krew/cmd/krew/cmd" 21 | ) 22 | 23 | func main() { 24 | defer klog.Flush() 25 | cmd.Execute() 26 | } 27 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Community Code of Conduct 2 | 3 | Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) 4 | -------------------------------------------------------------------------------- /docs/DEVELOPER_GUIDE.md: -------------------------------------------------------------------------------- 1 | # Developer Guide 2 | 3 | This page has moved from GitHub to our website: 4 | https://krew.sigs.k8s.io/docs/developer-guide/ 5 | -------------------------------------------------------------------------------- /docs/KREW_LOGO.md: -------------------------------------------------------------------------------- 1 | # Krew logo 2 | 3 | Krew logo is designed by [@iboonox](https://twitter.com/iboonox) and it is 4 | licensed under Apache 2.0 license. 5 | 6 | Krew logo follows the nautical theme of Kubernetes: The tentacles wrapping 7 | around the crate refer to tentacles of a cuttlefish, which is the mascot of 8 | [kubectl](https://github.com/kubernetes/kubectl#kubectl) project, and the crate 9 | represents a kubectl plugin package. 10 | 11 | ## Available logo types 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | 31 | 35 | 36 | 37 | 38 | 42 | 43 | 44 |
TypePreview
Horizontal 25 | 27 |
Stacked 32 | 34 |
Icon-only 39 | 41 |
45 | 46 | ## Logo assets 47 | 48 | | Type | Color Scheme | Format | 49 | |--|--|--| 50 | | horizontal (text next to the icon) | colored | [[png]](../assets/logo/horizontal/color/krew-horizontal-color.png) [[svg]](../assets/logo/horizontal/color/krew-horizontal-color.svg) | 51 | | horizontal (text next to the icon) | black | [[png]](../assets/logo/horizontal/black/krew-horizontal-black.png) [[svg]](../assets/logo/horizontal/black/krew-horizontal-black.svg) | 52 | | horizontal (text next to the icon) | white | [[png]](../assets/logo/horizontal/white/krew-horizontal-white.png) [[svg]](../assets/logo/horizontal/white/krew-horizontal-white.svg) | 53 | | stacked (text below the icon) | colored | [[png]](../assets/logo/stacked/color/krew-stacked-color.png) [[svg]](../assets/logo/stacked/color/krew-stacked-color.svg) | 54 | | stacked (text below the icon) | black | [[png]](../assets/logo/stacked/black/krew-stacked-black.png) [[svg]](../assets/logo/stacked/black/krew-stacked-black.svg) | 55 | | stacked (text below the icon) | white | [[png]](../assets/logo/stacked/white/krew-stacked-white.png) [[svg]](../assets/logo/stacked/white/krew-stacked-white.svg) | 56 | | icon-only | colored | [[png]](../assets/logo/icon/color/krew-icon-color.png) [[svg]](../assets/logo/icon/color/krew-icon-color.svg) | 57 | | icon-only | black | [[png]](../assets/logo/icon/black/krew-icon-black.png) [[svg]](../assets/logo/icon/black/krew-icon-black.svg) | 58 | | icon-only | white | [[png]](../assets/logo/icon/white/krew-icon-white.png) [[svg]](../assets/logo/icon/white/krew-icon-white.svg) | 59 | -------------------------------------------------------------------------------- /docs/NAMING_GUIDE.md: -------------------------------------------------------------------------------- 1 | # Plugin Naming Style Guide 2 | 3 | This page has moved to our website: 4 | https://krew.sigs.k8s.io/docs/developer-guide/develop/naming-guide/ 5 | -------------------------------------------------------------------------------- /docs/PLUGIN_LIFECYCLE.md: -------------------------------------------------------------------------------- 1 | # Plugin Lifecycle 2 | 3 | (This guide is intended for developers of Krew.) 4 | 5 | :warning: The instructions below may not be up to date for krew v0.2 and higher. 6 | It is provided as a reference to learn how Krew works under the covers, but 7 | may not be entirely accurate. 8 | 9 | ## Installation 10 | 11 | The plugins will be downloaded and checked against its sha256 to verify the 12 | integrity. The package will be uncompressed into a directory called 13 | `${TMP}/krew/download///`. Then the directory will 14 | be renamed (mv) to `~/.krew/store///`. 15 | This ensures a partially atomic and idempotent operation on most file systems. 16 | 17 | ## Upgrade 18 | 19 | ![Upgrading Plugins](src/krew_upgrade.svg) 20 | 21 | Upgrading is more difficult because we already have a plugin that is working. 22 | Krew should not destroy a working environment. The operations should be 23 | idempotent. If something fails we should be able to recover to the wanted state. 24 | 25 | Upgrading works by iterating over the `~/.krew/store/` directory 26 | names and comparing the current hash to the corresponding index file. When a 27 | hash differs the plugin gets installed again. 28 | 29 | Install the plugin: 30 | 31 | 1. Delete the old version 32 | 2. Therefore it‘s not possible for plugins to store 33 | data in the plugin directory. 34 | 35 | In case any plugin operation does not succeed the plugin directory is not 36 | damaged under the assumption that mv is atomic. If another plugin 37 | upgrade/install command is issued and a directory in download still exists or 38 | two plugin version directories exist in `/store//`, krew assumes 39 | that the previous operation failed. 40 | 41 | Krew informs the user and retries to reinstall the package. 42 | 43 | ![Self Upgrade](src/krew_upgrade_self.svg) 44 | 45 | On Windows it is not possible to modify a file/directory which is currently in 46 | use. This requires a special case for upgrading krew, which itself is a plugin, 47 | from krew. If krew upgrades itself, it is installing itself, instead of deleting 48 | the whole old directory it just deletes the old `/commands/` directory witch 49 | holds all the plugin descriptor files. 50 | 51 | This way only the new krew version is executed. If krew upgrade is executed 52 | again the old version directory gets deleted. 53 | -------------------------------------------------------------------------------- /docs/RELEASING_KREW.md: -------------------------------------------------------------------------------- 1 | # Releasing Krew 2 | 3 | (This document is intended for maintainers of Krew only.) 4 | 5 | ### Build/Test the release locally 6 | 7 | 1. Build krew release assets locally: 8 | 9 | hack/make-all.sh 10 | 11 | 2. Try krew installation on each platform: 12 | 13 | ```sh 14 | krew=out/bin/krew-darwin_amd64 # assuming macOS amd64 15 | 16 | for osarch in darwin_amd64 darwin_arm64 linux_amd64 linux_arm linux_arm64 linux_ppc64le windows_amd64; do 17 | KREW_ROOT="$(mktemp -d --tmpdir krew-XXXXXXXXXX)" KREW_OS="${osarch%_*}" KREW_ARCH="${osarch#*_}" \ 18 | $krew install --manifest=out/krew.yaml --archive="out/krew-${osarch}.tar.gz" 19 | done 20 | ``` 21 | 22 | ### Release a new version 23 | 24 | Krew follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html). 25 | Krew tags versions starting with `v`. Example: `v0.2.0-rc.1`. 26 | 27 | 1. **Decide on a version number:** set it to `$TAG` variable: 28 | 29 | ```sh 30 | TAG=v0.3.2-rc.1 # <- change this 31 | ``` 32 | 33 | 1. **Create a release commit:** 34 | 35 | ```sh 36 | git commit -am "Release ${TAG:?TAG required}" --allow-empty 37 | git push origin master 38 | ``` 39 | 40 | (Only repository administrators can directly push to master branch.) 41 | 42 | 1. **Wait until the build succeeds:** Wait for CI to show green for the 43 | build of the commit you just pushed to master branch. 44 | 45 | 1. **Tag the release:** 46 | 47 | git tag "${TAG:?TAG required}" 48 | 49 | 1. **Push the tag:** 50 | 51 | git push origin "${TAG:?TAG required}" 52 | 53 | 1. **Verify on Releases tab on GitHub:** Make sure `krew.yaml`, `krew.tar.gz` 54 | and other release assets show up on "Releases" tab. 55 | 56 | 1. **Make the new version available on krew index:** Get the latest `krew.yaml` from 57 | 58 | curl -LO https://github.com/kubernetes-sigs/krew/releases/download/"${TAG:?TAG required}"/krew.yaml 59 | 60 | and make a pull request to 61 | [krew-index](https://github.com/kubernetes-sigs/krew-index/) repository. 62 | This will make the plugin available to upgrade for users using older versions 63 | of krew. 64 | 65 | 1. **Update krew-index CI**: The CI tests for `krew-index` repository relies on 66 | tools from main `krew` repository, and they should use the latest version. 67 | When there's a new version, update `.github/workflows/ci.yml` in `krew-index` repo. 68 | 69 | ## Release artifacts 70 | 71 | When a tag is pushed to the repository, GitHub workflow will make a release 72 | on GitHub, and upload the release artifacts as files on the release. 73 | -------------------------------------------------------------------------------- /docs/USER_GUIDE.md: -------------------------------------------------------------------------------- 1 | # User Guide 2 | 3 | This page has moved from GitHub to our website: 4 | https://krew.sigs.k8s.io/docs/user-guide/ 5 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module sigs.k8s.io/krew 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/fatih/color v1.15.0 7 | github.com/google/go-cmp v0.6.0 8 | github.com/mattn/go-isatty v0.0.19 9 | github.com/pkg/errors v0.9.1 10 | github.com/sahilm/fuzzy v0.1.0 11 | github.com/spf13/cobra v1.8.0 12 | github.com/spf13/pflag v1.0.5 13 | k8s.io/apimachinery v0.29.3 14 | k8s.io/client-go v0.29.3 15 | k8s.io/klog/v2 v2.120.1 16 | sigs.k8s.io/yaml v1.4.0 17 | ) 18 | 19 | require ( 20 | github.com/go-logr/logr v1.4.1 // indirect 21 | github.com/gogo/protobuf v1.3.2 // indirect 22 | github.com/google/gofuzz v1.2.0 // indirect 23 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 24 | github.com/json-iterator/go v1.1.12 // indirect 25 | github.com/kylelemons/godebug v1.1.0 // indirect 26 | github.com/mattn/go-colorable v0.1.13 // indirect 27 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 28 | github.com/modern-go/reflect2 v1.0.2 // indirect 29 | golang.org/x/net v0.19.0 // indirect 30 | golang.org/x/sys v0.15.0 // indirect 31 | golang.org/x/text v0.14.0 // indirect 32 | gopkg.in/inf.v0 v0.9.1 // indirect 33 | gopkg.in/yaml.v2 v2.4.0 // indirect 34 | k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect 35 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 36 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.Dockerfile.txt: -------------------------------------------------------------------------------- 1 | # Copyright YEAR The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.Makefile.txt: -------------------------------------------------------------------------------- 1 | # Copyright YEAR The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | // Copyright YEAR The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.py.txt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright YEAR The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.sh.txt: -------------------------------------------------------------------------------- 1 | # Copyright YEAR The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /hack/ensure-kubectl-installed.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -euo pipefail 18 | 19 | install_kubectl_if_needed() { 20 | if hash kubectl 2>/dev/null; then 21 | echo >&2 "using kubectl from the host system and not reinstalling" 22 | else 23 | local bin_dir 24 | bin_dir="$(go env GOPATH)/bin" 25 | local -r kubectl_version='v1.14.2' 26 | local -r kubectl_path="${bin_dir}/kubectl" 27 | local goos goarch kubectl_url 28 | goos="$(go env GOOS)" 29 | goarch="$(go env GOARCH)" 30 | kubectl_url="https://dl.k8s.io/release/${kubectl_version}/bin/${goos}/${goarch}/kubectl" 31 | 32 | echo >&2 "kubectl not detected in environment, downloading ${kubectl_url}" 33 | mkdir -p "${bin_dir}" 34 | curl --fail --show-error --silent --location --output "$kubectl_path" "${kubectl_url}" 35 | chmod +x "$kubectl_path" 36 | echo >&2 "installed kubectl to ${kubectl_path}" 37 | fi 38 | } 39 | 40 | install_kubectl_if_needed 41 | -------------------------------------------------------------------------------- /hack/install-gox.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -euo pipefail 18 | [[ -n "${DEBUG:-}" ]] && set -x 19 | 20 | ensure_gox() { 21 | command -v "gox" &>/dev/null 22 | } 23 | 24 | go install github.com/mitchellh/gox@v1.0.1 25 | 26 | if ! ensure_gox; then 27 | echo >&2 "gox not in PATH" 28 | exit 1 29 | fi 30 | -------------------------------------------------------------------------------- /hack/make-all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -e -o pipefail 18 | 19 | SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 20 | 21 | "${SCRIPTDIR}/make-binaries.sh" 22 | "${SCRIPTDIR}/make-release-artifacts.sh" 23 | -------------------------------------------------------------------------------- /hack/make-binaries.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # This script builds krew binaries for all supported platforms (or those os/arch 18 | # combinations specified via OSARCH variable). 19 | 20 | set -e -o pipefail 21 | SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 22 | 23 | if ! command -v "gox" &>/dev/null; then 24 | echo >&2 "gox not installed in PATH, run hack/install-gox.sh." 25 | exit 1 26 | fi 27 | 28 | supported_platforms="darwin/amd64 darwin/arm64 windows/amd64\ 29 | linux/amd64 linux/arm linux/arm64 linux/ppc64le" 30 | version_pkg="sigs.k8s.io/krew/internal/version" 31 | 32 | cd "${SCRIPTDIR}/.." 33 | rm -rf -- "out/" 34 | 35 | # Builds 36 | echo >&2 "Building binaries for: ${OSARCH:-$supported_platforms}" 37 | git_rev="${SHORT_SHA:-$(git rev-parse --short HEAD)}" 38 | git_tag="${TAG_NAME:-$(git describe --tags --dirty --always)}" 39 | echo >&2 "(Stamping with git tag=${git_tag} rev=${git_rev})" 40 | 41 | env CGO_ENABLED=0 gox -osarch="${OSARCH:-$supported_platforms}" \ 42 | -tags netgo \ 43 | -mod readonly \ 44 | -ldflags="-w -X ${version_pkg}.gitCommit=${git_rev} \ 45 | -X ${version_pkg}.gitTag=${git_tag}" \ 46 | -output="out/bin/krew-{{.OS}}_{{.Arch}}" \ 47 | ./cmd/krew/... 48 | -------------------------------------------------------------------------------- /hack/make-binary.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # This script builds krew binary for the current OS/arch. 18 | 19 | set -e -o pipefail 20 | SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 21 | 22 | exec env OSARCH="$(go env GOOS)/$(go env GOARCH)" \ 23 | "${SCRIPTDIR}/make-binaries.sh" 24 | -------------------------------------------------------------------------------- /hack/make-release-artifacts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -e -o pipefail 18 | 19 | SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 20 | cd "${SCRIPTDIR}/.." 21 | 22 | bin_dir="out/bin" 23 | if [[ ! -d "${bin_dir}" ]]; then 24 | echo >&2 "Binaries are not built (${bin_dir}), run hack/make-binaries.sh" 25 | exit 1 26 | fi 27 | 28 | checksum_cmd="shasum -a 256" 29 | if hash sha256sum 2>/dev/null; then 30 | checksum_cmd="sha256sum" 31 | fi 32 | checksum_sed="" 33 | 34 | while IFS= read -r -d $'\0' f; do 35 | archive_dir="$(mktemp -d)" 36 | cp "$f" "${archive_dir}" 37 | cp -- "${SCRIPTDIR}/../LICENSE" "${archive_dir}" 38 | name="$(basename "$f" .exe)" 39 | archive="${name}.tar.gz" 40 | echo >&2 "Creating ${archive} archive." 41 | ( 42 | cd "${archive_dir}" 43 | # consistent timestamps for files in archive dir to ensure consistent checksums 44 | TZ=UTC touch -t "0001010000" ./* 45 | tar --use-compress-program "gzip --no-name" -cvf "${SCRIPTDIR}/../out/${archive}" ./* 46 | ) 47 | 48 | # create sumfile 49 | sumfile="out/${archive}.sha256" 50 | checksum="$(eval "${checksum_cmd[@]}" "out/${archive}" | awk '{print $1;}')" 51 | echo >&2 "${archive} checksum: ${checksum}" 52 | echo "${checksum}" >"${sumfile}" 53 | echo >&2 "Written ${sumfile}." 54 | 55 | # prepare krew manifest sed 56 | checksum_sed="${checksum_sed};s/$(tr "[[:lower:]-]" "[[:upper:]_]" <<<${name})_CHECKSUM/${checksum}/" 57 | 58 | done < <(find "${bin_dir}" -type f -print0) 59 | 60 | # create a out/krew.exe convenience copy 61 | if [[ -x "./${bin_dir}/krew-windows_amd64.exe" ]]; then 62 | krew_exe="krew.exe" 63 | cp -- "./${bin_dir}/krew-windows_amd64.exe" "./out/${krew_exe}" 64 | exe_sumfile="out/krew.exe.sha256" 65 | exe_checksum="$(eval "${checksum_cmd[@]}" "out/${krew_exe}" | awk '{print $1;}')" 66 | echo >&2 "${krew_exe} checksum: ${exe_checksum}" 67 | echo "${exe_checksum}" >"${exe_sumfile}" 68 | echo >&2 "Written ${exe_sumfile}." 69 | fi 70 | 71 | # Copy and process krew manifest 72 | git_describe="$(git describe --tags --dirty --always)" 73 | if [[ ! "${git_describe}" =~ v.* ]]; then 74 | # if tag cannot be inferred (e.g. CI/CD), still provide a valid 75 | # version field for krew.yaml 76 | git_describe="v0.0.0-detached+${git_describe}" 77 | fi 78 | krew_version="${TAG_NAME:-$git_describe}" 79 | sed "${checksum_sed};s/KREW_TAG/${krew_version}/g" ./hack/krew.yaml >./out/krew.yaml 80 | echo >&2 "Written out/krew.yaml." 81 | -------------------------------------------------------------------------------- /hack/make-release-notes.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # This script should be executed while tagging a commit. 18 | # You can run this script while tagging the release as: 19 | # git tag -a v0.1 -m "$(TAG=v0.1 hack/make-release-notes.sh)" 20 | 21 | set -euo pipefail 22 | 23 | gopath="$(go env GOPATH)" 24 | 25 | TAG="${TAG:?TAG environment variable must be set for this script}" 26 | if ! [[ "$TAG" =~ v.* ]]; then 27 | echo >&2 "TAG must be in format v.*" 28 | exit 1 29 | fi 30 | 31 | SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 32 | cd "${SCRIPTDIR}/.." 33 | 34 | archive_dir="out" 35 | if [[ ! -d "${archive_dir}" ]]; then 36 | echo >&2 "Archive dir is not created (${archive_dir}), run hack/make-all.sh" 37 | exit 1 38 | fi 39 | 40 | cd "${archive_dir}" 41 | 42 | download_assets=() 43 | for entry in *; do 44 | if [[ -f "${entry}" ]]; then 45 | download_assets[${#download_assets[@]}]="${entry}" 46 | fi 47 | done 48 | if [[ ${#download_assets[@]} == 0 ]]; then 49 | echo >&2 "Archives are not created, run hack/make-release-artifacts.sh" 50 | exit 1 51 | fi 52 | 53 | readme="https://github.com/kubernetes-sigs/krew/blob/${TAG}/README.md" 54 | download_base="https://github.com/kubernetes-sigs/krew/releases/download" 55 | 56 | # install release-notes tool if not present 57 | if [[ ! -f "${gopath}/bin/release-notes" ]]; then 58 | echo >&2 'Installing release-notes tool...' 59 | go install github.com/corneliusweig/release-notes@v0.1.0 60 | fi 61 | 62 | echo "Installation" 63 | echo "------------" 64 | echo "To install this release, refer to the instructions at ${readme}." 65 | echo 66 | echo "Release Assets" 67 | echo "--------------" 68 | echo "Artifacts for this release can be downloaded from the following links." 69 | echo "It is recommended to follow [installation instructions](${readme})" 70 | echo "and not using these artifacts directly." 71 | echo 72 | for f in "${download_assets[@]}"; do 73 | echo "- $download_base/${TAG}/${f}" 74 | done 75 | echo 76 | echo "Thanks to our contributors for helping out with ${TAG}:" 77 | previous_version="$(git describe --tags --match 'v*' --abbrev=0 "${TAG}^")" 78 | git log "${previous_version}..${TAG}" --format=%an | 79 | sort | uniq -c | sort -rn | 80 | sed -E 's,^(\s+[0-9]+\s),- ,g' 81 | echo 82 | echo "(krew ${TAG} was tagged on $(date -u).)" 83 | echo 84 | echo '
' 85 | echo 'Merged pull requests' 86 | echo # this empty line is important for correct markdown rendering 87 | # you can pass your github token with --token here if you run out of requests 88 | 89 | "${gopath}/bin/release-notes" kubernetes-sigs krew --since "${previous_version}" 90 | echo '
' 91 | echo 92 | -------------------------------------------------------------------------------- /hack/run-in-docker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # This script starts a development container by mounting the krew binary from 18 | # the local filesystem. 19 | 20 | set -euo pipefail 21 | SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 22 | log() { echo >&2 "$*"; } 23 | log_ok() { log "$(tput setaf 2)$*$(tput sgr0)"; } 24 | log_fail() { log "$(tput setaf 1)$*$(tput sgr0)"; } 25 | image="krew:sandbox" 26 | 27 | krew_bin="${SCRIPTDIR}/../out/bin/krew-linux_amd64" 28 | if [[ ! -f "${krew_bin}" ]]; then 29 | log "Building the ${krew_bin}." 30 | env OSARCH="linux/amd64" "${SCRIPTDIR}/make-binaries.sh" 31 | else 32 | log_ok "Using existing ${krew_bin}." 33 | fi 34 | 35 | docker build -f "${SCRIPTDIR}/sandboxed.Dockerfile" -q \ 36 | --tag "${image}" "${SCRIPTDIR}/.." 37 | log_ok "Sandbox image '${image}' built successfully." 38 | 39 | kubeconfig="${KUBECONFIG:-$HOME/.kube/config}" 40 | if [[ ! -f "${kubeconfig}" ]]; then 41 | log_fail "Warning: kubeconfig not found at ${kubeconfig}, using /dev/null" 42 | kubeconfig=/dev/null 43 | fi 44 | 45 | log_ok "Starting docker container with volume mounts:" 46 | log " kubeconfig=${kubeconfig}" 47 | log " kubectl-krew=${krew_bin}" 48 | log_ok "You can rebuild with the following command without restarting the container:" 49 | log " env OSARCH=linux/amd64 hack/make-binaries.sh" 50 | exec docker run --rm --tty --interactive \ 51 | --volume "${krew_bin}:/usr/local/bin/kubectl-krew" \ 52 | --volume "${kubeconfig}:/etc/kubeconfig" \ 53 | --env KUBECONFIG=/etc/kubeconfig \ 54 | --hostname krew \ 55 | "${image}" 56 | -------------------------------------------------------------------------------- /hack/run-integration-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -euo pipefail 18 | 19 | [[ -n "${DEBUG:-}" ]] && set -x 20 | 21 | SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 22 | BINDIR="${SCRIPTDIR}/../out/bin" 23 | goos="$(go env GOOS)" 24 | goarch="$(go env GOARCH)" 25 | krew_binary_default="${BINDIR}/krew-${goos}_${goarch}" 26 | 27 | if [[ "$#" -gt 0 && ("$1" == '-h' || "$1" == '--help') ]]; then 28 | cat <&2 "Could not find $KREW_BINARY. You need to build krew for ${goos}/${goarch} before running the integration tests." 45 | exit 1 46 | fi 47 | krew_binary_realpath="$(readlink -f "${KREW_BINARY}")" 48 | if [[ ! -x "${krew_binary_realpath}" ]]; then 49 | echo >&2 "krew binary at ${krew_binary_realpath} is not an executable" 50 | exit 1 51 | fi 52 | KREW_BINARY="${krew_binary_realpath}" 53 | export KREW_BINARY 54 | 55 | go test sigs.k8s.io/krew/integration_test "$@" 56 | -------------------------------------------------------------------------------- /hack/run-lint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -euo pipefail 18 | 19 | [[ -n "${DEBUG:-}" ]] && set -x 20 | 21 | gopath="$(go env GOPATH)" 22 | 23 | if ! [[ -x "$gopath/bin/golangci-lint" ]]; then 24 | echo >&2 'Installing golangci-lint' 25 | curl --silent --fail --location \ 26 | https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "$gopath/bin" v2.1.6 27 | fi 28 | 29 | # configured by .golangci.yml 30 | "$gopath/bin/golangci-lint" run 31 | 32 | # install shfmt that ensures consistent format in shell scripts 33 | if ! [[ -x "${gopath}/bin/shfmt" ]]; then 34 | echo >&2 'Installing shfmt' 35 | go install mvdan.cc/sh/v3/cmd/shfmt@v3.0.0 36 | fi 37 | 38 | SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 39 | shfmt_out="$($gopath/bin/shfmt -l -i=2 ${SCRIPTDIR})" 40 | if [[ -n "${shfmt_out}" ]]; then 41 | echo >&2 "The following shell scripts need to be formatted, run: 'shfmt -w -i=2 ${SCRIPTDIR}'" 42 | echo >&2 "${shfmt_out}" 43 | exit 1 44 | fi 45 | -------------------------------------------------------------------------------- /hack/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -euo pipefail 18 | 19 | SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 20 | 21 | color_red="$(tput setaf 1)" 22 | color_green="$(tput setaf 2)" 23 | color_blue="$(tput setaf 4)" 24 | color_reset="$(tput sgr0)" 25 | 26 | print_with_color() { 27 | echo "${1}${*:2}${color_reset}" 28 | } 29 | 30 | print_status() { 31 | local result=$? # <- this must be the first action 32 | if [[ $result == 0 ]]; then 33 | print_with_color "$color_green" 'SUCCESS' 34 | else 35 | print_with_color "$color_red" 'FAILURE' 36 | fi 37 | } 38 | trap print_status EXIT 39 | 40 | print_with_color "$color_blue" 'Checking boilerplate' 41 | "$SCRIPTDIR"/verify-boilerplate.sh 42 | 43 | print_with_color "$color_blue" 'Running tests' 44 | go test -short -race sigs.k8s.io/krew/... 45 | 46 | print_with_color "$color_blue" 'Running linter' 47 | "$SCRIPTDIR"/run-lint.sh 48 | 49 | print_with_color "$color_blue" 'Check code patterns' 50 | "$SCRIPTDIR"/verify-code-patterns.sh 51 | -------------------------------------------------------------------------------- /hack/sandboxed.Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | FROM gcr.io/gcp-runtimes/ubuntu_16_0_4:latest 16 | RUN apt-get update -qqy # retain the apt cache 17 | RUN apt-get install -qqy git curl wget 18 | 19 | ARG KUBECTL_VERSION=v1.14.2 20 | RUN curl -fsSLo /usr/bin/kubectl https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl && \ 21 | chmod +x /usr/bin/kubectl 22 | 23 | # initialize index ahead of time 24 | RUN mkdir -p $HOME/.krew/index && \ 25 | git clone https://github.com/kubernetes-sigs/krew-index $HOME/.krew/index 26 | 27 | ENTRYPOINT [ "/usr/bin/env", "bash" ] 28 | -------------------------------------------------------------------------------- /hack/verify-boilerplate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2014 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Notice: this script was imported from k8s.io/kubernetes/hack/verify-boilerplate.sh 18 | 19 | set -o errexit 20 | set -o nounset 21 | set -o pipefail 22 | 23 | KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. 24 | 25 | boilerDir="${KUBE_ROOT}/hack/boilerplate" 26 | boiler="${boilerDir}/boilerplate.py" 27 | 28 | files_need_boilerplate=($(${boiler} "$@")) 29 | 30 | # Run boilerplate check 31 | if [[ ${#files_need_boilerplate[@]} -gt 0 ]]; then 32 | for file in "${files_need_boilerplate[@]}"; do 33 | echo "Boilerplate header is wrong for: ${file}" >&2 34 | done 35 | 36 | exit 1 37 | fi 38 | -------------------------------------------------------------------------------- /hack/verify-code-patterns.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -euo pipefail 18 | 19 | # Disallow usage of os.MkdirTemp in tests in favor of testutil. 20 | out="$(grep --include '*_test.go' --exclude-dir 'vendor/' -EIrn 'os\.MkdirTemp' || true)" 21 | if [[ -n "$out" ]]; then 22 | echo >&2 "You used os.MkdirTemp in tests, use 'testutil.NewTempDir()' instead:" 23 | echo >&2 "$out" 24 | exit 1 25 | fi 26 | 27 | # use code constant for ".yaml" 28 | out="$(grep --include '*.go' \ 29 | --exclude "*_test.go" \ 30 | --exclude 'constants.go' \ 31 | --exclude-dir 'vendor/' \ 32 | -EIrn '\.yaml"' || true)" 33 | if [[ -n "$out" ]]; then 34 | echo >&2 'You used ".yaml" in production, use constants.ManifestExtension instead:' 35 | echo >&2 "$out" 36 | exit 1 37 | fi 38 | 39 | # Do not use glog/klog in test code 40 | out="$(grep --include '*_test.go' --exclude-dir 'vendor/' -EIrn '[kg]log\.' || true)" 41 | if [[ -n "$out" ]]; then 42 | echo >&2 "You used glog or klog in tests, use 't.Logf' instead:" 43 | echo >&2 "$out" 44 | exit 1 45 | fi 46 | 47 | # Do not use fmt.Errorf as it does not start a stacktrace at error site 48 | out="$(grep --include '*.go' -EIrn 'fmt\.Errorf?' || true)" 49 | if [[ -n "$out" ]]; then 50 | echo >&2 "You used fmt.Errorf; use pkg/errors.Errorf instead to preserve stack traces:" 51 | echo >&2 "$out" 52 | exit 1 53 | fi 54 | 55 | # Do not initialize index.{Plugin,Platform} structs in test code. 56 | out="$(grep --include '*_test.go' --exclude-dir 'vendor/' -EIrn '[^]](index\.)(Plugin|Platform){' || true)" 57 | if [[ -n "$out" ]]; then 58 | echo >&2 "Do not use index.Platform or index.Plugin structs directly in tests," 59 | echo >&2 "use testutil.NewPlugin() or testutil.NewPlatform() instead:" 60 | echo >&2 "-----" 61 | echo >&2 "$out" 62 | exit 1 63 | fi 64 | -------------------------------------------------------------------------------- /hack/verify-index-migration.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2020 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # This script tests the automatic index migration which was added for 18 | # migrating krew 0.3.x to krew 0.4.x. 19 | # 20 | # TODO(ahmetb,corneliusweig,chriskim06) remove at/after krew 0.5.x when 21 | # index-migration is no longer supported. 22 | 23 | set -euo pipefail 24 | 25 | [[ -n "${DEBUG:-}" ]] && set -x 26 | 27 | SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 28 | BINDIR="${SCRIPTDIR}/../out/bin" 29 | goos="$(go env GOOS)" 30 | goarch="$(go env GOARCH)" 31 | 32 | install_krew_0_3_4() { 33 | krew_root="${1}" 34 | temp_dir="$(mktemp -d)" 35 | trap 'rm -rf "${temp_dir}"' RETURN 36 | ( 37 | set -x 38 | cd "${temp_dir}" && 39 | curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/download/v0.3.4/krew.{tar.gz,yaml}" && 40 | tar zxvf krew.tar.gz && 41 | KREW=./krew-"$(uname | tr '[:upper:]' '[:lower:]')_amd64" && 42 | env KREW_ROOT="${krew_root}" "$KREW" install --manifest=krew.yaml --archive=krew.tar.gz && 43 | env KREW_ROOT="${krew_root}" "$KREW" update 44 | ) 45 | } 46 | 47 | install_plugin() { 48 | plugin="${2}" 49 | 50 | run_krew "${1}" install "${plugin}" 1>/dev/null 51 | } 52 | 53 | # patch_krew_bin replaces the installed krew binary with the new version 54 | patch_krew_bin() { 55 | krew_root="${1}" 56 | new_binary="${2}" 57 | 58 | local old_binary 59 | old_binary="$(readlink -f "${krew_root}/bin/kubectl-krew")" 60 | cp -f "${new_binary}" "${old_binary}" 61 | } 62 | 63 | # run_krew runs 'krew' with the specified KREW_ROOT and arguments. 64 | run_krew() { 65 | krew_root="${1}" 66 | shift 67 | 68 | env KREW_ROOT="${krew_root}" \ 69 | PATH="${krew_root}/bin:$PATH" \ 70 | kubectl krew "$@" 71 | } 72 | 73 | verify_index_migrated() { 74 | krew_root="${1}" 75 | [[ -d "${krew_root}/index/default" ]] 76 | } 77 | 78 | main() { 79 | new_krew="${BINDIR}/krew-${goos}_${goarch}" 80 | if [[ ! -e "${new_krew}" ]]; then 81 | echo >&2 "Could not find ${new_krew}." 82 | exit 1 83 | fi 84 | 85 | krew_root="$(mktemp -d)" 86 | trap 'rm -rf "${krew_root}"' RETURN 87 | 88 | echo >&2 "Test directory: ${krew_root}" 89 | install_krew_0_3_4 "${krew_root}" 90 | install_plugin "${krew_root}" "get-all" 91 | echo >&2 "Swapping krew binary" 92 | patch_krew_bin "${krew_root}" "${new_krew}" 93 | run_krew "${krew_root}" list || ( 94 | echo >&2 "krew list is failing" 95 | exit 1 96 | ) 97 | verify_index_migrated "${krew_root}" || ( 98 | echo >&2 "index was not migrated" 99 | exit 1 100 | ) 101 | } 102 | 103 | main 104 | -------------------------------------------------------------------------------- /hack/verify-installation.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # This script verifies that a krew build can be installed to a system using 18 | # itself as the documented installation method. 19 | 20 | set -euo pipefail 21 | 22 | [[ -n "${DEBUG:-}" ]] && set -x 23 | 24 | SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 25 | build_dir="${SCRIPTDIR}/../out" 26 | goos="$(go env GOOS)" 27 | goarch="$(go env GOARCH)" 28 | 29 | krew_manifest="${build_dir}/krew.yaml" 30 | if [[ ! -f "${krew_manifest}" ]]; then 31 | echo >&2 "Could not find manifest ${krew_manifest}." 32 | echo >&2 "Did you run hack/make-all.sh?" 33 | exit 1 34 | fi 35 | 36 | krew_archive="${build_dir}/krew-${goos}_${goarch}.tar.gz" 37 | if [[ ! -f "${krew_archive}" ]]; then 38 | echo >&2 "Could not find archive ${krew_archive}." 39 | echo >&2 "Did you run hack/make-all.sh?" 40 | exit 1 41 | fi 42 | 43 | temp_dir="$(mktemp -d)" 44 | trap 'rm -rf -- "${temp_dir}"' EXIT 45 | echo >&2 "Extracting krew from tarball." 46 | tar zxf "${krew_archive}" -C "${temp_dir}" 47 | krew_binary="${temp_dir}/krew-${goos}_${goarch}" 48 | 49 | krew_root="$(mktemp -d)" 50 | trap 'rm -rf -- "${krew_root}"' EXIT 51 | system_path="/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin" 52 | 53 | echo >&2 "Installing the krew build to a temporary directory." 54 | env -i KREW_ROOT="${krew_root}" \ 55 | "${krew_binary}" install \ 56 | --manifest="${krew_manifest}" \ 57 | --archive "${krew_archive}" 58 | 59 | echo >&2 "Verifying krew installation (symlink)." 60 | env -i PATH="${krew_root}/bin:${system_path}" /bin/bash -c \ 61 | "which kubectl-krew 1>/dev/null" 62 | -------------------------------------------------------------------------------- /integration_test/commandline_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package integrationtest 16 | 17 | import "testing" 18 | 19 | func TestUnknownCommand(t *testing.T) { 20 | skipShort(t) 21 | 22 | test := NewTest(t) 23 | 24 | if _, err := test.Krew("foobar").Run(); err == nil { 25 | t.Errorf("Expected `krew foobar` to fail") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /integration_test/help_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package integrationtest 16 | 17 | import ( 18 | "regexp" 19 | "testing" 20 | ) 21 | 22 | func TestKrewHelp(t *testing.T) { 23 | skipShort(t) 24 | 25 | test := NewTest(t) 26 | 27 | test.Krew().RunOrFail() // no args 28 | test.Krew("help").RunOrFail() 29 | test.Krew("-h").RunOrFail() 30 | test.Krew("--help").RunOrFail() 31 | } 32 | 33 | func TestRootHelpShowsKubectlPrefix(t *testing.T) { 34 | skipShort(t) 35 | test := NewTest(t) 36 | 37 | out := string(test.Krew("help").RunOrFailOutput()) 38 | 39 | expect := []*regexp.Regexp{ 40 | regexp.MustCompile(`(?m)Usage:\s+kubectl krew`), 41 | regexp.MustCompile(`(?m)Use "kubectl krew`), 42 | } 43 | 44 | for _, e := range expect { 45 | if !e.MatchString(out) { 46 | t.Errorf("output does not have matching string to pattern %s ; output=%s", e, out) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /integration_test/info_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package integrationtest 16 | 17 | import ( 18 | "strings" 19 | "testing" 20 | ) 21 | 22 | func TestKrewInfo(t *testing.T) { 23 | skipShort(t) 24 | 25 | test := NewTest(t) 26 | 27 | out := string(test.WithDefaultIndex().Krew("info", validPlugin).RunOrFailOutput()) 28 | expected := `INDEX: default` 29 | if !strings.Contains(out, expected) { 30 | t.Fatalf("info output doesn't have %q. output=%q", expected, out) 31 | } 32 | } 33 | 34 | func TestKrewInfoInvalidPlugin(t *testing.T) { 35 | skipShort(t) 36 | 37 | test := NewTest(t) 38 | 39 | plugin := "invalid-plugin" 40 | _, err := test.WithDefaultIndex().Krew("info", plugin).Run() 41 | if err == nil { 42 | t.Errorf("Expected `krew info %s` to fail", plugin) 43 | } 44 | } 45 | 46 | func TestKrewInfoCustomIndex(t *testing.T) { 47 | skipShort(t) 48 | 49 | test := NewTest(t) 50 | 51 | test = test.WithDefaultIndex().WithCustomIndexFromDefault("foo") 52 | test.Krew("install", "foo/"+validPlugin).RunOrFail() 53 | 54 | out := string(test.Krew("info", "foo/"+validPlugin).RunOrFailOutput()) 55 | expected := `INDEX: foo` 56 | if !strings.Contains(out, expected) { 57 | t.Fatalf("info output doesn't have %q. output=%q", expected, out) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /integration_test/list_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package integrationtest 16 | 17 | import ( 18 | "sort" 19 | "strings" 20 | "testing" 21 | 22 | "github.com/google/go-cmp/cmp" 23 | 24 | "sigs.k8s.io/krew/internal/environment" 25 | "sigs.k8s.io/krew/internal/index/indexscanner" 26 | "sigs.k8s.io/krew/internal/installation/receipt" 27 | "sigs.k8s.io/krew/internal/testutil" 28 | "sigs.k8s.io/krew/pkg/constants" 29 | "sigs.k8s.io/krew/pkg/index" 30 | ) 31 | 32 | func TestKrewList(t *testing.T) { 33 | skipShort(t) 34 | 35 | test := NewTest(t) 36 | 37 | test = test.WithDefaultIndex().WithCustomIndexFromDefault("foo") 38 | initialList := test.Krew("list").RunOrFailOutput() 39 | initialOut := []byte{'\n'} 40 | 41 | if diff := cmp.Diff(initialList, initialOut); diff != "" { 42 | t.Fatalf("expected empty output from 'list':\n%s", diff) 43 | } 44 | 45 | test.Krew("install", validPlugin).RunOrFail() 46 | expected := []byte(validPlugin + "\n") 47 | 48 | eventualList := test.Krew("list").RunOrFailOutput() 49 | if diff := cmp.Diff(eventualList, expected); diff != "" { 50 | t.Fatalf("'list' output doesn't match:\n%s", diff) 51 | } 52 | 53 | test.Krew("install", "foo/"+validPlugin2).RunOrFail() 54 | 55 | want := []string{validPlugin, "foo/" + validPlugin2} 56 | actual := lines(test.Krew("list").RunOrFailOutput()) 57 | if diff := cmp.Diff(actual, want); diff != "" { 58 | t.Fatalf("'list' output doesn't match:\n%s", diff) 59 | } 60 | } 61 | 62 | func TestKrewListSorted(t *testing.T) { 63 | skipShort(t) 64 | test := NewTest(t) 65 | 66 | test = test.WithDefaultIndex() 67 | 68 | paths := environment.NewPaths(test.Root()) 69 | ps, err := indexscanner.LoadPluginListFromFS(paths.IndexPluginsPath(constants.DefaultIndexName)) 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | 74 | indexes := []string{"", "default", "bar"} 75 | for i, p := range ps { 76 | src := indexes[i%len(indexes)] 77 | r := testutil.NewReceipt().WithPlugin(p).WithStatus(index.ReceiptStatus{Source: index.SourceIndex{Name: src}}).V() 78 | if err := receipt.Store(r, paths.PluginInstallReceiptPath(p.Name)); err != nil { 79 | t.Fatal(err) 80 | } 81 | } 82 | out := lines(test.Krew("list").RunOrFailOutput()) 83 | if !sort.StringsAreSorted(out) { 84 | t.Fatalf("list output is not sorted: [%s]", strings.Join(out, ", ")) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /integration_test/migration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // todo(corneliusweig) remove this test file with v0.4 16 | package integrationtest 17 | 18 | import ( 19 | "os" 20 | "strings" 21 | "testing" 22 | 23 | "sigs.k8s.io/krew/internal/environment" 24 | "sigs.k8s.io/krew/pkg/constants" 25 | ) 26 | 27 | func TestKrewIndexAutoMigration(t *testing.T) { 28 | skipShort(t) 29 | 30 | test := NewTest(t) 31 | 32 | test.WithDefaultIndex() 33 | prepareOldIndexLayout(test) 34 | 35 | // any command here should cause the index migration to occur 36 | test.Krew("index", "list").RunOrFail() 37 | if !isIndexMigrated(test) { 38 | t.Error("index should have been auto-migrated") 39 | } 40 | } 41 | 42 | func TestKrewUnsupportedVersion(t *testing.T) { 43 | skipShort(t) 44 | 45 | test := NewTest(t) 46 | 47 | test.WithDefaultIndex().Krew("install", validPlugin).RunOrFail() 48 | 49 | // needs to be after initial installation 50 | prepareOldKrewRoot(test) 51 | 52 | // any command should fail here 53 | out, err := test.Krew("list").Run() 54 | if err == nil { 55 | t.Error("krew should fail when old receipts structure is detected") 56 | } 57 | if !strings.Contains(string(out), "Uninstall Krew") { 58 | t.Errorf("output should contain instructions on upgrading: %s", string(out)) 59 | } 60 | } 61 | 62 | func isIndexMigrated(it *ITest) bool { 63 | indexPath := environment.NewPaths(it.Root()).IndexPath(constants.DefaultIndexName) 64 | _, err := os.Stat(indexPath) 65 | return err == nil 66 | } 67 | 68 | // TODO remove when testing indexmigration is no longer necessary 69 | func prepareOldIndexLayout(it *ITest) { 70 | paths := environment.NewPaths(it.Root()) 71 | indexPath := paths.IndexPath(constants.DefaultIndexName) 72 | tmpPath := it.TempDir().Path("tmp_index") 73 | newPath := paths.IndexBase() 74 | if err := os.Rename(indexPath, tmpPath); err != nil { 75 | it.t.Fatal(err) 76 | } 77 | if err := os.Remove(newPath); err != nil { 78 | it.t.Fatal(err) 79 | } 80 | if err := os.Rename(tmpPath, newPath); err != nil { 81 | it.t.Fatal(err) 82 | } 83 | } 84 | 85 | func prepareOldKrewRoot(test *ITest) { 86 | // receipts are not present in old krew home 87 | if err := os.RemoveAll(test.tempDir.Path("receipts")); err != nil { 88 | test.t.Fatal(err) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /integration_test/search_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package integrationtest 16 | 17 | import ( 18 | "regexp" 19 | "sort" 20 | "strings" 21 | "testing" 22 | 23 | "sigs.k8s.io/krew/pkg/constants" 24 | ) 25 | 26 | func TestKrewSearchAll(t *testing.T) { 27 | skipShort(t) 28 | 29 | test := NewTest(t) 30 | 31 | output := test.WithDefaultIndex().Krew("search").RunOrFailOutput() 32 | availablePlugins := test.IndexPluginCount(constants.DefaultIndexName) 33 | if plugins := lines(output); len(plugins)-1 != availablePlugins { 34 | // the first line is the header 35 | t.Errorf("Expected %d plugins, got %d", availablePlugins, len(plugins)-1) 36 | } 37 | } 38 | 39 | func TestKrewSearchOne(t *testing.T) { 40 | skipShort(t) 41 | 42 | test := NewTest(t) 43 | 44 | plugins := lines(test.WithDefaultIndex().Krew("search", "krew").RunOrFailOutput()) 45 | if len(plugins) < 2 { 46 | t.Errorf("Expected krew to be a valid plugin") 47 | } 48 | if !strings.HasPrefix(plugins[1], "krew ") { 49 | t.Errorf("The first match should be krew") 50 | } 51 | } 52 | 53 | func TestKrewSearchMultiIndex(t *testing.T) { 54 | skipShort(t) 55 | test := NewTest(t) 56 | test = test.WithDefaultIndex().WithCustomIndexFromDefault("foo") 57 | 58 | test.Krew("install", validPlugin).RunOrFail() 59 | test.Krew("install", "foo/"+validPlugin2).RunOrFail() 60 | 61 | output := string(test.Krew("search").RunOrFailOutput()) 62 | wantPatterns := []*regexp.Regexp{ 63 | regexp.MustCompile(`(?m)^` + validPlugin + `\b.*\byes`), 64 | regexp.MustCompile(`(?m)^` + validPlugin2 + `\b.*\bno`), 65 | regexp.MustCompile(`(?m)^foo/` + validPlugin + `\b.*\bno$`), 66 | regexp.MustCompile(`(?m)^foo/` + validPlugin2 + `\b.*\byes$`), 67 | } 68 | for _, p := range wantPatterns { 69 | if !p.MatchString(output) { 70 | t.Fatalf("pattern %s not found in search output=%s", p, output) 71 | } 72 | } 73 | } 74 | 75 | func TestKrewSearchMultiIndexSortedByDisplayName(t *testing.T) { 76 | skipShort(t) 77 | test := NewTest(t) 78 | test = test.WithDefaultIndex().WithCustomIndexFromDefault("foo") 79 | 80 | output := string(test.Krew("search").RunOrFailOutput()) 81 | 82 | // match first column that is not NAME by matching everything up until a space 83 | names := regexp.MustCompile(`(?m)^[^\s|NAME]+\b`).FindAllString(output, -1) 84 | if len(names) < 10 { 85 | t.Fatalf("could not capture names") 86 | } 87 | if !sort.StringsAreSorted(names) { 88 | t.Fatalf("names are not sorted: [%s]", strings.Join(names, ", ")) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /integration_test/testdata/ctx.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: krew.googlecontainertools.github.com/v1alpha2 2 | kind: Plugin 3 | metadata: 4 | name: ctx 5 | spec: 6 | homepage: https://github.com/ahmetb/kubectx 7 | shortDescription: Switch between contexts in your kubeconfig 8 | version: v0.7.0 9 | description: | 10 | Also known as "kubectx", a utility to switch between context entries in 11 | your kubeconfig file efficiently. 12 | caveats: | 13 | If fzf is installed on your machine, you can interactively choose 14 | between the entries using the arrow keys, or by fuzzy searching 15 | as you type. 16 | See https://github.com/ahmetb/kubectx for customization and details. 17 | platforms: 18 | - selector: 19 | matchExpressions: 20 | - key: os 21 | operator: In 22 | values: 23 | - darwin 24 | - linux 25 | uri: https://github.com/ahmetb/kubectx/archive/v0.7.1.tar.gz 26 | sha256: 6df4def2caf5a9c291310124098ad6c4c3123936ddd4080b382b9f7930a233ec 27 | bin: kubectx 28 | files: 29 | - from: kubectx-*/kubectx 30 | to: . 31 | - from: kubectx-*/LICENSE 32 | to: . 33 | -------------------------------------------------------------------------------- /integration_test/testdata/foo.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/krew/b1ac5bfdfb5bba3d652323ecd17febd40eea9d27/integration_test/testdata/foo.tar.gz -------------------------------------------------------------------------------- /integration_test/testdata/foo.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: krew.googlecontainertools.github.com/v1alpha2 2 | kind: Plugin 3 | metadata: 4 | name: foo 5 | spec: 6 | version: "v0.1.0" 7 | shortDescription: A valid plugin for integration tests 8 | platforms: 9 | - uri: https://foo.bar/foo.tar.gz 10 | sha256: 354bad230cdd0966fc8c919476c4e6c7f2078b04a6ff7dead6a811cdc101d31e 11 | bin: foo.sh 12 | files: 13 | - from: ./foo.sh 14 | to: "." 15 | selector: 16 | matchExpressions: 17 | - key: os 18 | operator: In 19 | values: ["darwin", "linux"] 20 | - uri: https://foo.bar/foo.tar.gz 21 | sha256: 354bad230cdd0966fc8c919476c4e6c7f2078b04a6ff7dead6a811cdc101d31e 22 | bin: foo.bat 23 | files: 24 | - from: ./foo.bat 25 | to: "." 26 | selector: 27 | matchLabels: 28 | os: windows 29 | -------------------------------------------------------------------------------- /integration_test/version_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package integrationtest 16 | 17 | import ( 18 | "path" 19 | "regexp" 20 | "strings" 21 | "testing" 22 | 23 | "github.com/pkg/errors" 24 | ) 25 | 26 | func TestKrewVersion(t *testing.T) { 27 | skipShort(t) 28 | 29 | test := NewTest(t) 30 | 31 | stdOut := string(test.Krew("version").RunOrFailOutput()) 32 | err := checkRequiredSubstrings(test, "https://github.com/kubernetes-sigs/krew-index.git", stdOut) 33 | if err != nil { 34 | t.Error(err) 35 | } 36 | } 37 | 38 | func TestKrewVersion_CustomDefaultIndexURI(t *testing.T) { 39 | skipShort(t) 40 | 41 | test := NewTest(t) 42 | 43 | stdOut := string(test.WithEnv("KREW_DEFAULT_INDEX_URI", "foo").Krew("version").RunOrFailOutput()) 44 | err := checkRequiredSubstrings(test, "foo", stdOut) 45 | if err != nil { 46 | t.Error(err) 47 | } 48 | } 49 | 50 | func checkRequiredSubstrings(test *ITest, index, stdOut string) error { 51 | lineSplit := regexp.MustCompile(`\s+`) 52 | actual := make(map[string]string) 53 | for _, line := range strings.Split(stdOut, "\n") { 54 | if line == "" { 55 | continue 56 | } 57 | optionValue := lineSplit.Split(line, 2) 58 | if len(optionValue) < 2 { 59 | return errors.Errorf("`%v` is not an `OPTION VALUE` pair separated by spaces", optionValue) 60 | } 61 | actual[optionValue[0]] = optionValue[1] 62 | } 63 | 64 | requiredSubstrings := map[string]string{ 65 | "OPTION": "VALUE", 66 | "BasePath": test.Root(), 67 | "GitTag": "", 68 | "GitCommit": "", 69 | "IndexURI": index, 70 | "IndexPath": path.Join(test.Root(), "index"), 71 | "InstallPath": path.Join(test.Root(), "store"), 72 | "BinPath": path.Join(test.Root(), "bin"), 73 | "DetectedPlatform": "/", 74 | } 75 | 76 | for k, v := range requiredSubstrings { 77 | got, ok := actual[k] 78 | if !ok { 79 | return errors.Errorf("`krew version` output doesn't contain field %q", k) 80 | } else if !strings.Contains(got, v) { 81 | return errors.Errorf("`krew version` %q field doesn't contain string %q (got: %q)", k, v, got) 82 | } 83 | } 84 | 85 | return nil 86 | } 87 | -------------------------------------------------------------------------------- /internal/download/fetch.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package download 16 | 17 | import ( 18 | "io" 19 | "net/http" 20 | "os" 21 | 22 | "github.com/pkg/errors" 23 | "k8s.io/klog/v2" 24 | ) 25 | 26 | // Fetcher is used to get files from a URI. 27 | type Fetcher interface { 28 | // Get gets the file and returns an stream to read the file. 29 | Get(uri string) (io.ReadCloser, error) 30 | } 31 | 32 | var _ Fetcher = HTTPFetcher{} 33 | 34 | // HTTPFetcher is used to get a file from a http:// or https:// schema path. 35 | type HTTPFetcher struct{} 36 | 37 | // Get gets the file and returns an stream to read the file. 38 | func (HTTPFetcher) Get(uri string) (io.ReadCloser, error) { 39 | klog.V(2).Infof("Fetching %q", uri) 40 | resp, err := http.Get(uri) 41 | if err != nil { 42 | return nil, errors.Wrapf(err, "failed to download %q", uri) 43 | } 44 | if resp.StatusCode > 200 { 45 | return nil, errors.Errorf("failed to download %q, status code %d", uri, resp.StatusCode) 46 | } 47 | return resp.Body, nil 48 | } 49 | 50 | var _ Fetcher = fileFetcher{} 51 | 52 | type fileFetcher struct{ f string } 53 | 54 | func (f fileFetcher) Get(_ string) (io.ReadCloser, error) { 55 | klog.V(2).Infof("Reading %q", f.f) 56 | file, err := os.Open(f.f) 57 | return file, errors.Wrapf(err, "failed to open archive file %q for reading", f.f) 58 | } 59 | 60 | // NewFileFetcher returns a local file reader. 61 | func NewFileFetcher(path string) Fetcher { return fileFetcher{f: path} } 62 | -------------------------------------------------------------------------------- /internal/download/testdata/bash-ascii-file: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "test" -------------------------------------------------------------------------------- /internal/download/testdata/bash-utf8-file: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "test 😀 " -------------------------------------------------------------------------------- /internal/download/testdata/null-file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/krew/b1ac5bfdfb5bba3d652323ecd17febd40eea9d27/internal/download/testdata/null-file -------------------------------------------------------------------------------- /internal/download/testdata/test-flat-hierarchy.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/krew/b1ac5bfdfb5bba3d652323ecd17febd40eea9d27/internal/download/testdata/test-flat-hierarchy.tar.gz -------------------------------------------------------------------------------- /internal/download/testdata/test-flat-hierarchy.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/krew/b1ac5bfdfb5bba3d652323ecd17febd40eea9d27/internal/download/testdata/test-flat-hierarchy.zip -------------------------------------------------------------------------------- /internal/download/testdata/test-with-directory-entry.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/krew/b1ac5bfdfb5bba3d652323ecd17febd40eea9d27/internal/download/testdata/test-with-directory-entry.tar.gz -------------------------------------------------------------------------------- /internal/download/testdata/test-with-directory-entry.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/krew/b1ac5bfdfb5bba3d652323ecd17febd40eea9d27/internal/download/testdata/test-with-directory-entry.zip -------------------------------------------------------------------------------- /internal/download/testdata/test-with-no-directory-entry.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/krew/b1ac5bfdfb5bba3d652323ecd17febd40eea9d27/internal/download/testdata/test-with-no-directory-entry.tar.gz -------------------------------------------------------------------------------- /internal/download/testdata/test-with-no-directory-entry.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/krew/b1ac5bfdfb5bba3d652323ecd17febd40eea9d27/internal/download/testdata/test-with-no-directory-entry.zip -------------------------------------------------------------------------------- /internal/download/testdata/test/foo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/krew/b1ac5bfdfb5bba3d652323ecd17febd40eea9d27/internal/download/testdata/test/foo -------------------------------------------------------------------------------- /internal/download/verifier.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package download 16 | 17 | import ( 18 | "bytes" 19 | "crypto/sha256" 20 | "encoding/hex" 21 | "hash" 22 | "io" 23 | 24 | "github.com/pkg/errors" 25 | "k8s.io/klog/v2" 26 | ) 27 | 28 | // Verifier can check a reader against it's correctness. 29 | type Verifier interface { 30 | io.Writer 31 | Verify() error 32 | } 33 | 34 | var _ Verifier = sha256Verifier{} 35 | 36 | type sha256Verifier struct { 37 | hash.Hash 38 | wantedHash []byte 39 | } 40 | 41 | // NewSha256Verifier creates a Verifier that tests against the given hash. 42 | func NewSha256Verifier(hashed string) Verifier { 43 | raw, _ := hex.DecodeString(hashed) 44 | return sha256Verifier{ 45 | Hash: sha256.New(), 46 | wantedHash: raw, 47 | } 48 | } 49 | 50 | func (v sha256Verifier) Verify() error { 51 | klog.V(1).Infof("Compare sha256 (%s) signed version", hex.EncodeToString(v.wantedHash)) 52 | if bytes.Equal(v.wantedHash, v.Sum(nil)) { 53 | return nil 54 | } 55 | return errors.Errorf("checksum does not match, want: %x, got %x", v.wantedHash, v.Sum(nil)) 56 | } 57 | -------------------------------------------------------------------------------- /internal/download/verifier_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package download 16 | 17 | import ( 18 | "bytes" 19 | "io" 20 | "testing" 21 | ) 22 | 23 | func TestSha256Verifier(t *testing.T) { 24 | type args struct { 25 | hash string 26 | } 27 | tests := []struct { 28 | name string 29 | args args 30 | write []byte 31 | wantError bool 32 | }{ 33 | { 34 | name: "test okay hash", 35 | args: args{ 36 | hash: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", 37 | }, 38 | write: []byte("hello world"), 39 | wantError: false, 40 | }, 41 | { 42 | name: "test wrong hash", 43 | args: args{ 44 | hash: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", 45 | }, 46 | write: []byte("HELLO WORLD"), 47 | wantError: true, 48 | }, 49 | } 50 | for _, tt := range tests { 51 | t.Run(tt.name, func(t *testing.T) { 52 | v := NewSha256Verifier(tt.args.hash) 53 | _, _ = io.Copy(v, bytes.NewReader(tt.write)) 54 | if err := v.Verify(); (err != nil) != tt.wantError { 55 | t.Errorf("NewSha256Verifier().Write(%x).Verify() = %v, wantReader %v", tt.write, err, tt.wantError) 56 | return 57 | } 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /internal/gitutil/git.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gitutil 16 | 17 | import ( 18 | "bytes" 19 | "io" 20 | "os" 21 | osexec "os/exec" 22 | "path/filepath" 23 | "runtime" 24 | "strings" 25 | 26 | "github.com/pkg/errors" 27 | "k8s.io/klog/v2" 28 | ) 29 | 30 | // EnsureCloned will clone into the destination path, otherwise will return no error. 31 | func EnsureCloned(uri, destinationPath string) error { 32 | if ok, err := IsGitCloned(destinationPath); err != nil { 33 | return err 34 | } else if !ok { 35 | _, err = Exec("", "clone", "-v", uri, destinationPath) 36 | return err 37 | } 38 | return nil 39 | } 40 | 41 | // IsGitCloned will test if the path is a git dir. 42 | func IsGitCloned(gitPath string) (bool, error) { 43 | f, err := os.Stat(filepath.Join(gitPath, ".git")) 44 | if os.IsNotExist(err) { 45 | return false, nil 46 | } 47 | return err == nil && f.IsDir(), err 48 | } 49 | 50 | // update will fetch origin and set HEAD to origin/HEAD 51 | // and also will create a pristine working directory by removing 52 | // untracked files and directories. 53 | func updateAndCleanUntracked(destinationPath string) error { 54 | if _, err := Exec(destinationPath, "fetch", "-v"); err != nil { 55 | return errors.Wrapf(err, "fetch index at %q failed", destinationPath) 56 | } 57 | 58 | if _, err := Exec(destinationPath, "reset", "--hard", "@{upstream}"); err != nil { 59 | return errors.Wrapf(err, "reset index at %q failed", destinationPath) 60 | } 61 | 62 | _, err := Exec(destinationPath, "clean", "-xfd") 63 | return errors.Wrapf(err, "clean index at %q failed", destinationPath) 64 | } 65 | 66 | // EnsureUpdated will ensure the destination path exists and is up to date. 67 | func EnsureUpdated(uri, destinationPath string) error { 68 | if err := EnsureCloned(uri, destinationPath); err != nil { 69 | return err 70 | } 71 | return updateAndCleanUntracked(destinationPath) 72 | } 73 | 74 | // GetRemoteURL returns the url of the remote origin 75 | func GetRemoteURL(dir string) (string, error) { 76 | return Exec(dir, "config", "--get", "remote.origin.url") 77 | } 78 | 79 | func Exec(pwd string, args ...string) (string, error) { 80 | klog.V(4).Infof("Going to run git %s", strings.Join(args, " ")) 81 | cmd := osexec.Command("git", args...) 82 | cmd.Dir = pwd 83 | if runtime.GOOS == "windows" { 84 | // Workaround on windows. git for windows can't handle @{uptream} as same as 85 | // given. Disable glob for this command if running on Cygwin or MSYS2. 86 | envs := os.Environ() 87 | envs = append(envs, "MSYS=noglob "+os.Getenv("MSYS"), "CYGWIN=noglob "+os.Getenv("CYGWIN")) 88 | cmd.Env = envs 89 | } 90 | buf := bytes.Buffer{} 91 | var w io.Writer = &buf 92 | if klog.V(2).Enabled() { 93 | w = io.MultiWriter(w, os.Stderr) 94 | } 95 | cmd.Stdout, cmd.Stderr = w, w 96 | if err := cmd.Run(); err != nil { 97 | return "", errors.Wrapf(err, "command execution failure, output=%q", buf.String()) 98 | } 99 | return strings.TrimSpace(buf.String()), nil 100 | } 101 | -------------------------------------------------------------------------------- /internal/index/indexoperations/index.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package indexoperations 16 | 17 | import ( 18 | "os" 19 | "regexp" 20 | 21 | "github.com/pkg/errors" 22 | 23 | "sigs.k8s.io/krew/internal/environment" 24 | "sigs.k8s.io/krew/internal/gitutil" 25 | ) 26 | 27 | var validNamePattern = regexp.MustCompile(`^[A-Za-z0-9_-]+$`) 28 | 29 | // Index describes the name and URL of a configured index. 30 | type Index struct { 31 | Name string 32 | URL string 33 | } 34 | 35 | // ListIndexes returns a slice of Index objects. The path argument is used as 36 | // the base path of the index. 37 | func ListIndexes(paths environment.Paths) ([]Index, error) { 38 | entries, err := os.ReadDir(paths.IndexBase()) 39 | if err != nil { 40 | return nil, errors.Wrap(err, "failed to list directory") 41 | } 42 | 43 | indexes := []Index{} 44 | for _, e := range entries { 45 | if !e.IsDir() { 46 | continue 47 | } 48 | indexName := e.Name() 49 | remote, err := gitutil.GetRemoteURL(paths.IndexPath(indexName)) 50 | if err != nil { 51 | return nil, errors.Wrapf(err, "failed to list the remote URL for index %s", indexName) 52 | } 53 | 54 | indexes = append(indexes, Index{ 55 | Name: indexName, 56 | URL: remote, 57 | }) 58 | } 59 | return indexes, nil 60 | } 61 | 62 | // AddIndex initializes a new index to install plugins from. 63 | func AddIndex(paths environment.Paths, name, url string) error { 64 | dir := paths.IndexPath(name) 65 | if _, err := os.Stat(dir); os.IsNotExist(err) { 66 | return gitutil.EnsureCloned(url, dir) 67 | } else if err != nil { 68 | return err 69 | } 70 | return errors.New("index already exists") 71 | } 72 | 73 | // DeleteIndex removes specified index name. If index does not exist, returns an error that can be tested by os.IsNotExist. 74 | func DeleteIndex(paths environment.Paths, name string) error { 75 | dir := paths.IndexPath(name) 76 | if _, err := os.Stat(dir); err != nil { 77 | return err 78 | } 79 | 80 | return os.RemoveAll(dir) 81 | } 82 | 83 | // IsValidIndexName validates if an index name contains invalid characters 84 | func IsValidIndexName(name string) bool { 85 | return validNamePattern.MatchString(name) 86 | } 87 | -------------------------------------------------------------------------------- /internal/index/indexscanner/testdata/testindex/dontscan.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: krew.googlecontainertools.github.com/v1alpha2 16 | kind: Plugin 17 | metadata: 18 | name: dontscan 19 | spec: 20 | platforms: 21 | - files: 22 | - from: "*" 23 | to: "." 24 | uri: https://example.com 25 | sha256: deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef 26 | selector: 27 | matchExpressions: 28 | - {key: os, operator: In, values: [macos, linux]} 29 | - files: 30 | - from: "*" 31 | to: "." 32 | uri: https://example.com 33 | sha256: deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef 34 | selector: 35 | matchLabels: 36 | os: "windows" 37 | shortDescription: "exists" 38 | 39 | -------------------------------------------------------------------------------- /internal/index/indexscanner/testdata/testindex/plugins/badplugin.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: krew.googlecontainertools.github.com/v1alpha2 16 | kind: Plugin 17 | metadata: 18 | name: badplugin 19 | BADKEYFIELD: {} # Unknown key should throw error. 20 | 21 | -------------------------------------------------------------------------------- /internal/index/indexscanner/testdata/testindex/plugins/badplugin2.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: krew.googlecontainertools.github.com/v1alpha2 16 | kind: Plugin 17 | metadata: 18 | name: badplugin 19 | spec: 20 | platforms: 21 | - BADKEYFIELD: {} 22 | 23 | -------------------------------------------------------------------------------- /internal/index/indexscanner/testdata/testindex/plugins/bar.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: krew.googlecontainertools.github.com/v1alpha2 16 | kind: Plugin 17 | metadata: 18 | name: bar 19 | spec: 20 | version: v1.0.0 21 | platforms: 22 | - files: 23 | - from: "*" 24 | to: "." 25 | uri: https://example.com 26 | sha256: deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef 27 | bin: kubectl-bar 28 | selector: 29 | matchLabels: 30 | os: windows 31 | - files: 32 | - from: "*" 33 | to: "." 34 | uri: https://example.com 35 | sha256: deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef 36 | bin: kubectl-bar 37 | selector: 38 | matchLabels: 39 | os: linux 40 | shortDescription: "exists" 41 | -------------------------------------------------------------------------------- /internal/index/indexscanner/testdata/testindex/plugins/foo.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: krew.googlecontainertools.github.com/v1alpha2 16 | kind: Plugin 17 | metadata: 18 | name: foo 19 | spec: 20 | version: v1.0.0 21 | platforms: 22 | - files: 23 | - from: "*" 24 | to: "." 25 | uri: https://example.com 26 | sha256: deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef 27 | bin: kubectl-foo 28 | selector: 29 | matchExpressions: 30 | - {key: os, operator: In, values: [macos, linux]} 31 | - files: 32 | - from: "*" 33 | to: "." 34 | uri: https://example.com 35 | sha256: deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef 36 | bin: kubectl-foo 37 | selector: 38 | matchLabels: 39 | os: "windows" 40 | shortDescription: "exists" 41 | homepage: "https://example.com/foo" 42 | -------------------------------------------------------------------------------- /internal/index/indexscanner/testdata/testindex/plugins/notyaml.txt: -------------------------------------------------------------------------------- 1 | apiVersion: krew.googlecontainertools.github.com/v1alpha2 2 | kind: Plugin 3 | metadata: 4 | name: notyaml-plugin 5 | -------------------------------------------------------------------------------- /internal/index/indexscanner/testdata/testindex/plugins/wrongname.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: krew.googlecontainertools.github.com/v1alpha2 16 | kind: Plugin 17 | metadata: 18 | name: mismatchedname 19 | -------------------------------------------------------------------------------- /internal/indexmigration/migration.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package indexmigration 16 | 17 | import ( 18 | "os" 19 | "path/filepath" 20 | 21 | "github.com/pkg/errors" 22 | "k8s.io/klog/v2" 23 | 24 | "sigs.k8s.io/krew/internal/environment" 25 | ) 26 | 27 | // Done checks if the krew installation requires a migration to support multiple indexes. 28 | // A migration is necessary when the index directory contains a ".git" directory. 29 | func Done(paths environment.Paths) (bool, error) { 30 | klog.V(2).Info("Checking if index migration is needed.") 31 | _, err := os.Stat(filepath.Join(paths.IndexBase(), ".git")) 32 | if err != nil && os.IsNotExist(err) { 33 | klog.V(2).Infoln("Index already migrated.") 34 | return true, nil 35 | } 36 | return false, err 37 | } 38 | 39 | // Migrate moves the index directory to the new default index path. 40 | func Migrate(paths environment.Paths) error { 41 | klog.Info("Migrating krew index layout.") 42 | indexPath := paths.IndexBase() 43 | tmpPath := filepath.Join(paths.BasePath(), "tmp_index_migration") 44 | newPath := filepath.Join(paths.IndexBase(), "default") 45 | 46 | if err := os.Rename(indexPath, tmpPath); err != nil { 47 | return errors.Wrapf(err, "could not move index directory %q to temporary location %q", indexPath, tmpPath) 48 | } 49 | 50 | if err := os.Mkdir(indexPath, os.ModePerm); err != nil { 51 | return errors.Wrapf(err, "could not create index directory %q", indexPath) 52 | } 53 | 54 | if err := os.Rename(tmpPath, newPath); err != nil { 55 | return errors.Wrapf(err, "could not move temporary index directory %q to new location %q", tmpPath, newPath) 56 | } 57 | 58 | klog.Info("Migration completed successfully.") 59 | return nil 60 | } 61 | -------------------------------------------------------------------------------- /internal/indexmigration/migration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package indexmigration 16 | 17 | import ( 18 | "os" 19 | "testing" 20 | 21 | "sigs.k8s.io/krew/internal/environment" 22 | "sigs.k8s.io/krew/internal/testutil" 23 | ) 24 | 25 | func TestIsMigrated(t *testing.T) { 26 | tests := []struct { 27 | name string 28 | dirPath string 29 | expected bool 30 | }{ 31 | { 32 | name: "Already migrated", 33 | dirPath: "index/default/.git", 34 | expected: true, 35 | }, 36 | { 37 | name: "Not migrated", 38 | dirPath: "index/.git", 39 | expected: false, 40 | }, 41 | } 42 | 43 | for _, test := range tests { 44 | t.Run(test.name, func(t *testing.T) { 45 | tmpDir := testutil.NewTempDir(t) 46 | 47 | err := os.MkdirAll(tmpDir.Path(test.dirPath), os.ModePerm) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | 52 | newPaths := environment.NewPaths(tmpDir.Root()) 53 | actual, err := Done(newPaths) 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | if actual != test.expected { 58 | t.Errorf("Expected %v but found %v", test.expected, actual) 59 | } 60 | }) 61 | } 62 | } 63 | 64 | func TestMigrate(t *testing.T) { 65 | tmpDir := testutil.NewTempDir(t) 66 | 67 | tmpDir.Write("index/.git", nil) 68 | 69 | newPaths := environment.NewPaths(tmpDir.Root()) 70 | err := Migrate(newPaths) 71 | if err != nil { 72 | t.Fatal(err) 73 | } 74 | done, err := Done(newPaths) 75 | if err != nil || !done { 76 | t.Errorf("expected migration to be done: %s", err) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /internal/installation/platform.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package installation 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "runtime" 21 | 22 | "github.com/pkg/errors" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | "k8s.io/apimachinery/pkg/labels" 25 | "k8s.io/klog/v2" 26 | 27 | "sigs.k8s.io/krew/pkg/index" 28 | ) 29 | 30 | // GetMatchingPlatform finds the platform spec in the specified plugin that 31 | // matches the os/arch of the current machine (can be overridden via KREW_OS 32 | // and/or KREW_ARCH). 33 | func GetMatchingPlatform(platforms []index.Platform) (index.Platform, bool, error) { 34 | return matchPlatform(platforms, OSArch()) 35 | } 36 | 37 | // matchPlatform returns the first matching platform to given os/arch. 38 | func matchPlatform(platforms []index.Platform, env OSArchPair) (index.Platform, bool, error) { 39 | envLabels := labels.Set{ 40 | "os": env.OS, 41 | "arch": env.Arch, 42 | } 43 | klog.V(2).Infof("Matching platform for labels(%v)", envLabels) 44 | 45 | for i, platform := range platforms { 46 | sel, err := metav1.LabelSelectorAsSelector(platform.Selector) 47 | if err != nil { 48 | return index.Platform{}, false, errors.Wrap(err, "failed to compile label selector") 49 | } 50 | if sel.Matches(envLabels) { 51 | klog.V(2).Infof("Found matching platform with index (%d)", i) 52 | return platform, true, nil 53 | } 54 | } 55 | return index.Platform{}, false, nil 56 | } 57 | 58 | // OSArchPair is wrapper around operating system and architecture 59 | type OSArchPair struct { 60 | OS, Arch string 61 | } 62 | 63 | // String converts environment into a string 64 | func (p OSArchPair) String() string { 65 | return fmt.Sprintf("%s/%s", p.OS, p.Arch) 66 | } 67 | 68 | // OSArch returns the OS/arch combination to be used on the current system. It 69 | // can be overridden by setting KREW_OS and/or KREW_ARCH environment variables. 70 | func OSArch() OSArchPair { 71 | return OSArchPair{ 72 | OS: getEnvOrDefault("KREW_OS", runtime.GOOS), 73 | Arch: getEnvOrDefault("KREW_ARCH", runtime.GOARCH), 74 | } 75 | } 76 | 77 | func getEnvOrDefault(env, absent string) string { 78 | v := os.Getenv(env) 79 | if v != "" { 80 | return v 81 | } 82 | return absent 83 | } 84 | -------------------------------------------------------------------------------- /internal/installation/platform_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package installation 16 | 17 | import ( 18 | "runtime" 19 | "testing" 20 | 21 | "github.com/google/go-cmp/cmp" 22 | 23 | "sigs.k8s.io/krew/internal/testutil" 24 | "sigs.k8s.io/krew/pkg/index" 25 | ) 26 | 27 | func Test_osArch(t *testing.T) { 28 | in := OSArchPair{OS: runtime.GOOS, Arch: runtime.GOARCH} 29 | 30 | if diff := cmp.Diff(in, OSArch()); diff != "" { 31 | t.Errorf("os/arch got a different result:\n%s", diff) 32 | } 33 | } 34 | 35 | func Test_osArch_override(t *testing.T) { 36 | customGoOSArch := OSArchPair{OS: "dragons", Arch: "metav1"} 37 | t.Setenv("KREW_OS", customGoOSArch.OS) 38 | t.Setenv("KREW_ARCH", customGoOSArch.Arch) 39 | 40 | if diff := cmp.Diff(customGoOSArch, OSArch()); diff != "" { 41 | t.Errorf("os/arch override got a different result:\n%s", diff) 42 | } 43 | } 44 | 45 | func Test_matchPlatform(t *testing.T) { 46 | target := OSArchPair{OS: "foo", Arch: "amd64"} 47 | matchingPlatform := testutil.NewPlatform().WithOSArch(target.OS, target.Arch).V() 48 | differentOS := testutil.NewPlatform().WithOSArch("other", target.Arch).V() 49 | differentArch := testutil.NewPlatform().WithOSArch(target.OS, "other").V() 50 | 51 | p, ok, err := matchPlatform([]index.Platform{differentOS, differentArch, matchingPlatform}, target) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | if !ok { 56 | t.Fatal("failed to find a match") 57 | } 58 | if diff := cmp.Diff(p, matchingPlatform); diff != "" { 59 | t.Fatalf("got a different object from the matching platform:\n%s", diff) 60 | } 61 | 62 | _, ok, err = matchPlatform([]index.Platform{differentOS, differentArch}, target) 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | if ok { 67 | t.Fatal("got a matching platform, but was not expecting") 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /internal/installation/receipt/receipt.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package receipt 16 | 17 | import ( 18 | "os" 19 | 20 | "github.com/pkg/errors" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | "sigs.k8s.io/yaml" 23 | 24 | "sigs.k8s.io/krew/internal/index/indexscanner" 25 | "sigs.k8s.io/krew/pkg/index" 26 | ) 27 | 28 | // Store saves the given receipt at the destination. 29 | // The caller has to ensure that the destination directory exists. 30 | func Store(receipt index.Receipt, dest string) error { 31 | yamlBytes, err := yaml.Marshal(receipt) 32 | if err != nil { 33 | return errors.Wrapf(err, "convert to yaml") 34 | } 35 | 36 | err = os.WriteFile(dest, yamlBytes, 0o644) 37 | return errors.Wrapf(err, "write plugin receipt %q", dest) 38 | } 39 | 40 | // Load reads the plugin receipt at the specified destination. 41 | // If not found, it returns os.IsNotExist error. 42 | func Load(path string) (index.Receipt, error) { 43 | return indexscanner.ReadReceiptFromFile(path) 44 | } 45 | 46 | // New returns a new receipt with the given plugin and index name. 47 | func New(plugin index.Plugin, indexName string, timestamp metav1.Time) index.Receipt { 48 | plugin.CreationTimestamp = timestamp 49 | return index.Receipt{ 50 | Plugin: plugin, 51 | Status: index.ReceiptStatus{ 52 | Source: index.SourceIndex{ 53 | Name: indexName, 54 | }, 55 | }, 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /internal/installation/receipt/receipt_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package receipt 16 | 17 | import ( 18 | "os" 19 | "testing" 20 | 21 | "github.com/google/go-cmp/cmp" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | 24 | "sigs.k8s.io/krew/internal/index/indexscanner" 25 | "sigs.k8s.io/krew/internal/testutil" 26 | "sigs.k8s.io/krew/pkg/constants" 27 | ) 28 | 29 | func TestStore(t *testing.T) { 30 | tmpDir := testutil.NewTempDir(t) 31 | 32 | testPlugin := testutil.NewPlugin().WithName("some-plugin").WithPlatforms(testutil.NewPlatform().V()).V() 33 | testReceipt := testutil.NewReceipt().WithPlugin(testPlugin).V() 34 | dest := tmpDir.Path("some-plugin.yaml") 35 | 36 | if err := Store(testReceipt, dest); err != nil { 37 | t.Fatal(err) 38 | } 39 | 40 | actual, err := indexscanner.ReadReceiptFromFile(dest) 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | 45 | if diff := cmp.Diff(&testReceipt, &actual); diff != "" { 46 | t.Fatal(diff) 47 | } 48 | } 49 | 50 | func TestLoad(t *testing.T) { 51 | tmpDir := testutil.NewTempDir(t) 52 | 53 | testPlugin := testutil.NewPlugin().WithName("foo").WithPlatforms(testutil.NewPlatform().V()).V() 54 | testPluginReceipt := testutil.NewReceipt().WithPlugin(testPlugin).V() 55 | if err := Store(testPluginReceipt, tmpDir.Path("foo.yaml")); err != nil { 56 | t.Fatal(err) 57 | } 58 | 59 | gotPlugin, err := Load(tmpDir.Path("foo.yaml")) 60 | if err != nil { 61 | t.Fatal(err) 62 | } 63 | if diff := cmp.Diff(&gotPlugin, &testPluginReceipt); diff != "" { 64 | t.Fatal(diff) 65 | } 66 | } 67 | 68 | func TestLoad_preservesNonExistsError(t *testing.T) { 69 | _, err := Load("non-existing.yaml") 70 | if !os.IsNotExist(err) { 71 | t.Fatalf("returned error is not ENOENT: %+v", err) 72 | } 73 | } 74 | 75 | func TestNew(t *testing.T) { 76 | timestamp := metav1.Now() 77 | testPlugin := testutil.NewPlugin().WithName("foo").WithPlatforms(testutil.NewPlatform().V()).V() 78 | wantReceipt := testutil.NewReceipt().WithPlugin(testPlugin).V() 79 | wantReceipt.CreationTimestamp = timestamp 80 | 81 | gotReceipt := New(testPlugin, constants.DefaultIndexName, timestamp) 82 | if diff := cmp.Diff(gotReceipt, wantReceipt); diff != "" { 83 | t.Fatalf("expected receipts to match: %s", diff) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /internal/installation/semver/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package semver is a wrapper for handling of semantic version 16 | // (https://semver.org) values. 17 | package semver 18 | 19 | import ( 20 | "strings" 21 | 22 | "github.com/pkg/errors" 23 | k8sver "k8s.io/apimachinery/pkg/util/version" 24 | ) 25 | 26 | // Version is in-memory representation of a semantic version 27 | // (https://semver.org) value. 28 | type Version k8sver.Version 29 | 30 | // String returns string representation of a semantic version value with a 31 | // leading 'v' character. 32 | func (v Version) String() string { 33 | vv := k8sver.Version(v) 34 | s := (&vv).String() 35 | if !strings.HasPrefix(s, "v") { 36 | s = "v" + s 37 | } 38 | return s 39 | } 40 | 41 | // Parse parses a semantic version value with a leading 'v' character. 42 | func Parse(s string) (Version, error) { 43 | var vv Version 44 | if !strings.HasPrefix(s, "v") { 45 | return vv, errors.Errorf("version string %q not starting with 'v'", s) 46 | } 47 | v, err := k8sver.ParseSemantic(s) 48 | if err != nil { 49 | return vv, err 50 | } 51 | return Version(*v), nil 52 | } 53 | 54 | // Less checks if a is strictly less than b (a d/b/c 40 | func ReplaceBase(path, old, replacement string) (string, error) { 41 | extendingPath, ok := IsSubPath(old, path) 42 | if !ok { 43 | return "", errors.Errorf("can't replace %q in %q, it is not a subpath", old, path) 44 | } 45 | return filepath.Join(replacement, extendingPath), nil 46 | } 47 | 48 | // CanonicalPluginName resolves a plugin's index and name from input string. 49 | // If an index is not specified, the default index name is assumed. 50 | func CanonicalPluginName(in string) (string, string) { 51 | if strings.Count(in, "/") == 0 { 52 | return constants.DefaultIndexName, in 53 | } 54 | p := strings.SplitN(in, "/", 2) 55 | return p[0], p[1] 56 | } 57 | -------------------------------------------------------------------------------- /internal/receiptsmigration/migration.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // todo(corneliusweig) remove migration code with v0.4 16 | package receiptsmigration 17 | 18 | import ( 19 | "os" 20 | 21 | "sigs.k8s.io/krew/internal/environment" 22 | ) 23 | 24 | // Done checks if the krew installation requires a migration. 25 | // It considers a migration necessary when plugins are installed, but no receipts are present. 26 | func Done(newPaths environment.Paths) (bool, error) { 27 | receipts, err := os.ReadDir(newPaths.InstallReceiptsPath()) 28 | if err != nil { 29 | return false, err 30 | } 31 | plugins, err := os.ReadDir(newPaths.BinPath()) 32 | if err != nil { 33 | return false, err 34 | } 35 | 36 | hasInstalledPlugins := len(plugins) > 0 37 | hasNoReceipts := len(receipts) == 0 38 | 39 | return !(hasInstalledPlugins && hasNoReceipts), nil //nolint:staticcheck // de morgan's law is not necessary here 40 | } 41 | -------------------------------------------------------------------------------- /internal/receiptsmigration/migration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // todo(corneliusweig) remove migration code with v0.4 16 | package receiptsmigration 17 | 18 | import ( 19 | "os" 20 | "testing" 21 | 22 | "sigs.k8s.io/krew/internal/environment" 23 | "sigs.k8s.io/krew/internal/testutil" 24 | ) 25 | 26 | func TestIsMigrated(t *testing.T) { 27 | tests := []struct { 28 | name string 29 | filesPresent []string 30 | expected bool 31 | }{ 32 | { 33 | name: "One plugin and receipts", 34 | filesPresent: []string{"bin/foo", "receipts/present"}, 35 | expected: true, 36 | }, 37 | { 38 | name: "When nothing is installed", 39 | expected: true, 40 | }, 41 | { 42 | name: "When a plugin is installed but no receipts", 43 | filesPresent: []string{"bin/foo"}, 44 | expected: false, 45 | }, 46 | { 47 | name: "When no plugin is installed but a receipt exists", 48 | filesPresent: []string{"receipts/present"}, 49 | expected: true, 50 | }, 51 | } 52 | 53 | for _, test := range tests { 54 | t.Run(test.name, func(t *testing.T) { 55 | tmpDir := testutil.NewTempDir(t) 56 | 57 | newPaths := environment.NewPaths(tmpDir.Root()) 58 | 59 | _ = os.MkdirAll(tmpDir.Path("receipts"), os.ModePerm) 60 | _ = os.MkdirAll(tmpDir.Path("bin"), os.ModePerm) 61 | for _, name := range test.filesPresent { 62 | touch(tmpDir, name) 63 | } 64 | 65 | actual, err := Done(newPaths) 66 | if err != nil { 67 | t.Fatal(err) 68 | } 69 | if actual != test.expected { 70 | t.Errorf("Expected %v but found %v", test.expected, actual) 71 | } 72 | }) 73 | } 74 | } 75 | 76 | // touch creates a file without content in the temporary directory. 77 | func touch(td *testutil.TempDir, file string) { 78 | td.Write(file, nil) 79 | } 80 | -------------------------------------------------------------------------------- /internal/testutil/receipt.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package testutil 16 | 17 | import ( 18 | "sigs.k8s.io/krew/pkg/constants" 19 | "sigs.k8s.io/krew/pkg/index" 20 | ) 21 | 22 | type Receipt struct{ v index.Receipt } 23 | 24 | // NewReceipt builds an index.Receipt that is valid. 25 | func NewReceipt() *Receipt { 26 | return &Receipt{v: index.Receipt{ 27 | Status: index.ReceiptStatus{ 28 | Source: index.SourceIndex{ 29 | Name: constants.DefaultIndexName, 30 | }, 31 | }, 32 | }} 33 | } 34 | 35 | func (r *Receipt) WithPlugin(p index.Plugin) *Receipt { r.v.Plugin = p; return r } 36 | func (r *Receipt) WithStatus(s index.ReceiptStatus) *Receipt { r.v.Status = s; return r } 37 | func (r *Receipt) V() index.Receipt { return r.v } 38 | -------------------------------------------------------------------------------- /internal/testutil/tempdir.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package testutil contains test utilities for krew. 16 | package testutil 17 | 18 | import ( 19 | "os" 20 | "path/filepath" 21 | "strings" 22 | "testing" 23 | 24 | "sigs.k8s.io/yaml" 25 | 26 | "sigs.k8s.io/krew/internal/gitutil" 27 | ) 28 | 29 | type TempDir struct { 30 | t *testing.T 31 | root string 32 | } 33 | 34 | // NewTempDir creates a temporary directory which is automatically cleaned up 35 | // when the test exits. 36 | func NewTempDir(t *testing.T) *TempDir { 37 | t.Helper() 38 | root := t.TempDir() 39 | 40 | return &TempDir{t: t, root: root} 41 | } 42 | 43 | // Root returns the root of the temporary directory. 44 | func (td *TempDir) Root() string { 45 | return td.root 46 | } 47 | 48 | // Path returns the path to a file in the temp directory. 49 | // The input file is expected to use '/' as directory separator regardless of the host OS. 50 | func (td *TempDir) Path(file string) string { 51 | if strings.HasPrefix(file, td.root) { 52 | return filepath.FromSlash(file) 53 | } 54 | return filepath.Join(td.root, filepath.FromSlash(file)) 55 | } 56 | 57 | // Write creates a file containing content in the temporary directory. 58 | func (td *TempDir) Write(file string, content []byte) *TempDir { 59 | td.t.Helper() 60 | path := td.Path(file) 61 | if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil { 62 | td.t.Fatalf("cannot create directory %q: %s", filepath.Dir(path), err) 63 | } 64 | if err := os.WriteFile(path, content, os.ModePerm); err != nil { 65 | td.t.Fatalf("cannot write to file %q: %s", path, err) 66 | } 67 | return td 68 | } 69 | 70 | func (td *TempDir) WriteYAML(file string, obj interface{}) *TempDir { 71 | td.t.Helper() 72 | content, err := yaml.Marshal(obj) 73 | if err != nil { 74 | td.t.Fatalf("cannot marshal obj: %s", err) 75 | } 76 | return td.Write(file, content) 77 | } 78 | 79 | func (td *TempDir) InitEmptyGitRepo(path, url string) { 80 | td.t.Helper() 81 | 82 | if err := os.MkdirAll(path, os.ModePerm); err != nil { 83 | td.t.Fatalf("cannot create directory %q: %s", filepath.Dir(path), err) 84 | } 85 | if _, err := gitutil.Exec(path, "init"); err != nil { 86 | td.t.Fatalf("error initializing git repo: %s", err) 87 | } 88 | if _, err := gitutil.Exec(path, "remote", "add", "origin", url); err != nil { 89 | td.t.Fatalf("error setting remote origin: %s", err) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /internal/version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package version contains the version information of the krew binary. 16 | package version 17 | 18 | var ( 19 | // gitCommit contains the git commit identifier. 20 | gitCommit string 21 | 22 | // gitTag contains the git tag or describe output. 23 | gitTag string 24 | ) 25 | 26 | // GitCommit returns the value stamped into the binary at compile-time or a 27 | // default "unknown" value. 28 | func GitCommit() string { 29 | if gitCommit == "" { 30 | return "unknown" 31 | } 32 | return gitCommit 33 | } 34 | 35 | // GitTag returns the value stamped into the binary at compile-time or a 36 | // default "unknown" value. 37 | func GitTag() string { 38 | if gitTag == "" { 39 | return "unknown" 40 | } 41 | return gitTag 42 | } 43 | -------------------------------------------------------------------------------- /internal/version/version_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package version 16 | 17 | import "testing" 18 | 19 | func TestGitCommit(t *testing.T) { 20 | orig := gitCommit 21 | defer func() { gitCommit = orig }() 22 | 23 | gitCommit = "" 24 | if v := GitCommit(); v != "unknown" { 25 | t.Errorf("empty gitCommit, expected=\"unknown\" got=%q", v) 26 | } 27 | 28 | gitCommit = "abcdef" 29 | if v := GitCommit(); v != "abcdef" { 30 | t.Errorf("empty gitCommit, expected=\"abcdef\" got=%q", v) 31 | } 32 | } 33 | 34 | func TestGitTag(t *testing.T) { 35 | orig := gitTag 36 | defer func() { gitTag = orig }() 37 | 38 | gitTag = "" 39 | if v := GitTag(); v != "unknown" { 40 | t.Errorf("empty gitTag, expected=\"unknown\" got=%q", v) 41 | } 42 | 43 | gitTag = "abcdef" 44 | if v := GitTag(); v != "abcdef" { 45 | t.Errorf("empty gitTag, expected=\"abcdef\" got=%q", v) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | GO_VERSION = "1.17" 3 | base = "site/" 4 | publish = "public/" 5 | command = "hugo && cd ./functions && go build -o api ./server" 6 | functions = "functions/" 7 | 8 | [build.environment] 9 | GO_VERSION = "1.17" 10 | HUGO_VERSION = "0.92.2" 11 | 12 | [context.production.environment] 13 | GO_VERSION = "1.17" 14 | HUGO_ENV = "production" 15 | 16 | [context.deploy-preview] 17 | GO_VERSION = "1.17" 18 | command = "hugo --buildFuture -b $DEPLOY_PRIME_URL && cd ./functions && go build -o api ./server" 19 | 20 | [context.branch-deploy] 21 | GO_VERSION = "1.17" 22 | command = "hugo --buildFuture -b $DEPLOY_PRIME_URL && cd ./functions && go build -o api ./server" 23 | -------------------------------------------------------------------------------- /pkg/constants/constants.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package constants 16 | 17 | const ( 18 | CurrentAPIVersion = "krew.googlecontainertools.github.com/v1alpha2" 19 | PluginKind = "Plugin" 20 | ManifestExtension = ".yaml" 21 | KrewPluginName = "krew" // plugin name of krew itself 22 | 23 | // DefaultIndexURI points to the upstream index. 24 | DefaultIndexURI = "https://github.com/kubernetes-sigs/krew-index.git" 25 | // DefaultIndexName is a magic string that's used for a plugin name specified without an index. 26 | DefaultIndexName = "default" 27 | ) 28 | -------------------------------------------------------------------------------- /pkg/index/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package index 16 | 17 | import ( 18 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 19 | ) 20 | 21 | // Plugin describes a plugin manifest file. 22 | type Plugin struct { 23 | metav1.TypeMeta `json:",inline" yaml:",inline"` 24 | metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata"` 25 | 26 | Spec PluginSpec `json:"spec"` 27 | } 28 | 29 | // PluginSpec is the plugin specification. 30 | type PluginSpec struct { 31 | Version string `json:"version,omitempty"` 32 | ShortDescription string `json:"shortDescription,omitempty"` 33 | Description string `json:"description,omitempty"` 34 | Caveats string `json:"caveats,omitempty"` 35 | Homepage string `json:"homepage,omitempty"` 36 | 37 | Platforms []Platform `json:"platforms,omitempty"` 38 | } 39 | 40 | // Platform describes how to perform an installation on a specific platform 41 | // and how to match the target platform (os, arch). 42 | type Platform struct { 43 | URI string `json:"uri,omitempty"` 44 | Sha256 string `json:"sha256,omitempty"` 45 | 46 | Selector *metav1.LabelSelector `json:"selector,omitempty"` 47 | Files []FileOperation `json:"files"` 48 | 49 | // Bin specifies the path to the plugin executable. 50 | // The path is relative to the root of the installation folder. 51 | // The binary will be linked after all FileOperations are executed. 52 | Bin string `json:"bin"` 53 | } 54 | 55 | // FileOperation specifies a file copying operation from plugin archive to the 56 | // installation directory. 57 | type FileOperation struct { 58 | From string `json:"from,omitempty"` 59 | To string `json:"to,omitempty"` 60 | } 61 | 62 | // Receipt describes a plugin receipt file. 63 | type Receipt struct { 64 | Plugin `json:",inline" yaml:",inline"` 65 | 66 | Status ReceiptStatus `json:"status"` 67 | } 68 | 69 | // ReceiptStatus contains information about the installed plugin. 70 | type ReceiptStatus struct { 71 | Source SourceIndex `json:"source"` 72 | } 73 | 74 | // SourceIndex contains information about the index a plugin was installed from. 75 | type SourceIndex struct { 76 | // Name is the configured name of an index a plugin was installed from. 77 | Name string `json:"name"` 78 | } 79 | -------------------------------------------------------------------------------- /pkg/index/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package index 16 | 17 | import ( 18 | "os" 19 | 20 | "sigs.k8s.io/krew/pkg/constants" 21 | ) 22 | 23 | func DefaultIndex() string { 24 | if uri := os.Getenv("KREW_DEFAULT_INDEX_URI"); uri != "" { 25 | return uri 26 | } 27 | return constants.DefaultIndexURI 28 | } 29 | -------------------------------------------------------------------------------- /pkg/index/util_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Kubernetes Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package index 16 | 17 | import ( 18 | "testing" 19 | 20 | "sigs.k8s.io/krew/pkg/constants" 21 | ) 22 | 23 | func TestDefaultIndex(t *testing.T) { 24 | if got := DefaultIndex(); got != constants.DefaultIndexURI { 25 | t.Errorf("DefaultIndex() = %q, want %q", got, constants.DefaultIndexURI) 26 | } 27 | 28 | want := "foo" 29 | t.Setenv("KREW_DEFAULT_INDEX_URI", want) 30 | 31 | if got := DefaultIndex(); got != want { 32 | t.Errorf("DefaultIndex() = %q, want %q", got, want) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /site/.gitignore: -------------------------------------------------------------------------------- 1 | public/ 2 | resources/ 3 | -------------------------------------------------------------------------------- /site/config.yaml: -------------------------------------------------------------------------------- 1 | title: "Krew – kubectl plugin manager" 2 | baseURL: "https://krew.sigs.k8s.io/" 3 | languageCode: "en-us" 4 | enableGitInfo: true 5 | enableRobotsTXT: true 6 | disableKinds: 7 | - taxonomy 8 | - taxonomyTerm 9 | markup: 10 | highlight: 11 | style: dracula 12 | goldmark: 13 | renderer: 14 | unsafe: true # required for dynamic JS content editing 15 | -------------------------------------------------------------------------------- /site/content/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Home 3 | --- 4 | 5 | ## What is Krew? 6 | 7 | Krew is the plugin manager for `kubectl` command-line tool. 8 | 9 | Krew helps you: 10 | - discover [kubectl plugins][kpl], 11 | - install them on your machine, 12 | - and keep the installed plugins up-to-date. 13 | 14 | There are [ kubectl plugins][list] 15 | currently distributed on Krew. 16 | 17 | Krew works across all major platforms, like macOS, Linux and Windows. 18 | 19 | Krew also helps kubectl plugin developers: You can package and distribute your 20 | plugins on multiple platforms easily and makes them discoverable through a 21 | centralized plugin repository with Krew. 22 | 23 | [kpl]: https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/ 24 | [list]: {{< relref "plugins.md" >}} 25 | -------------------------------------------------------------------------------- /site/content/docs/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Documentation 3 | slug: docs 4 | --- 5 | -------------------------------------------------------------------------------- /site/content/docs/developer-guide/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Developer Guide 3 | weight: 300 4 | --- 5 | 6 | This guide is for `kubectl` plugin developers looking to distribute their 7 | plugins with Krew. 8 | -------------------------------------------------------------------------------- /site/content/docs/developer-guide/custom-indexes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hosting Custom Plugin Indexes 3 | slug: custom-indexes 4 | weight: 500 5 | --- 6 | 7 | Krew comes with a plugin index named `default` that points to the 8 | [`krew-index` repository](https://github.com/kubernetes-sigs/krew), which allows 9 | centralized discovery through community curation. 10 | 11 | However, you can host your own plugin indexes (and possibly remove or replace the 12 | `default` index). Hosting your own plugin index is not recommended, unless you 13 | have a use case that specifically calls for it, such as: 14 | 15 | - Your plugin is not accepted to `krew-index` 16 | - You want full control over the distribution lifecycle of your own plugin 17 | - You want to run a _private_ plugin index in your organization (e.g. 18 | for installations on developer machines) 19 | 20 | Hosting your own custom index is simple: 21 | 22 | - Custom index repositories must be `git` repositories. 23 | - Your clients should have read access to the repository. If the repository 24 | is not public, users can still authenticate to it with SSH keys or other 25 | [gitremote-helpers](https://git-scm.com/docs/gitremote-helpers) installed 26 | on the client machine. 27 | - The repository must contain a `plugins/` directory at the root, with at least 28 | one plugin manifest in it. Plugin manifests should be directly in this 29 | directory (not in a subdirectory). 30 | - Ensure plugin manifests are valid YAML and pass Krew manifest validation 31 | (optionally, you can use the 32 | [validate-krew-manifest](https://github.com/kubernetes-sigs/krew/tree/master/cmd/validate-krew-manifest) 33 | tool for static analysis). 34 | 35 | Example plugin repository layout: 36 | 37 | ```text 38 | . 39 | └── plugins/ 40 | ├── plugin-a.yaml 41 | ├── plugin-b.yaml 42 | └── plugin-c.yaml 43 | ``` 44 | -------------------------------------------------------------------------------- /site/content/docs/developer-guide/develop/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Developing kubectl plugins 3 | weight: 100 4 | --- 5 | -------------------------------------------------------------------------------- /site/content/docs/developer-guide/develop/best-practices.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Best practices 3 | slug: best-practices 4 | weight: 300 5 | --- 6 | 7 | {{< toc >}} 8 | 9 | This guide describes practices to use when developing `kubectl` plugins. Before 10 | submitting your plugin to Krew, please review these practices. 11 | 12 | While the Krew project team does not strictly enforce guidelines about how a plugin 13 | works, abiding by these practices can help your plugin work for more users 14 | and behave more predictably. 15 | 16 | ## Choosing a language 17 | 18 | Most `kubectl` plugins are written in Go or as bash scripts. 19 | 20 | If you are planning to write a plugin with Go, check out: 21 | 22 | - [client-go]: a Go SDK to work with Kubernetes API and kubeconfig files 23 | - [cli-runtime]: a set of packages to share code with `kubectl` for printing output or [sharing command-line options][cli-opts] 24 | - [sample-cli-plugin]: an example plugin implementation in Go 25 | 26 | ## Consistency with kubectl {#kubectl-options} 27 | 28 | Krew does not impose any rules in terms of the shape of your plugin. 29 | 30 | However, if you support the common command-line options with `kubectl`, you can make your 31 | users’ lives easier. 32 | 33 | For example, it is recommended that you support the following command-line flags used by 34 | `kubectl`: 35 | 36 | - `-h`/`--help` 37 | - `-n`/`--namespace` 38 | - `-A`/`--all-namespaces` 39 | 40 | Furthermore, by using the [genericclioptions][cli-opts] package (Go), you can 41 | support the global command-line flags listed in `kubectl options` (e.g. 42 | `--kubeconfig`, `--context` and many others) in your plugins. 43 | 44 | ## Import authentication plugins (Go) {#auth-plugins} 45 | 46 | By default, plugins that use [client-go] 47 | cannot authenticate to Kubernetes clusters on many cloud providers. To address 48 | this, include the following import in your plugin: 49 | 50 | ```go 51 | import _ "k8s.io/client-go/plugin/pkg/client/auth" 52 | ``` 53 | 54 | [cli-runtime]: https://github.com/kubernetes/cli-runtime/ 55 | [client-go]: https://godoc.org/k8s.io/client-go 56 | [cli-opts]: https://godoc.org/k8s.io/cli-runtime/pkg/genericclioptions 57 | [sample-cli-plugin]: https://github.com/kubernetes/sample-cli-plugin 58 | 59 | ## Revise usage and help messages with the `kubectl` prefix {#help-messages} 60 | 61 | Many users will discover how to use your plugin by calling it without any arguments (which 62 | might trigger a help message), or with `-h`/`--help` arguments. 63 | 64 | Therefore, it is helpful to change your usage strings to show the `kubectl ` prefix before the plugin 65 | name. For example: 66 | 67 | ```text 68 | Usage: 69 | kubectl popeye [flags] 70 | ``` 71 | 72 | To determine if your executable is running as a `kubectl` plugin, you can look 73 | at `argv[0]`, which will include the `kubectl-` prefix: 74 | 75 | - **Go:** 76 | 77 | ```go 78 | if strings.HasPrefix(filepath.Base(os.Args[0]), "kubectl-") { } 79 | ``` 80 | 81 | - **Bash:** 82 | 83 | ```bash 84 | if [[ "$(basename "$0")" == kubectl-* ]]; then # invoked as plugin 85 | # ... 86 | fi 87 | ``` 88 | -------------------------------------------------------------------------------- /site/content/docs/developer-guide/develop/naming-guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Plugin Naming Guide 3 | slug: naming-guide 4 | weight: 200 5 | --- 6 | 7 | This document describes guidelines for naming your kubectl plugins. 8 | 9 | These guidelines are used for reviewing the plugins [submitted to Krew]({{}}). 11 | 12 | ##### _Use lowercase and hyphens_ 13 | 14 | Plugin names must be all lowercase and separate words with hyphens. 15 | **Don't** use camelCase, PascalCase, or snake_case; use 16 | [kebab-case](http://wiki.c2.com/?KebabCase). 17 | 18 | - **NO:** `kubectl OpenSvc` 19 | - **YES:** `kubectl open-svc` 20 | 21 | ##### _Be specific_ 22 | 23 | Plugin names should not be verbs or nouns that are generic, already overloaded, or 24 | likely to be used for broader purposes by another plugin. 25 | 26 | - **NO:** `kubectl login` (Too broad) 27 | - **YES:** `kubectl gke-login` 28 | 29 | Also: 30 | 31 | - **NO:** `kubectl ui` (Should be used only for Kubernetes Dashboard) 32 | - **YES:** `kubectl gke-ui` 33 | 34 | ##### _Be unique_ 35 | 36 | Find a unique name for your plugin that differentiates it from other 37 | plugins that perform a similar function. 38 | 39 | - **NO:** `kubectl view-logs` (Unclear how it is different from the builtin 40 | "logs" command, or many other tools for viewing logs) 41 | - **YES:** `kubectl tailer` (Unique name, points to the underlying) 42 | tool name. 43 | 44 | ##### _Use Verbs and Resource Types_ 45 | 46 | If the name does not make it clear (a) what verb the plugin is doing on a 47 | resource, or (b) what kind of resource it's doing the action on, consider 48 | clarifying unless it is obvious. 49 | 50 | - **NO:** `kubectl service` (Unclear what this plugin is doing with) 51 | service. 52 | - **NO:** `kubectl open` (Unclear what the plugin is opening) 53 | - **YES:** `kubectl open-svc` (It is clear the plugin will open a service) 54 | 55 | ##### _Prefix Vendor Identifiers_ 56 | 57 | Use vendor-specific strings as prefix, separated with a dash. This makes it 58 | easier to search/group plugins that are about a specific vendor. 59 | 60 | - **NO:** `kubectl ui-gke` (Makes it harder to search or locate in a 61 | plugin list) 62 | - **YES:** `kubectl gke-ui` (Will show up together with other gke-* plugins) 63 | 64 | ##### _Avoid repeating kube[rnetes]_ 65 | 66 | Plugin names should not include "kube-" or "kubernetes-" prefixes. 67 | 68 | - **NO:** `kubectl kube-node-admin` ("kubectl " already has "kube" in it) 69 | - **YES:** `kubectl node-admin` 70 | 71 | While it is not recommended to include "kube*" in the plugin command name it 72 | is recommended that the code repository starts with "kubectl-" so plugin 73 | source code can be found outside of krew and the intended use is clear. 74 | 75 | ##### _Avoid Resource Acronyms and Abbreviations_ 76 | 77 | Using kubectl acronyms for API resources (e.g. svc, ing, deploy, cm) reduces 78 | the readability and discoverability of a plugin, which is more important 79 | than the few keystrokes saved. 80 | 81 | - **NO:** `kubectl new-ing` (Unclear that the plugin is for Ingress) 82 | - **YES:** `kubectl debug-ingress` 83 | 84 | Note: If you have suggestions for improving this guide, open an issue or send a 85 | pull request, as this is a topic under active development. 86 | -------------------------------------------------------------------------------- /site/content/docs/developer-guide/develop/plugin-development.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction to plugin development 3 | weight: 100 4 | --- 5 | 6 | If you are looking to start developing plugins for `kubectl`, read the 7 | [Kubernetes 8 | documentation](https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/) 9 | on this topic. 10 | 11 | To summarize the documentation, the procedure is to: 12 | 13 | - Create an executable binary, named `kubectl-foo` (for example) to have a plugin that can be 14 | invoked as `kubectl foo`. 15 | - Place the executable in a directory that is listed in the user’s `PATH` environment 16 | variable. (You don't have to do this for plugins distributed with Krew). 17 | - You can't overwrite a built-in `kubectl` command with a plugin. 18 | 19 | > **Note:** If you are writing a plugin in Go, consider using the [cli-runtime] project, 20 | > which is designed to provide the same command-line arguments, kubeconfig 21 | > parser, Kubernetes API REST client, and printing logic. Look at 22 | > [sample-cli-plugin] for an example of a kubectl plugin. 23 | > 24 | > Also, see the unofficial [GitHub template 25 | > repo](https://github.com/replicatedhq/krew-plugin-template) for a Krew plugin 26 | > in Go that implements some best practices covered later in this guide, and helps 27 | > you automate releases using GoReleaser to create a release when a tag is pushed. 28 | 29 | [cli-runtime]: https://github.com/kubernetes/cli-runtime/ 30 | [sample-cli-plugin]: https://github.com/kubernetes/sample-cli-plugin 31 | 32 | When developing your own plugins, make sure you check out: 33 | 34 | - [Plugin naming guide]({{}}) to choose a good name 35 | for your plugin 36 | - [Plugin development best practices]({{}}) guide 37 | for a brief checklist of what we're looking for in the submitted plugins. 38 | 39 | After you develop a plugin with a good name following the best practices, you 40 | can [develop a Krew plugin manifest]({{}}) and 41 | [submit your plugin to Krew]({{}}). 42 | -------------------------------------------------------------------------------- /site/content/docs/developer-guide/distributing-with-krew.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Distributing plugins on Krew 3 | weight: 110 4 | --- 5 | 6 | ## Why use Krew for distribution? {#why} 7 | 8 | On the surface, installing a kubectl plugin seems simple enough -- all you need to do is to place 9 | an executable in the user’s `PATH` prefixed with `kubectl-` -- that you may be 10 | considering some other alternatives to Krew, such as: 11 | 12 | - Having the user manually download the plugin executable and move it to some directory in 13 | the `PATH` 14 | - Distributing the plugin executable using an OS package manager, like Homebrew 15 | (macOS), apt/yum (Linux), or Chocolatey (Windows) 16 | - Distributing the plugin executable using a language package manager (e.g. npm or 17 | go get) 18 | 19 | While these approaches are not necessarily unworkable, potential drawbacks to consider include: 20 | 21 | - How to get updates to users (in the case of manual installation) 22 | - How to package a plugin for multiple platforms (macOS, Linux, and Windows) 23 | - How to ensure your users have the appropriate language package manager (go, npm) 24 | - How to handle a change to the implementation language (e.g. a move from npm to another package manager) 25 | 26 | Krew solves these problems cleanly for all kubectl plugins, since it's designed 27 | **specifically to address these shortcomings**. With Krew, after you write a plugin 28 | manifest once your plugin can be installed on all platforms 29 | without having to deal with their package managers. 30 | 31 | ## Steps to get started 32 | 33 | Once you [develop]({{< ref "develop/plugin-development.md" >}}) a `kubectl` 34 | plugin, follow these steps to distribute your plugin on Krew: 35 | 36 | 1. Package your plugin into an archive file (`.tar.gz` or `.zip`). 37 | 1. Make the archive file publicly available (e.g. as GitHub release files). 38 | 1. Write a [Krew plugin manifest]({{< ref "plugin-manifest.md" >}}) file. 39 | 1. [Submit your plugin to krew-index]({{< ref "release/../release/submitting-to-krew.md" >}}). 40 | -------------------------------------------------------------------------------- /site/content/docs/developer-guide/example-plugin-manifests.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Example plugin manifests 3 | slug: example-manifests 4 | weight: 200 5 | --- 6 | 7 | Learning how to [write plugin manifests]({{< ref "plugin-manifest.md" >}}) 8 | can be a time-consuming task. 9 | 10 | The Krew team encourages you to copy and adapt plugin manifests of [existing 11 | plugins][list]. Since these are already reviewed and approved, your plugin is 12 | likely to be accepted more quickly. 13 | 14 | * **Go:** 15 | - [tree](https://github.com/kubernetes-sigs/krew-index/blob/master/plugins/tree.yaml): 16 | supports Windows/Linux/macOS, per-OS builds, extracts all files from the 17 | archive 18 | - [sort-manifests](https://github.com/kubernetes-sigs/krew-index/blob/master/plugins/sort-manifests.yaml): 19 | Linux/macOS only, extracting specific files 20 | 21 | * **Bash:** 22 | - [ctx](https://github.com/kubernetes-sigs/krew-index/blob/master/plugins/ctx.yaml): 23 | Linux/macOS only, downloads GitHub tag tarball, extracts using wildcards 24 | 25 | * **Rust:** 26 | - [view-allocations](https://github.com/kubernetes-sigs/krew-index/blob/master/plugins/view-allocations.yaml): 27 | Linux/macOS only, implicitly extracts all files from the archive 28 | 29 | [list]: https://github.com/kubernetes-sigs/krew-index/tree/master/plugins 30 | -------------------------------------------------------------------------------- /site/content/docs/developer-guide/installing-locally.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Testing plugin installation locally 3 | slug: testing-locally 4 | weight: 300 5 | --- 6 | 7 | After you have written your [plugin manifest]({{< ref "plugin-manifest.md" >}}) 8 | and archived your plugin into a `.zip` or `.tar.gz` file, you can verify that 9 | your plugin installs correctly with Krew by running: 10 | 11 | ```sh 12 | {{}}kubectl krew install --manifest=foo.yaml --archive=foo.tar.gz 13 | ``` 14 | 15 | - The `--manifest` flag specifies a custom manifest rather than using 16 | the default [krew index][index] 17 | - `--archive` overrides the download `uri:` specified in the plugin manifest and 18 | uses a local `.zip` or `.tar.gz` file instead. 19 | 20 | If the installation **fails**, run the command again with `-v=4` flag to see the 21 | verbose logs and examine what went wrong. 22 | 23 | If the installation **succeeds**, you should now be able to run your plugin. 24 | 25 | If you made your archive file available for download on the Internet, run the 26 | same command without the `--archive` option and actually test downloading the 27 | file from the specified `uri` and validate its `sha256` sum is correct. 28 | 29 | After you have tested your plugin installation, uninstall it with `kubectl krew uninstall foo`. 30 | 31 | ### Testing other platforms 32 | 33 | If you need to test other `platforms` definitions that don't match your current machine, 34 | you can use the `KREW_OS` and `KREW_ARCH` environment variables to override the 35 | OS and architecture that Krew thinks it's running on. 36 | 37 | For example, if you're on a Linux machine, you can test Windows installation 38 | with: 39 | 40 | ```sh 41 | {{}}KREW_OS=windows KREW_ARCH=amd64 kubectl krew install --manifest=[...] 42 | ``` 43 | 44 | [index]: https://github.com/kubernetes-sigs/krew-index 45 | -------------------------------------------------------------------------------- /site/content/docs/developer-guide/plugin-stats.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Plugin usage analytics 3 | slug: plugin-stats 4 | weight: 600 5 | --- 6 | 7 | Krew does not track user behavior. However, for plugins that distribute their 8 | packages as assets on [GitHub 9 | releases], 10 | Krew offers some statistics to track download analytics for your plugins over 11 | time. 12 | 13 | To see your plugin’s download statistics over time: 14 | 15 | 1. Visit the [krew-index-tracker] dashboard. 16 | 2. Choose your plugin from the dropdown and browse the data for your plugin. 17 | 18 | This data is obtained by scraping the downloads count of your plugin assets via 19 | the [GitHub API] regularly. Since Krew does not track its users, this data: 20 | 21 | - does not reflect active installations 22 | - does not distinguish between installs, reinstalls, and upgrades 23 | - is purely a tracking of download counts of your release assets over time 24 | 25 | > **Note:** The Krew plugin stats dashboard is provided as a best effort by Krew 26 | > maintainers to measure the success of Krew and its plugins. We cannot guarantee 27 | > its availability and accuracy. 28 | > 29 | > The scraping code can be found 30 | > [here](https://github.com/predatorray/krew-index-tracker). 31 | 32 | [GitHub releases]: https://help.github.com/en/github/administering-a-repository/managing-releases-in-a-repository 33 | [krew-index-tracker]: https://predatorray.github.io/krew-index-tracker/ 34 | [GitHub API]: https://developer.github.com/v3/repos/releases/#list-assets-for-a-release 35 | -------------------------------------------------------------------------------- /site/content/docs/developer-guide/release/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Releasing 3 | weight: 400 4 | --- 5 | -------------------------------------------------------------------------------- /site/content/docs/developer-guide/release/plugin-updates.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Releasing plugin updates 3 | slug: updating-plugins 4 | weight: 200 5 | --- 6 | 7 | When you have a newer version of your plugin, you can update your plugin 8 | manifest at the [krew-index] repository to distribute this new version to your users. 9 | 10 | This manual operation looks like: 11 | 12 | 1. Update the `version`, `uri`, and `sha256` fields of the plugin manifest file. 13 | 1. [Test plugin installation locally]({{< ref "../installing-locally.md" >}}) 14 | 1. Make a pull request to [krew-index] to update the plugin manifest file. 15 | 16 | > **Note:** Ideally, the specified `version:` field should match the release tag 17 | of the plugin. This helps users and maintainers to easily identify which 18 | version of the plugin they have installed. 19 | 20 | If you only change the `version`, `uri` and `sha256` fields of your plugin manifest, 21 | your pull request will be automatically approved, tested, and merged ([see an 22 | example](https://github.com/kubernetes-sigs/krew-index/pull/508)). 23 | 24 | You can [**automate releasing plugin updates**]({{< ref 25 | "release-automation.md" >}}) if you're publishing your plugins on GitHub. 26 | 27 | [krew-index]: https://sigs.k8s.io/krew-index 28 | -------------------------------------------------------------------------------- /site/content/docs/developer-guide/release/release-automation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Automating plugin updates 3 | slug: automating-updates 4 | weight: 300 5 | --- 6 | 7 | Normally, [releasing a new version]({{< ref "plugin-updates.md" >}}) requires manual 8 | work and creating a pull request every time you have a new version. 9 | 10 | However, you can use a **Github Action** to publish new releases of your Krew plugin. 11 | 12 | Specifically, `krew-release-bot` is a Github Action to automatically bump the version in 13 | `krew-index` repo every time you push a new git tag to your repository: 14 | 15 | - It requires no secrets (e.g. `GITHUB_TOKEN`) to operate. 16 | - It creates your plugin manifest dynamically from a template you write. 17 | - It makes pull requests on your behalf to the `krew-index` repository. 18 | 19 | Refer to the [krew-release-bot](https://github.com/rajatjindal/krew-release-bot) 20 | documentation for details. 21 | 22 | The Krew team **strongly recommends** automating your plugin's releases. Trivial 23 | version bumps are automatically tested and merged without human intervention, 24 | usually under five minutes ([see an example of bots talking to each 25 | other](https://github.com/kubernetes-sigs/krew-index/pull/490)). 26 | -------------------------------------------------------------------------------- /site/content/docs/developer-guide/release/submitting-to-krew.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Submitting plugins to Krew 3 | slug: new-plugin 4 | weight: 100 5 | --- 6 | 7 | Krew maintains a centralized plugin index at the [krew-index] repository. 8 | This repository is cloned on each Krew user’s machine to help them find 9 | existing plugins. 10 | 11 | ## Pre-submit checklist 12 | 13 | 1. Review the [Krew plugin naming guide]({{< ref 14 | "../develop/naming-guide.md" >}}). 15 | 1. Read the [plugin development best practices]({{< ref 16 | "../develop/best-practices.md" >}}). 17 | 1. Make sure your plugin’s source code is available as open source. 18 | 1. Adopt an open source license, and add it to your plugin archive file. 19 | 1. Make sure to extract the LICENSE file during the plugin installation. 20 | 1. Tag a git release with a [semantic 21 | version](https://semver.org) (e.g. `v1.0.0`). 22 | 1. [Test your plugin installation locally]({{< ref "../installing-locally.md" >}}). 23 | 24 | ## Submitting a plugin to krew-index 25 | 26 | Once you've run through the checklist above, create a **pull request** to the 27 | [krew-index] repository with your plugin manifest file (e.g. `example.yaml`) to 28 | the `plugins/` directory. 29 | 30 | After your pull request is merged, users will be able to find and [install]({{< ref 31 | "../../user-guide/install.md" >}}) your plugin through Krew. 32 | 33 | [krew-index]: https://sigs.k8s.io/krew-index 34 | -------------------------------------------------------------------------------- /site/content/docs/user-guide/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: User Guide 3 | weight: 200 4 | --- 5 | 6 | Learn how to use Krew to its full extent. 7 | -------------------------------------------------------------------------------- /site/content/docs/user-guide/configuration.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Advanced Configuration 3 | slug: advanced-configuration 4 | weight: 900 5 | --- 6 | 7 | {{< toc >}} 8 | 9 | ## Customize installation directory {#custom-install-dir} 10 | 11 | By default, Krew installs itself and plugins to `$HOME/.krew`. This means 12 | Krew itself and the installed plugins will be visible only to the user who 13 | installed it. 14 | 15 | To customize this installation path, set the `KREW_ROOT` environment variable 16 | while [installing Krew]({{< relref "setup/install.md" >}}). After Krew is 17 | installed, you still need to set `KREW_ROOT` in your environment for Krew 18 | to be able to find its installation directory. 19 | 20 | For example, add this to your `~/.bashrc` or `~/.zshrc` file: 21 | 22 | ```shell 23 | export KREW_ROOT="/usr/local/krew" 24 | ``` 25 | 26 | Note that you still need to add `$KREW_ROOT/bin` to your `PATH` variable 27 | for `kubectl` to be able to find installed plugins. 28 | 29 | ## Use a different default index {#custom-default-index} 30 | 31 | When Krew is installed, it automatically initializes an index named `default` 32 | pointing to the [krew-index][ki] repository. You can force Krew to use a 33 | different repository by setting `KREW_DEFAULT_INDEX_URI` before running the 34 | [installation instructions]({{}}) or after [removing the 35 | default index]({{}}). 36 | `KREW_DEFAULT_INDEX_URI` must point to a git repository URI that uses a valid 37 | git remote protocol. 38 | 39 | To use a different default index, set the `KREW_DEFAULT_INDEX_URI` environment 40 | variable in your `~/.bashrc`, `~/.bash_profile`, or `~/.zshrc`: 41 | 42 | ```shell 43 | export KREW_DEFAULT_INDEX_URI='git@github.com:foo/custom-index.git' 44 | ``` 45 | 46 | ## Configure network proxy {#custom-network-proxy} 47 | 48 | If you want to use Krew with an HTTP proxy, you can configure environment 49 | variables HTTP_PROXY, HTTPS_PROXY and NO_PROXY. Behavior of these 50 | environment variables are explained [here][httpproxy]. 51 | 52 | ```shell 53 | export HTTP_PROXY="proxy-ip:port" 54 | export HTTPS_PROXY="proxy-ip:port" 55 | export NO_PROXY="ip1,ip2:port2,.example.com" 56 | ``` 57 | 58 | [ki]: https://github.com/kubernetes-sigs/krew-index 59 | [httpproxy]: https://pkg.go.dev/golang.org/x/net/http/httpproxy#Config 60 | -------------------------------------------------------------------------------- /site/content/docs/user-guide/install.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installing Plugins 3 | slug: installing-plugins 4 | weight: 400 5 | --- 6 | 7 | Plugins can be installed with the `kubectl krew install` command: 8 | 9 | ```text 10 | {{}}kubectl krew install ca-cert 11 | {{}}Installing plugin: ca-cert 12 | Installed plugin: ca-cert{{}} 13 | ``` 14 | 15 | This command downloads the plugin and verifies the integrity of the downloaded 16 | file. 17 | 18 | After installing a plugin, you can start using it by running `kubectl `: 19 | 20 | ```sh 21 | {{}}kubectl ca-cert 22 | ``` 23 | 24 | 25 | -------------------------------------------------------------------------------- /site/content/docs/user-guide/list.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Listing Installed Plugins 3 | slug: list 4 | weight: 500 5 | --- 6 | 7 | To list all plugins installed via Krew, run: 8 | 9 | ```sh 10 | {{}}kubectl krew list 11 | ``` 12 | 13 | You can list all installed `kubectl` plugins (including those not installed via 14 | Krew) using: 15 | 16 | ```sh 17 | {{}}kubectl plugin list 18 | ``` 19 | 20 | ### Backing up plugin list 21 | 22 | When you pipe or redirect the `kubectl krew list` command’s output to another file 23 | or command, it will return a list of plugin names installed, e.g.: 24 | 25 | ```sh 26 | {{}}kubectl krew list | tee backup.txt 27 | access-matrix 28 | whoami 29 | tree 30 | ``` 31 | 32 | You can then [install]({{}}) the list of plugins from the file 33 | (on another machine, for example) by feeding the file to the `install` command over 34 | standard input (stdin): 35 | 36 | ```sh 37 | {{}}kubectl krew install < backup.txt 38 | ``` 39 | -------------------------------------------------------------------------------- /site/content/docs/user-guide/quickstart.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Quickstart 3 | slug: quickstart 4 | weight: 100 5 | --- 6 | 7 | Krew helps you discover and install [`kubectl` plugins][kpl] on your machine. 8 | 9 | You can install and use [a wide variety of][list] `kubectl` plugins to enhance 10 | your Kubernetes experience. 11 | 12 | Let's get started: 13 | 14 | 1. [Install and set up]({{}}) Krew on your machine. 15 | 16 | 1. Download the plugin list: 17 | 18 | ```sh 19 | {{}}kubectl krew update 20 | ``` 21 | 22 | 1. Discover plugins available on Krew: 23 | 24 | ```sh 25 | {{}}kubectl krew search 26 | {{}}NAME DESCRIPTION INSTALLED 27 | access-matrix Show an RBAC access matrix for server resources no 28 | advise-psp Suggests PodSecurityPolicies for cluster. no 29 | auth-proxy Authentication proxy to a pod or service no 30 | [...]{{}} 31 | ``` 32 | 33 | 1. Choose a plugin from the list and install it: 34 | 35 | ```sh 36 | {{}}kubectl krew install access-matrix 37 | ``` 38 | 39 | 1. Use the installed plugin: 40 | 41 | ```sh 42 | {{}}kubectl access-matrix 43 | ``` 44 | 45 | 1. Keep your plugins up-to-date: 46 | 47 | ```sh 48 | {{}}kubectl krew upgrade 49 | ``` 50 | 51 | 1. Uninstall a plugin you no longer use: 52 | 53 | ```sh 54 | {{}}kubectl krew uninstall access-matrix 55 | ``` 56 | 57 | This is practically all you need to know to start using Krew. 58 | 59 | [kpl]: https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/ 60 | [list]: {{< relref "plugins.md" >}} 61 | -------------------------------------------------------------------------------- /site/content/docs/user-guide/search.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Discovering Plugins 3 | slug: discovering-plugins 4 | weight: 300 5 | --- 6 | 7 | You can find a list of `kubectl` plugins distributed via Krew [here][list]. 8 | However, you can find plugins using the command line as well. 9 | 10 | ## Search available plugins 11 | 12 | First, refresh your local copy of the plugin index: 13 | 14 | ```sh 15 | {{}}kubectl krew update 16 | ``` 17 | 18 | To list all plugins available, run: 19 | 20 | ```text 21 | {{}}kubectl krew search 22 | {{}} 23 | NAME DESCRIPTION INSTALLED 24 | access-matrix Show an RBAC access matrix for server resources no 25 | advise-psp Suggests PodSecurityPolicies for cluster. no 26 | auth-proxy Authentication proxy to a pod or service no 27 | bulk-action Do bulk actions on Kubernetes resources. no 28 | ca-cert Print the PEM CA certificate of the current clu... no 29 | ...{{}} 30 | ``` 31 | 32 | You can specify search keywords as arguments: 33 | 34 | ```sh 35 | {{}}kubectl krew search pod 36 | {{}} 37 | NAME DESCRIPTION INSTALLED 38 | evict-pod Evicts the given pod no 39 | pod-dive Shows a pod's workload tree and info inside a node no 40 | pod-logs Display a list of pods to get logs from no 41 | pod-shell Display a list of pods to execute a shell in no 42 | rm-standalone-pods Remove all pods without owner references no 43 | support-bundle Creates support bundles for off-cluster analysis no{{}} 44 | ``` 45 | 46 | ## Learn more about a plugin 47 | 48 | To get more information on a plugin, run `kubectl krew info `: 49 | 50 | ```sh 51 | {{}} kubectl krew info tree 52 | {{}} 53 | NAME: tree 54 | VERSION: v0.4.0 55 | DESCRIPTION: 56 | This plugin shows sub-resources of a specified Kubernetes API object in a 57 | tree view in the command-line. The parent-child relationship is discovered 58 | using ownerReferences on the child object. 59 | ...{{}} 60 | ``` 61 | 62 | [list]: {{< relref "plugins.md" >}} 63 | -------------------------------------------------------------------------------- /site/content/docs/user-guide/setup/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Setup 3 | weight: 200 4 | --- 5 | -------------------------------------------------------------------------------- /site/content/docs/user-guide/setup/install.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installing 3 | slug: install 4 | weight: 200 5 | --- 6 | 7 | Krew itself is a `kubectl` plugin that is installed and updated via Krew (yes, 8 | Krew self-hosts). 9 | 10 | > ⚠️ **Warning:** krew is only compatible with `kubectl` v1.12 or later. 11 | 12 | - **macOS/Linux**: [bash/zsh](#bash), [fish](#fish) 13 | - **[Windows](#windows)** 14 | 15 | ## macOS/Linux {#posix} 16 | 17 | #### Bash or ZSH shells {#bash} 18 | 19 | 1. Make sure that `git` is installed. 20 | 1. Run this command to download and install `krew`: 21 | 22 | ```sh 23 | ( 24 | set -x; cd "$(mktemp -d)" && 25 | OS="$(uname | tr '[:upper:]' '[:lower:]')" && 26 | ARCH="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/\(arm\)\(64\)\?.*/\1\2/' -e 's/aarch64$/arm64/')" && 27 | KREW="krew-${OS}_${ARCH}" && 28 | curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/${KREW}.tar.gz" && 29 | tar zxvf "${KREW}.tar.gz" && 30 | ./"${KREW}" install krew 31 | ) 32 | ``` 33 | 34 | 1. Add the `$HOME/.krew/bin` directory to your PATH environment variable. To do 35 | this, update your `.bashrc` or `.zshrc` file and append the following line: 36 | 37 | ```sh 38 | export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH" 39 | ``` 40 | 41 | and restart your shell. 42 | 43 | 1. Run `kubectl krew` to check the installation. 44 | 45 | #### Fish shell {#fish} 46 | 47 | 1. Make sure that `git` is installed. 48 | 1. Run this command in your terminal to download and install `krew`: 49 | 50 | ```fish 51 | begin 52 | set -x; set temp_dir (mktemp -d); cd "$temp_dir" && 53 | set OS (uname | tr '[:upper:]' '[:lower:]') && 54 | set ARCH (uname -m | sed -e 's/x86_64/amd64/' -e 's/\(arm\)\(64\)\?.*/\1\2/' -e 's/aarch64$/arm64/') && 55 | set KREW krew-$OS"_"$ARCH && 56 | curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/$KREW.tar.gz" && 57 | tar zxvf $KREW.tar.gz && 58 | ./$KREW install krew && 59 | set -e KREW temp_dir && 60 | cd - 61 | end 62 | ``` 63 | 64 | 1. Add the `$HOME/.krew/bin` directory to your PATH environment variable. To do 65 | this, update your `config.fish` file and append the following line: 66 | 67 | ```fish 68 | set -gx PATH $PATH $HOME/.krew/bin 69 | ``` 70 | 71 | and restart your shell. 72 | 73 | 1. Run `kubectl krew` to check the installation. 74 | 75 | ## Windows {#windows} 76 | 77 | 1. Make sure `git` is installed. 78 | 1. Download `krew.exe` from the [Releases][releases] page to a directory. 79 | 1. Launch a command prompt (`cmd.exe`) with administrator privileges (since the installation requires use of symbolic links) and navigate to that directory. 80 | 1. Run the following command to install krew: 81 | 82 | ```sh 83 | .\krew install krew 84 | ``` 85 | 86 | 1. Add the `%USERPROFILE%\.krew\bin` directory to your `PATH` environment variable 87 | ([how?](https://java.com/en/download/help/path.xml)) 88 | 89 | 1. Launch a new command-line window. 90 | 1. Run `kubectl krew` to check the installation. 91 | 92 | [releases]: https://github.com/kubernetes-sigs/krew/releases 93 | 94 | ## Other package managers 95 | 96 | You can alternatively install Krew via some OS-package managers like Homebrew 97 | (macOS). 98 | 99 | However, that method is not actively supported at this time. 100 | -------------------------------------------------------------------------------- /site/content/docs/user-guide/setup/uninstall.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Uninstalling Krew 3 | slug: uninstall 4 | weight: 200 5 | --- 6 | 7 | To remove Krew and its associated plugins altogether, run this command on your Linux or macOS machine: 8 | 9 | ```sh 10 | {{}}rm -rf -- ~/.krew 11 | ``` 12 | 13 | On Windows, remove the `%USERPROFILE%\.krew` directory. 14 | 15 | > **Note:** If you installed Krew with another package manager (e.g. Homebrew), 16 | > follow their instructions for uninstalling in addition to the instructions above. 17 | -------------------------------------------------------------------------------- /site/content/docs/user-guide/setup/updates.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Update checks 3 | slug: update 4 | weight: 300 5 | --- 6 | 7 | Krew will occasionally check if a new version is available and remind you to 8 | upgrade to a newer version. 9 | 10 | This is done by calling the GitHub API, and the process does not collect any data from your 11 | machine. 12 | 13 | If you want to disable the update checks, set the `KREW_NO_UPGRADE_CHECK` 14 | environment variable. To permanently disable this feature, add the following to your 15 | `~/.bashrc`, `~/.bash_profile`, or `~/.zshrc`: 16 | 17 | ```shell 18 | export KREW_NO_UPGRADE_CHECK=1 19 | ``` 20 | -------------------------------------------------------------------------------- /site/content/docs/user-guide/uninstall.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Uninstalling Plugins 3 | slug: uninstalling-plugins 4 | weight: 700 5 | --- 6 | 7 | When you don't need a plugin anymore you can uninstall it with: 8 | 9 | ```sh 10 | {{}}kubectl krew uninstall 11 | ``` 12 | -------------------------------------------------------------------------------- /site/content/docs/user-guide/upgrade.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Upgrading Plugins 3 | slug: upgrading-plugins 4 | weight: 600 5 | --- 6 | 7 | To upgrade all plugins that you have installed to their latest versions, run: 8 | 9 | ```sh 10 | {{}}kubectl krew upgrade 11 | ``` 12 | 13 | Since Krew itself is a plugin also managed through Krew, running the upgrade 14 | command will also upgrade your `krew` setup to the latest version. 15 | 16 | To upgrade only certain plugins, you can explicitly specify their names: 17 | 18 | ```sh 19 | {{}}kubectl krew upgrade 20 | ``` 21 | -------------------------------------------------------------------------------- /site/content/docs/user-guide/using-custom-indexes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Using Custom Plugin Indexes 3 | slug: custom-indexes 4 | weight: 800 5 | --- 6 | 7 | Plugin indexes contain plugin manifests, which are documents that describe the 8 | installation procedure for a plugin. For discovery purposes, Krew comes with a 9 | `default` plugin index, plugins hosted in the [`krew-index` repository][ki]. 10 | 11 | However, some plugin authors may choose to host their own indexes that contain 12 | their curation of kubectl plugins. These are called "custom plugin indexes". 13 | 14 | ## Adding a custom index 15 | 16 | A custom plugin index can be added with the `kubectl krew index add` command: 17 | 18 | ```sh 19 | {{}}kubectl krew index add foo https://github.com/foo/custom-index.git 20 | ``` 21 | 22 | The URI you use can be any [git remote](https://git-scm.com/docs/git-remote) 23 | (e.g., `git@github.com:foo/custom-index.git`). 24 | 25 | ## Removing a custom index 26 | 27 | You can remove a custom plugin index by passing the name it was added with to 28 | the `index remove` command: 29 | 30 | ```sh 31 | {{}}kubectl krew index remove foo 32 | ``` 33 | 34 | ## Listing indexes 35 | 36 | To see what indexes you have added run the `index list` command: 37 | 38 | ```sh 39 | {{}}kubectl krew index list 40 | {{}}INDEX URL 41 | default https://github.com/kubernetes-sigs/krew-index.git 42 | foo https://github.com/foo/custom-index.git{{}} 43 | ``` 44 | 45 | ## Installing plugins from custom indexes 46 | 47 | Commands for managing plugins (e.g. `install`, `upgrade`) work with custom 48 | indexes as well. 49 | 50 | By default, Krew prefixes plugins with a `default/` prefix. So, to install 51 | a plugin from a custom index, you need to specify it in the format 52 | `INDEX_NAME/PLUGIN_NAME`. 53 | 54 | For example, to install a plugin named `bar` from custom index `foo`: 55 | 56 | ```sh 57 | {{}}kubectl krew install foo/bar 58 | ``` 59 | 60 | Similarly: 61 | 62 | - To list all plugins (including the ones from custom indexes), run: 63 | 64 | ```sh 65 | {{}}kubectl krew search 66 | ``` 67 | 68 | - To remove a plugin, you don't need to specify its index: 69 | 70 | ```sh 71 | {{}}kubectl krew uninstall PLUGIN_NAME 72 | ``` 73 | 74 | - To get information about a plugin from a custom index: 75 | 76 | ```sh 77 | {{}}kubectl krew info INDEX_NAME/PLUGIN_NAME 78 | ``` 79 | 80 | 81 | > **Note:** If two indexes each include a plugin with the same name, only one can 82 | > be installed at any time. 83 | 84 | ## The default index 85 | 86 | When you don't include an explicit `INDEX_NAME` prefix in your Krew command, the 87 | command will refer to a plugin from the default index. The `INDEX_NAME` prefix is 88 | used to differentiate plugins with the same name across different indexes. 89 | 90 | Krew ships with [`krew-index`][ki] as the `default` index, but this can be 91 | removed using the `kubectl krew index remove default` command. Once it is 92 | removed, you can add another index with the name `default` and plugins from it 93 | will not require the `INDEX_NAME` prefix in commands. 94 | 95 | [ki]: https://github.com/kubernetes-sigs/krew-index 96 | -------------------------------------------------------------------------------- /site/content/plugins.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Kubectl plugins available 3 | slug: plugins 4 | --- 5 | 6 | Below you will find the list of kubectl plugins distributed on the centralized 7 | [krew-index](https://sigs.k8s.io/krew-index). To install these plugins on 8 | your machine: 9 | 10 | 1. [Install Krew]({{< relref "docs/user-guide/setup/install.md" >}}) 11 | 2. Run `kubectl krew install ` to install a plugin via Krew. 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
NameDescriptionRepository
Loading...
28 | -------------------------------------------------------------------------------- /site/functions/README.md: -------------------------------------------------------------------------------- 1 | # Dynamic functions on Krew documentation 2 | 3 | Krew site makes use of Netlify Functions (lambdas) to fetch plugin list 4 | dynamically from `krew-index` repository using GitHub API. 5 | 6 | ## Set up on Netlify 7 | 8 | Functions require a one-time set up in the Netlify console. 9 | 10 | Regarding **GitHub API rate limits**: 11 | 12 | - In production, make **sure to set a `GITHUB_ACCESS_TOKEN` environment 13 | variable** with a permissionless "personal access token" to elevate the rate 14 | limits for our functions. 15 | 16 | Dynamic responses from the functions are cached on 17 | Netlify’s CDN for a long time, so this is not a huge problem and a single 18 | token is very likely to suffice a long period of time. 19 | 20 | - During local development, you can hit the GitHub API rate limit as well. 21 | You should set the `GITHUB_ACCESS_TOKEN` environment variable as needed. 22 | 23 | ## Local development 24 | 25 | Start `hugo` local iteration server on port 1313: 26 | 27 | ``` 28 | cd ./site 29 | hugo serve 30 | ``` 31 | 32 | In another terminal window, build and start the functions server on port 8080: 33 | 34 | ``` 35 | cd ./functions 36 | go run ./server -port=8080 37 | ``` 38 | 39 | Now, you can reach the website at http://localhost:8080 and the functions at 40 | paths defined in the code e.g. 41 | http://localhost:8080/.netlify/functions/api/plugins. 42 | -------------------------------------------------------------------------------- /site/functions/go.mod: -------------------------------------------------------------------------------- 1 | module sigs.k8s.io/krew/site/functions 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/apex/gateway v1.1.1 7 | github.com/google/go-github/v32 v32.1.0 8 | github.com/pkg/errors v0.9.1 9 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be 10 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 11 | sigs.k8s.io/krew v0.4.0 12 | sigs.k8s.io/yaml v1.2.0 13 | ) 14 | 15 | require ( 16 | github.com/aws/aws-lambda-go v1.19.1 // indirect 17 | github.com/gogo/protobuf v1.2.1 // indirect 18 | github.com/golang/protobuf v1.3.2 // indirect 19 | github.com/google/go-querystring v1.0.0 // indirect 20 | github.com/google/gofuzz v1.0.0 // indirect 21 | github.com/tj/assert v0.0.3 // indirect 22 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 // indirect 23 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7 // indirect 24 | golang.org/x/text v0.3.2 // indirect 25 | google.golang.org/appengine v1.1.0 // indirect 26 | gopkg.in/inf.v0 v0.9.1 // indirect 27 | gopkg.in/yaml.v2 v2.2.8 // indirect 28 | k8s.io/apimachinery v0.0.0-20190717022731-0bb8574e0887 // indirect 29 | k8s.io/klog v1.0.0 // indirect 30 | ) 31 | 32 | // replace sigs.k8s.io/krew => ../../ 33 | -------------------------------------------------------------------------------- /site/layouts/404.html: -------------------------------------------------------------------------------- 1 | {{ partial "header.html" . }} 2 | 3 |

Not found

4 | 5 |

6 | Sorry, this URL cannot be found. Maybe it’s moved. 7 |

8 | 9 | ← Home 10 | 11 | {{ partial "footer.html" . }} 12 | -------------------------------------------------------------------------------- /site/layouts/_default/single.html: -------------------------------------------------------------------------------- 1 | {{ partial "header.html" . }} 2 |
3 |

{{.Title}}

4 | {{ with .Content }} 5 | {{.}} 6 | {{ else }} 7 |
This page is not yet written.
8 | {{ end }} 9 |
10 | 11 | {{ partial "backbutton.html" .}} 12 | {{ partial "footer.html" . }} 13 | -------------------------------------------------------------------------------- /site/layouts/docs/section.html: -------------------------------------------------------------------------------- 1 | {{ $isDocsHome := (eq .Slug "docs") }} 2 | {{ partial "header.html" . }} 3 |
4 |

5 | {{ if $isDocsHome }} 6 | Documentation 7 | {{else}} 8 | {{ .Title}} 9 | {{end}} 10 |

11 | 12 | {{ with .Content }} 13 | 14 | {{ end }} 15 | 16 | {{ partial "toc.html" . }} 17 | 18 | {{ if not $isDocsHome }} 19 | {{ partial "backbutton.html" .}} 20 | {{ end }} 21 |
22 | {{ partial "footer.html" . }} 23 | -------------------------------------------------------------------------------- /site/layouts/index.html: -------------------------------------------------------------------------------- 1 | {{ partial "header.html" . }} 2 | 3 | 15 | 16 | {{ partial "footer.html" . }} 17 | -------------------------------------------------------------------------------- /site/layouts/partials/backbutton.html: -------------------------------------------------------------------------------- 1 | {{ $docsHome := "/docs" }} 2 | {{ $userGuide := "/docs/user-guide" }} 3 | {{ $devGuide := "/docs/developer-guide" }} 4 | 5 | {{ $isUserGuide := .Parent.IsDescendant (.Site.GetPage "section" $userGuide) }} 6 | {{ $isDevGuide := .Parent.IsDescendant (.Site.GetPage "section" $devGuide) }} 7 | 8 | {{ $parent := "" }} 9 | {{ if $isUserGuide }} 10 | {{ $parent = $userGuide }} 11 | {{ else if $isDevGuide }} 12 | {{ $parent = $devGuide }} 13 | {{ else }} 14 | {{ $parent = $docsHome }} 15 | {{ end }} 16 | 17 | {{ $parent := .Site.GetPage "section" $parent }} 18 | 19 | 20 | ← {{ $parent.Title }} 21 | 22 | -------------------------------------------------------------------------------- /site/layouts/partials/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ if .IsHome }} 9 | {{ .Site.Title }} 10 | {{ else }} 11 | {{ .Title }} · Krew 12 | {{ end }} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {{ partial "navbar.html" . }} 25 | 26 |
30 | 31 |
32 | -------------------------------------------------------------------------------- /site/layouts/partials/navbar.html: -------------------------------------------------------------------------------- 1 | 37 | -------------------------------------------------------------------------------- /site/layouts/partials/toc.html: -------------------------------------------------------------------------------- 1 | {{ with .Pages}} 2 |
    3 | {{ range . }} 4 | {{ if .Pages }} 5 |
  • {{.Name}}
  • 6 | {{ partial "toc.html" .}} 7 | {{ else }} 8 |
  • 9 | {{.Name}} 10 |
  • 11 | {{ end }} 12 | {{ end }} 13 |
14 | {{ end }} 15 | -------------------------------------------------------------------------------- /site/layouts/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | {{ if ne ( getenv "HUGO_ENV" ) "production" -}} 3 | Disallow: / 4 | {{- end -}} 5 | -------------------------------------------------------------------------------- /site/layouts/shortcodes/output.html: -------------------------------------------------------------------------------- 1 | {{- .Inner -}}{{- /* 2 | */ -}} 3 | -------------------------------------------------------------------------------- /site/layouts/shortcodes/prompt.html: -------------------------------------------------------------------------------- 1 | $ {{- /* 2 | */ -}} 3 | -------------------------------------------------------------------------------- /site/layouts/shortcodes/toc.html: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /site/static/css/style.css: -------------------------------------------------------------------------------- 1 | div.top-level-container { 2 | max-width: 960px; 3 | } 4 | 5 | blockquote, .blockquote { 6 | margin-left: 12px; 7 | color: #555; 8 | max-width: 520px; 9 | border-left: 2px solid #ddd; 10 | padding-left: 12px; 11 | } 12 | 13 | article h1, article h2, article h3, article h4 { 14 | font-weight: 300; 15 | } 16 | 17 | pre { 18 | padding: 12px; 19 | overflow: auto 20 | } 21 | 22 | .noselect { 23 | -webkit-touch-callout: none; 24 | -webkit-user-select: none; 25 | -khtml-user-select: none; 26 | -moz-user-select: none; 27 | -ms-user-select: none; 28 | user-select: none; 29 | } 30 | 31 | .cliprompt { 32 | color: orange; 33 | } 34 | 35 | pre .cmdoutput { 36 | color: #aaa; 37 | } 38 | 39 | .lead { 40 | letter-spacing: -0.4px; 41 | font-weight: 300; 42 | } 43 | 44 | article aside.toc { 45 | background-color: #f8f8f8;; 46 | font-size: 1rem; 47 | } 48 | -------------------------------------------------------------------------------- /site/static/img/favicon.svg: -------------------------------------------------------------------------------- 1 | ../../../assets/logo/icon/color/krew-icon-color.svg -------------------------------------------------------------------------------- /site/static/img/krew-logo.svg: -------------------------------------------------------------------------------- 1 | ../../../assets/logo/horizontal/color/krew-horizontal-color.svg --------------------------------------------------------------------------------