├── .circleci ├── build.config └── config.yml ├── .github ├── ISSUE_TEMPLATE │ ├── bug.yaml │ ├── feature_request.md │ └── other.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── github-action-test.yml │ └── stale.yml ├── .gitignore ├── .goreleaser.yml ├── .licenserc.yaml ├── .pre-commit-config.yaml ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cmd ├── pluto │ └── main.go ├── root.go └── version.go ├── docs ├── .vuepress │ ├── config-extras.js │ ├── config.js │ ├── public │ │ ├── favicon.png │ │ ├── img │ │ │ ├── fairwinds-logo.svg │ │ │ ├── insights-banner.png │ │ │ └── pluto-logo.png │ │ └── scripts │ │ │ ├── marketing.js │ │ │ └── modify.js │ ├── styles │ │ ├── index.styl │ │ └── palette.styl │ └── theme │ │ ├── index.js │ │ └── layouts │ │ └── Layout.vue ├── README.md ├── advanced.md ├── contributing │ ├── code-of-conduct.md │ └── guide.md ├── faq.md ├── installation.md ├── main-metadata.md ├── orb.md ├── package-lock.json ├── package.json └── quickstart.md ├── e2e ├── pre.sh ├── test.sh ├── tests │ ├── 00_static_files.yaml │ ├── 01_helm-detect-3.yaml │ ├── 03-cli-validation.yaml │ └── assets │ │ ├── additional-versions │ │ ├── duplicate.yaml │ │ └── new.yaml │ │ ├── chart │ │ ├── .helmignore │ │ ├── Chart.yaml │ │ ├── templates │ │ │ ├── NOTES.txt │ │ │ ├── _helpers.tpl │ │ │ ├── deployment-deprecated.yaml │ │ │ └── deployment.yaml │ │ └── values.yaml │ │ ├── deprecated116 │ │ └── deployment-extensions-v1beta1.yaml │ │ ├── helm3 │ │ ├── secret-demo2.json │ │ ├── secret.json │ │ └── secret_duplicate_name.json │ │ ├── list │ │ ├── list-deployment-deprecated-and-non-deprecated.yaml │ │ └── list-deployment-non-deprecated.yaml │ │ └── non-deprecated │ │ └── deployment-apps-v1.yaml └── venom.log ├── embed.go ├── fairwinds-insights.yaml ├── github-action └── action.yml ├── go.mod ├── go.sum ├── img └── pluto-logo.png ├── orb ├── @orb.yml ├── commands │ ├── configure_env.yml │ ├── detect.yml │ ├── detect_files.yml │ └── install.yml ├── examples │ ├── detect.yml │ └── detect_files.yml ├── executors │ └── default.yml ├── jobs │ ├── detect.yml │ └── detect_files.yml └── scripts │ ├── detect.sh │ ├── detect_files.sh │ └── install.sh ├── pkg ├── api │ ├── columns.go │ ├── helpers.go │ ├── helpers_test.go │ ├── output.go │ ├── output_test.go │ ├── test_data │ │ └── versions.yaml │ ├── versions.go │ └── versions_test.go ├── discovery-api │ ├── discovery_api.go │ └── discovery_api_test.go ├── finder │ ├── finder.go │ ├── finder_test.go │ └── testdata │ │ ├── deployment-extensions-v1beta1.json │ │ ├── deployment-extensions-v1beta1.yaml │ │ └── other.txt ├── helm │ ├── helm.go │ └── helm_test.go └── kube │ ├── kube.go │ ├── kube_test.go │ └── testdata │ ├── kubeconfig │ └── kubeconfig_invalid ├── release-orb.sh └── versions.yaml /.circleci/build.config: -------------------------------------------------------------------------------- 1 | DOCKERFILE='Dockerfile' 2 | 3 | EXTERNAL_REGISTRY_BASE_DOMAIN=quay.io 4 | REPOSITORY_NAME=fairwinds/pluto 5 | DOCKERTAG=${EXTERNAL_REGISTRY_BASE_DOMAIN}/${REPOSITORY_NAME} 6 | if [[ -n $CI_TAG ]]; then 7 | ADDITIONAL_DOCKER_TAG_VERSIONS=() 8 | ADDITIONAL_DOCKER_TAG_VERSIONS+=(`echo $CI_TAG | sed -e 's/\(\w\+\)\..*$/\1/'`) 9 | ADDITIONAL_DOCKER_TAG_VERSIONS+=(`echo $CI_TAG | sed -e 's/\(\w\+\.\w\+\)\..*$/\1/'`) 10 | fi 11 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | orb-tools: circleci/orb-tools@10.1.0 5 | rok8s: fairwinds/rok8s-scripts@12 6 | oss-docs: fairwinds/oss-docs@0 7 | 8 | executors: 9 | golang-exec: 10 | docker: 11 | - image: cimg/go:1.24 12 | 13 | references: 14 | e2e_config: &e2e_config 15 | command_runner_image: quay.io/reactiveops/ci-images:v14-bullseye 16 | kind_node_image: "kindest/node:v1.26.6@sha256:6e2d8b28a5b601defe327b98bd1c2d1930b49e5d8c512e1895099e4504007adb" 17 | executor: golang-exec 18 | pre_script: e2e/pre.sh 19 | store-test-results: /tmp/test-results/ 20 | script: e2e/test.sh 21 | requires: 22 | - test 23 | filters: 24 | branches: 25 | only: /.*/ 26 | tags: 27 | ignore: /.*/ 28 | install_vault_alpine: &install_vault_alpine 29 | run: 30 | name: install hashicorp vault 31 | command: | 32 | apk --update add curl yq 33 | cd /tmp 34 | curl -LO https://releases.hashicorp.com/vault/1.12.2/vault_1.12.2_linux_amd64.zip 35 | sha256sum vault_1.12.2_linux_amd64.zip | grep 116c143de377a77a7ea455a367d5e9fe5290458e8a941a6e2dd85d92aaedba67 36 | unzip vault_1.12.2_linux_amd64.zip 37 | mv vault /usr/bin/vault 38 | install_circleci: &install_circleci 39 | run: 40 | name: Install CircleCI CLI 41 | command: | 42 | cd /tmp 43 | curl -LO https://github.com/CircleCI-Public/circleci-cli/releases/download/v0.1.16535/circleci-cli_0.1.16535_linux_amd64.tar.gz 44 | tar -zxvf circleci-cli_0.1.16535_linux_amd64.tar.gz 45 | mv circleci-cli_0.1.16535_linux_amd64/circleci /bin/circleci 46 | circleci version 47 | jobs: 48 | test: 49 | working_directory: /home/circleci/go/src/github.com/fairwindsops/pluto 50 | docker: 51 | - image: cimg/go:1.24 52 | steps: 53 | - checkout 54 | - run: go mod download && go mod verify 55 | - run: go test -v ./... -coverprofile=coverage.txt -covermode=atomic 56 | release: 57 | working_directory: /home/circleci/go/src/github.com/fairwindsops/pluto 58 | resource_class: large 59 | shell: /bin/bash 60 | docker: 61 | - image: goreleaser/goreleaser:v2.9.0 62 | environment: 63 | GO111MODULE: "on" 64 | steps: 65 | - checkout 66 | - setup_remote_docker 67 | - *install_vault_alpine 68 | - rok8s/get_vault_env: 69 | vault_path: repo/global/env 70 | - rok8s/get_vault_env: 71 | vault_path: repo/pluto/env 72 | - run: 73 | name: docker login 74 | command: | 75 | docker login -u _json_key -p "$(echo $GCP_ARTIFACTREADWRITE_JSON_KEY | base64 -d)" us-docker.pkg.dev 76 | - run: echo 'export GORELEASER_CURRENT_TAG="${CIRCLE_TAG}"' >> $BASH_ENV 77 | - run: goreleaser 78 | snapshot: 79 | working_directory: /home/circleci/go/src/github.com/fairwindsops/pluto 80 | resource_class: large 81 | docker: 82 | - image: goreleaser/goreleaser:v2.9.0 83 | steps: 84 | - checkout 85 | - setup_remote_docker 86 | - run: goreleaser --snapshot --skip=sign 87 | - store_artifacts: 88 | path: dist 89 | destination: snapshot 90 | publish-dev-orb: 91 | working_directory: /go/src/github.com/fairwindsops/pluto 92 | docker: 93 | - image: quay.io/reactiveops/ci-images:v14-alpine 94 | shell: /bin/bash 95 | steps: 96 | - checkout 97 | - *install_vault_alpine 98 | - rok8s/get_vault_env: 99 | vault_path: repo/global/env 100 | - *install_circleci 101 | - orb-tools/pack: 102 | source: orb/ 103 | - orb-tools/publish: 104 | orb-ref: fairwinds/pluto@dev:${CIRCLE_BRANCH} 105 | token-variable: CIRCLECI_DEV_API_TOKEN 106 | request-orb-publish: 107 | docker: 108 | - image: quay.io/reactiveops/ci-images:v14-alpine 109 | steps: 110 | - *install_vault_alpine 111 | - rok8s/get_vault_env: 112 | vault_path: repo/pluto/env 113 | - run: 114 | name: "Request Orb Publish" 115 | command: | 116 | apk --update add curl 117 | curl -X POST --data-urlencode "payload={\"text\": \"Please publish a new pluto orb by checking out $CIRCLE_TAG and running release-orb.sh\"}" $SLACK_URL 118 | workflows: 119 | version: 2 120 | test: 121 | jobs: 122 | - test 123 | - snapshot: 124 | requires: 125 | - test 126 | filters: 127 | branches: 128 | only: /.*/ 129 | tags: 130 | ignore: /.*/ 131 | - rok8s/kubernetes_e2e_tests: 132 | requires: 133 | - test 134 | filters: 135 | branches: 136 | only: /.*/ 137 | tags: 138 | ignore: /.*/ 139 | name: functional tests 140 | <<: *e2e_config 141 | release: 142 | jobs: 143 | - release: 144 | filters: 145 | branches: 146 | ignore: /.*/ 147 | tags: 148 | only: /.*/ 149 | - oss-docs/publish-docs: 150 | requires: 151 | - release 152 | repository: pluto 153 | filters: 154 | branches: 155 | ignore: /.*/ 156 | tags: 157 | only: /^.*/ 158 | "Orb Test and Publish": 159 | jobs: 160 | - orb-tools/lint: 161 | name: "Lint Orb Files" 162 | pre-steps: 163 | - run: 164 | command: apk add git openssh 165 | lint-dir: orb 166 | filters: 167 | branches: 168 | only: /.*/ 169 | tags: 170 | only: /.*/ 171 | - publish-dev-orb: 172 | name: Publish Dev Orb 173 | requires: 174 | - "Lint Orb Files" 175 | filters: 176 | branches: 177 | only: /.*/ 178 | tags: 179 | ignore: /.*/ 180 | - request-orb-publish: 181 | name: "Request Orb Publishing" 182 | filters: 183 | branches: 184 | ignore: /.*/ 185 | tags: 186 | only: /^v.*/ 187 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yaml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | labels: [bug, triage] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! Please fill the form below. 9 | - type: textarea 10 | id: what-happened 11 | attributes: 12 | label: What happened? 13 | description: What happened? 14 | validations: 15 | required: true 16 | - type: textarea 17 | id: expected 18 | attributes: 19 | label: What did you expect to happen? 20 | description: What is the expected or desired behavior? 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: reproducible 25 | attributes: 26 | label: How can we reproduce this? 27 | description: Please share the steps that we can take to reproduce this. Also include any relevant configuration. 28 | validations: 29 | required: true 30 | - type: input 31 | id: version 32 | attributes: 33 | label: Version 34 | description: The version of the tool that you are using. If a helm chart, please share the name of the chart. 35 | validations: 36 | required: true 37 | - type: checkboxes 38 | id: search 39 | attributes: 40 | label: Search 41 | options: 42 | - label: I did search for other open and closed issues before opening this. 43 | required: true 44 | - type: checkboxes 45 | id: terms 46 | attributes: 47 | label: Code of Conduct 48 | description: By submitting this issue, you agree to follow the CODE_OF_CONDUCT in this repository. 49 | options: 50 | - label: I agree to follow this project's Code of Conduct 51 | required: true 52 | - type: textarea 53 | id: ctx 54 | attributes: 55 | label: Additional context 56 | description: Anything else you would like to add 57 | validations: 58 | required: false 59 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: [triage, enhancement] 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other 3 | about: For misc. tasks like research or continued conversation 4 | title: '' 5 | labels: [triage] 6 | assignees: '' 7 | 8 | --- 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | ## DO NOT EDIT - Managed by Terraform 2 | version: 2 3 | updates: 4 | - package-ecosystem: "docker" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | 9 | - package-ecosystem: "npm" 10 | directory: "/docs" 11 | schedule: 12 | interval: "weekly" 13 | open-pull-requests-limit: 0 14 | ignore: 15 | - dependency-name: "*" 16 | 17 | - package-ecosystem: "gomod" 18 | directory: "/" 19 | schedule: 20 | interval: "weekly" 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | This PR fixes # 3 | 4 | ## Checklist 5 | * [ ] I have signed the CLA 6 | * [ ] I have updated/added any relevant documentation 7 | 8 | ## Description 9 | ### What's the goal of this PR? 10 | 11 | ### What changes did you make? 12 | 13 | ### What alternative solution should we consider, if any? 14 | 15 | -------------------------------------------------------------------------------- /.github/workflows/github-action-test.yml: -------------------------------------------------------------------------------- 1 | name: Test github action 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | action: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2 11 | 12 | - name: Download pluto 13 | uses: ./github-action 14 | 15 | - name: Pluto exists? 16 | run: | 17 | pluto version -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PRs' 2 | on: 3 | schedule: 4 | - cron: '32 1 * * *' 5 | 6 | permissions: 7 | issues: write 8 | pull-requests: write 9 | 10 | jobs: 11 | stale: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/stale@v4 15 | with: 16 | exempt-issue-labels: pinned 17 | stale-pr-label: stale 18 | stale-issue-label: stale 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | 3 | # Output of the go coverage tool 4 | *.out 5 | cover-report.html 6 | coverage.txt 7 | cover.html 8 | 9 | /pluto 10 | pkged.go 11 | 12 | node_modules 13 | /dist 14 | 15 | orb.yml 16 | venom.*.log 17 | docs/README.md 18 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | brews: 3 | - name: pluto 4 | goarm: 6 5 | repository: 6 | owner: FairwindsOps 7 | name: homebrew-tap 8 | directory: Formula 9 | description: Detect deprecated Kubernetes apiVersions 10 | test: | 11 | system "#{bin}/pluto version" 12 | release: 13 | prerelease: auto 14 | footer: | 15 | You can verify the signatures of both the checksums.txt file and the published docker images using [cosign](https://github.com/sigstore/cosign). 16 | 17 | ```bash 18 | cosign verify-blob checksums.txt --signature=checksums.txt.sig --key https://artifacts.fairwinds.com/cosign-p256.pub 19 | ``` 20 | 21 | ``` 22 | cosign verify us-docker.pkg.dev/fairwinds-ops/oss/pluto:v5 --key https://artifacts.fairwinds.com/cosign-p256.pub 23 | ``` 24 | builds: 25 | - ldflags: 26 | - -X main.version={{.Version}} -X main.commit={{.Commit}} -s -w 27 | main: cmd/pluto/main.go 28 | goarch: 29 | - amd64 30 | - arm 31 | - arm64 32 | env: 33 | - CGO_ENABLED=0 34 | goos: 35 | - linux 36 | - darwin 37 | - windows 38 | goarm: 39 | - 6 40 | - 7 41 | checksum: 42 | name_template: "checksums.txt" 43 | 44 | signs: 45 | - cmd: cosign 46 | args: 47 | - "sign-blob" 48 | - "--key=hashivault://cosign-p256" 49 | - "--output-signature=${signature}" 50 | - "${artifact}" 51 | - "--yes" 52 | artifacts: all 53 | 54 | docker_signs: 55 | - artifacts: all 56 | args: ["sign", "--key=hashivault://cosign-p256", "us-docker.pkg.dev/fairwinds-ops/oss/pluto@${digest}", "-r", "--yes"] 57 | 58 | dockers: 59 | - image_templates: 60 | - "us-docker.pkg.dev/fairwinds-ops/oss/pluto:{{ .FullCommit }}-amd64" 61 | - "us-docker.pkg.dev/fairwinds-ops/oss/pluto:v{{ .Major }}-amd64" 62 | - "us-docker.pkg.dev/fairwinds-ops/oss/pluto:v{{ .Major }}.{{ .Minor }}-amd64" 63 | use: buildx 64 | dockerfile: Dockerfile 65 | build_flag_templates: 66 | - "--platform=linux/amd64" 67 | - image_templates: 68 | - "us-docker.pkg.dev/fairwinds-ops/oss/pluto:{{ .FullCommit }}-arm64v8" 69 | - "us-docker.pkg.dev/fairwinds-ops/oss/pluto:v{{ .Major }}-arm64v8" 70 | - "us-docker.pkg.dev/fairwinds-ops/oss/pluto:v{{ .Major }}.{{ .Minor }}-arm64v8" 71 | use: buildx 72 | goarch: arm64 73 | dockerfile: Dockerfile 74 | build_flag_templates: 75 | - "--platform=linux/arm64/v8" 76 | - image_templates: 77 | - "us-docker.pkg.dev/fairwinds-ops/oss/pluto:{{ .FullCommit }}-armv7" 78 | - "us-docker.pkg.dev/fairwinds-ops/oss/pluto:v{{ .Major }}-armv7" 79 | - "us-docker.pkg.dev/fairwinds-ops/oss/pluto:v{{ .Major }}.{{ .Minor }}-armv7" 80 | use: buildx 81 | goarch: arm64 82 | dockerfile: Dockerfile 83 | build_flag_templates: 84 | - "--platform=linux/arm/v7" 85 | docker_manifests: 86 | - name_template: us-docker.pkg.dev/fairwinds-ops/oss/pluto:{{ .Tag }} 87 | image_templates: 88 | - "us-docker.pkg.dev/fairwinds-ops/oss/pluto:{{ .FullCommit }}-amd64" 89 | - "us-docker.pkg.dev/fairwinds-ops/oss/pluto:{{ .FullCommit }}-arm64v8" 90 | - "us-docker.pkg.dev/fairwinds-ops/oss/pluto:{{ .FullCommit }}-armv7" 91 | - name_template: us-docker.pkg.dev/fairwinds-ops/oss/pluto:v{{ .Major }} 92 | image_templates: 93 | - "us-docker.pkg.dev/fairwinds-ops/oss/pluto:v{{ .Major }}-amd64" 94 | - "us-docker.pkg.dev/fairwinds-ops/oss/pluto:v{{ .Major }}-arm64v8" 95 | - "us-docker.pkg.dev/fairwinds-ops/oss/pluto:v{{ .Major }}-armv7" 96 | - name_template: us-docker.pkg.dev/fairwinds-ops/oss/pluto:v{{ .Major }}.{{ .Minor }} 97 | image_templates: 98 | - "us-docker.pkg.dev/fairwinds-ops/oss/pluto:v{{ .Major }}.{{ .Minor }}-amd64" 99 | - "us-docker.pkg.dev/fairwinds-ops/oss/pluto:v{{ .Major }}.{{ .Minor }}-arm64v8" 100 | - "us-docker.pkg.dev/fairwinds-ops/oss/pluto:v{{ .Major }}.{{ .Minor }}-armv7" 101 | -------------------------------------------------------------------------------- /.licenserc.yaml: -------------------------------------------------------------------------------- 1 | header: 2 | license: 3 | spdx-id: Apache-2.0 4 | copyright-owner: 'FairwindsOps Inc' 5 | content: | 6 | // Copyright 2020 FairwindsOps Inc 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | paths: 20 | - '**/*.go' 21 | comment: on-failure 22 | dependency: 23 | files: 24 | - go.mod 25 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v2.3.0 5 | hooks: 6 | - id: check-json 7 | - id: detect-private-key 8 | - id: trailing-whitespace 9 | exclude: > 10 | (?x)^( 11 | docs/.+ 12 | )$ 13 | - id: check-added-large-files 14 | args: ['--maxkb=500'] 15 | - id: check-byte-order-marker 16 | - id: check-merge-conflict 17 | - id: check-symlinks 18 | - id: end-of-file-fixer 19 | exclude: > 20 | (?x)^( 21 | docs/.+ 22 | )$ 23 | - id: check-executables-have-shebangs 24 | - id: flake8 25 | - id: no-commit-to-branch 26 | args: [--branch, master] 27 | - id: pretty-format-json 28 | args: ['--autofix'] 29 | - repo: https://github.com/jumanjihouse/pre-commit-hooks 30 | rev: 2.1.5 31 | hooks: 32 | - id: forbid-binary 33 | exclude: > 34 | (?x)^( 35 | .+\.png| 36 | .+\.woff| 37 | .+\.woff2| 38 | .+\.tff| 39 | .+\.ico 40 | )$ 41 | - id: shellcheck 42 | - repo: https://github.com/dnephin/pre-commit-golang.git 43 | rev: v0.3.5 44 | hooks: 45 | - id: go-fmt 46 | - id: golangci-lint 47 | - id: go-vet 48 | - id: go-unit-tests 49 | - id: go-build 50 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | ## DO NOT EDIT - Managed by Terraform 2 | * @sudermanjr @transient1 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at opensource@fairwinds.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.21 2 | 3 | LABEL org.opencontainers.image.authors="FairwindsOps, Inc." \ 4 | org.opencontainers.image.vendor="FairwindsOps, Inc." \ 5 | org.opencontainers.image.title="pluto" \ 6 | org.opencontainers.image.description="Pluto is a cli tool to help discover deprecated apiVersions in Kubernetes" \ 7 | org.opencontainers.image.documentation="https://pluto.docs.fairwinds.com/" \ 8 | org.opencontainers.image.source="https://github.com/FairwindsOps/pluto" \ 9 | org.opencontainers.image.url="https://github.com/FairwindsOps/pluto" \ 10 | org.opencontainers.image.licenses="Apache License 2.0" 11 | 12 | USER nobody 13 | COPY pluto / 14 | 15 | ENTRYPOINT ["/pluto"] 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018 FairwindsOps Inc 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Go parameters 2 | GOCMD=GO111MODULE=on CGO_ENABLED=0 go 3 | GOBUILD=$(GOCMD) build 4 | GOCLEAN=$(GOCMD) clean 5 | GOTEST=$(GOCMD) test 6 | BINARY_NAME=pluto 7 | COMMIT := $(shell git rev-parse HEAD) 8 | VERSION := "local-dev" 9 | 10 | all: lint test 11 | build: 12 | $(GOBUILD) -o $(BINARY_NAME) -ldflags "-X main.version=$(VERSION) -X main.commit=$(COMMIT) -s -w" -v ./cmd/pluto/main.go 13 | lint: 14 | golangci-lint run 15 | reportcard: 16 | goreportcard-cli -t 100 -v 17 | test: 18 | $(GOCMD) test -v --bench --benchmem -coverprofile coverage.txt -covermode=atomic ./... 19 | $(GOCMD) vet ./... 2> govet-report.out 20 | $(GOCMD) tool cover -html=coverage.txt -o cover-report.html 21 | printf "\nCoverage report available at cover-report.html\n\n" 22 | tidy: 23 | $(GOCMD) mod tidy 24 | clean: 25 | $(GOCLEAN) 26 | $(GOCMD) fmt ./... 27 | rm -f $(BINARY_NAME) 28 | # Cross compilation 29 | build-linux: 30 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BINARY_NAME) -ldflags "-X main.version=$(VERSION) -X main.commit=$(COMMIT) -s -w" -v ./cmd/pluto/main.go 31 | build-windows: 32 | CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GOBUILD) -o $(BINARY_NAME) -ldflags "-X main.version=$(VERSION) -X main.commit=$(COMMIT) -s -w" -v ./cmd/pluto/main.go 33 | build-docker: build-linux 34 | docker build --build-arg version=$(VERSION) --build-arg commit=$(COMMIT) -t us-docker.pkg.dev/fairwinds-ops/oss/pluto/$(BINARY_NAME):dev . 35 | orb-validate: 36 | circleci orb pack orb/ > orb.yml 37 | circleci orb validate orb.yml 38 | circleci-validate: 39 | circleci config validate --org-slug github/FairwindsOps 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Pluto Logo 3 |
4 |

Find Kubernetes resources that have been deprecated

5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | Pluto is a utility to help users find [deprecated Kubernetes apiVersions](https://k8s.io/docs/reference/using-api/deprecation-guide/) in their code repositories and their helm releases. 20 | 21 | ## Documentation 22 | Check out the [documentation at docs.fairwinds.com](https://pluto.docs.fairwinds.com) 23 | 24 | ## Purpose 25 | 26 | Kubernetes sometimes deprecates apiVersions. Most notably, a large number of deprecations happened in the [1.16 release](https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/). This is fine, and it's a fairly easy thing to deal with. However, it can be difficult to find all the places where you might have used a version that will be deprecated in your next upgrade. 27 | 28 | You might think, "I'll just ask the api-server to tell me!", but this is fraught with danger. If you ask the api-server to give you `deployments.v1.apps`, and the deployment was deployed as `deployments.v1beta1.extensions`, the api-server will quite happily convert the api version and return a manifest with `apps/v1`. This is fairly well outlined in the discussion in [this issue](https://github.com/kubernetes/kubernetes/issues/58131#issuecomment-356823588). 29 | 30 | So, long story short, finding the places where you have deployed a deprecated apiVersion can be challenging. This is where `pluto` comes in. You can use pluto to check a couple different places where you might have placed a deprecated version: 31 | * Infrastructure-as-Code repos: Pluto can check both static manifests and Helm charts for deprecated apiVersions 32 | * Live Helm releases: Pluto can check both Helm 2 and Helm 3 releases running in your cluster for deprecated apiVersions 33 | 34 | ## Kubernetes Deprecation Policy 35 | 36 | You can read the full policy [here](https://kubernetes.io/docs/reference/using-api/deprecation-policy/) 37 | 38 | Long story short, apiVersions get deprecated, and then they eventually get removed entirely. Pluto differentiates between these two, and will tell you if a version is `DEPRECATED` or `REMOVED` 39 | 40 | ## GitHub Action Usage 41 | Want to use pluto within your GitHub workflows? 42 | 43 | ```yaml 44 | - name: Download Pluto 45 | uses: FairwindsOps/pluto/github-action@master 46 | 47 | - name: Use pluto 48 | run: | 49 | pluto detect-files -d pkg/finder/testdata 50 | ``` 51 | 52 | 53 | ## Join the Fairwinds Open Source Community 54 | 55 | The goal of the Fairwinds Community is to exchange ideas, influence the open source roadmap, 56 | and network with fellow Kubernetes users. 57 | [Chat with us on Slack](https://join.slack.com/t/fairwindscommunity/shared_invite/zt-2na8gtwb4-DGQ4qgmQbczQyB2NlFlYQQ) 58 | or 59 | [join the user group](https://www.fairwinds.com/open-source-software-user-group) to get involved! 60 | 61 | 62 | Love Fairwinds Open Source? Automate Fairwinds Open Source for free with Fairwinds Insights. Click to learn more 64 | 65 | 66 | ## Other Projects from Fairwinds 67 | 68 | Enjoying Pluto? Check out some of our other projects: 69 | * [Polaris](https://github.com/FairwindsOps/Polaris) - Audit, enforce, and build policies for Kubernetes resources, including over 20 built-in checks for best practices 70 | * [Goldilocks](https://github.com/FairwindsOps/Goldilocks) - Right-size your Kubernetes Deployments by compare your memory and CPU settings against actual usage 71 | * [Nova](https://github.com/FairwindsOps/Nova) - Check to see if any of your Helm charts have updates available 72 | * [rbac-manager](https://github.com/FairwindsOps/rbac-manager) - Simplify the management of RBAC in your Kubernetes clusters 73 | 74 | Or [check out the full list](https://www.fairwinds.com/open-source-software?utm_source=pluto&utm_medium=pluto&utm_campaign=pluto) 75 | ## Fairwinds Insights 76 | If you're interested in running Pluto in multiple clusters, 77 | tracking the results over time, integrating with Slack, Datadog, and Jira, 78 | or unlocking other functionality, check out 79 | [Fairwinds Insights](https://fairwinds.com/pricing), 80 | a platform for auditing and enforcing policy in Kubernetes clusters. 81 | 82 | 83 | Fairwinds Insights 84 | 85 | -------------------------------------------------------------------------------- /cmd/pluto/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 FairwindsOps Inc 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 | package main 15 | 16 | import ( 17 | plutoversionsfile "github.com/fairwindsops/pluto/v5" 18 | "github.com/fairwindsops/pluto/v5/cmd" 19 | ) 20 | 21 | var ( 22 | // version is set during build 23 | version = "development" 24 | // commit is set during build 25 | commit = "n/a" 26 | 27 | versionsFile []byte = plutoversionsfile.Content() 28 | ) 29 | 30 | func main() { 31 | cmd.Execute(version, commit, versionsFile) 32 | } 33 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 FairwindsOps Inc 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 | // Copyright 2020 Fairwinds 16 | // 17 | // Licensed under the Apache License, Version 2.0 (the "License"); 18 | // you may not use this file except in compliance with the License. 19 | // You may obtain a copy of the License at 20 | // 21 | // http://www.apache.org/licenses/LICENSE-2.0 22 | // 23 | // Unless required by applicable law or agreed to in writing, software 24 | // distributed under the License is distributed on an "AS IS" BASIS, 25 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26 | // See the License for the specific language governing permissions and 27 | // limitations under the License 28 | 29 | package cmd 30 | 31 | import ( 32 | "fmt" 33 | 34 | "github.com/spf13/cobra" 35 | ) 36 | 37 | func init() { 38 | rootCmd.AddCommand(versionCmd) 39 | } 40 | 41 | var versionCmd = &cobra.Command{ 42 | Use: "version", 43 | Short: "Prints the current version of the tool.", 44 | Long: `Prints the current version.`, 45 | Run: func(cmd *cobra.Command, args []string) { 46 | fmt.Println("Version:" + version + " Commit:" + versionCommit) 47 | }, 48 | PersistentPostRunE: func(cmd *cobra.Command, args []string) error { 49 | return nil 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /docs/.vuepress/config-extras.js: -------------------------------------------------------------------------------- 1 | // To see all options: 2 | // https://vuepress.vuejs.org/config/ 3 | // https://vuepress.vuejs.org/theme/default-theme-config.html 4 | module.exports = { 5 | title: "Pluto Documentation", 6 | description: "Documentation for Fairwinds' Pluto", 7 | themeConfig: { 8 | docsRepo: "FairwindsOps/pluto", 9 | sidebar: [ 10 | { 11 | title: "Pluto", 12 | path: "/", 13 | sidebarDepth: 0, 14 | }, 15 | { 16 | title: "Installation", 17 | path: "/installation", 18 | }, 19 | { 20 | title: "Quickstart", 21 | path: "/quickstart", 22 | }, 23 | { 24 | title: "Advanced Usage", 25 | path: "/advanced", 26 | }, 27 | { 28 | title: "FAQ", 29 | path: "/faq", 30 | }, 31 | { 32 | title: "CircleCI Orb", 33 | path: "/orb", 34 | }, 35 | { 36 | title: "Contributing", 37 | children: [ 38 | { 39 | title: "Guide", 40 | path: "contributing/guide" 41 | }, 42 | { 43 | title: "Code of Conduct", 44 | path: "contributing/code-of-conduct" 45 | } 46 | ] 47 | } 48 | ] 49 | }, 50 | } 51 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | // This file is generated from FairwindsOps/documentation-template 2 | // DO NOT EDIT MANUALLY 3 | 4 | const fs = require('fs'); 5 | const npath = require('path'); 6 | 7 | const CONFIG_FILE = npath.join(__dirname, 'config-extras.js'); 8 | const BASE_DIR = npath.join(__dirname, '..'); 9 | 10 | const extras = require(CONFIG_FILE); 11 | if (!extras.title || !extras.description || !extras.themeConfig.docsRepo) { 12 | throw new Error("Please specify 'title', 'description', and 'themeConfig.docsRepo' in config-extras.js"); 13 | } 14 | 15 | const docFiles = fs.readdirSync(BASE_DIR) 16 | .filter(f => f !== "README.md") 17 | .filter(f => f !== ".vuepress") 18 | .filter(f => f !== "node_modules") 19 | .filter(f => npath.extname(f) === '.md' || npath.extname(f) === ''); 20 | 21 | const sidebar = [['/', 'Home']].concat(docFiles.map(f => { 22 | const ext = npath.extname(f); 23 | if (ext === '') { 24 | // this is a directory 25 | const title = f; 26 | const children = fs.readdirSync(npath.join(BASE_DIR, f)).map(subf => { 27 | return '/' + f + '/' + npath.basename(subf); 28 | }); 29 | return {title, children}; 30 | } 31 | const path = npath.basename(f); 32 | return path; 33 | })); 34 | 35 | const baseConfig = { 36 | title: "", 37 | description: "", 38 | head: [ 39 | ['link', { rel: 'icon', href: '/favicon.png' }], 40 | ['script', { src: '/scripts/modify.js' }], 41 | ['script', { src: '/scripts/marketing.js' }], 42 | ], 43 | themeConfig: { 44 | docsRepo: "", 45 | docsDir: 'docs', 46 | editLinks: true, 47 | editLinkText: "Help us improve this page", 48 | logo: '/img/fairwinds-logo.svg', 49 | heroText: "", 50 | sidebar, 51 | nav: [ 52 | {text: 'View on GitHub', link: 'https://github.com/' + extras.themeConfig.docsRepo}, 53 | ], 54 | }, 55 | plugins: { 56 | 'vuepress-plugin-clean-urls': { 57 | normalSuffix: '/', 58 | notFoundPath: '/404.html', 59 | }, 60 | 'check-md': {}, 61 | }, 62 | } 63 | 64 | let config = JSON.parse(JSON.stringify(baseConfig)) 65 | if (!fs.existsSync(CONFIG_FILE)) { 66 | throw new Error("Please add config-extras.js to specify your project details"); 67 | } 68 | for (let key in extras) { 69 | if (!config[key]) config[key] = extras[key]; 70 | else if (key === 'head') config[key] = config[key].concat(extras[key]); 71 | else Object.assign(config[key], extras[key]); 72 | } 73 | module.exports = config; 74 | -------------------------------------------------------------------------------- /docs/.vuepress/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FairwindsOps/pluto/36af4af118b8c9b218e329d020926051159fee61/docs/.vuepress/public/favicon.png -------------------------------------------------------------------------------- /docs/.vuepress/public/img/fairwinds-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/.vuepress/public/img/insights-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FairwindsOps/pluto/36af4af118b8c9b218e329d020926051159fee61/docs/.vuepress/public/img/insights-banner.png -------------------------------------------------------------------------------- /docs/.vuepress/public/img/pluto-logo.png: -------------------------------------------------------------------------------- 1 | ../../../../img/pluto-logo.png -------------------------------------------------------------------------------- /docs/.vuepress/public/scripts/marketing.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated from FairwindsOps/documentation-template 3 | * DO NOT EDIT MANUALLY 4 | */ 5 | 6 | var llcookieless = true; 7 | var sf14gv = 32793; 8 | (function() { 9 | var sf14g = document.createElement('script'); 10 | sf14g.src = 'https://lltrck.com/lt-v2.min.js'; 11 | var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(sf14g, s); 12 | })(); 13 | 14 | (function() { 15 | var gtag = document.createElement('script'); 16 | gtag.src = "https://www.googletagmanager.com/gtag/js?id=G-ZR5M5SRYKY"; 17 | var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(gtag, s); 18 | window.dataLayer = window.dataLayer || []; 19 | function gtag(){dataLayer.push(arguments);} 20 | gtag('js', new Date()); 21 | gtag('config', 'G-ZR5M5SRYKY'); 22 | })(); 23 | 24 | !function(f,b,e,v,n,t,s) 25 | {if(f.fbq)return;n=f.fbq=function(){n.callMethod? 26 | n.callMethod.apply(n,arguments):n.queue.push(arguments)}; 27 | if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0'; 28 | n.queue=[];t=b.createElement(e);t.async=!0; 29 | t.src=v;s=b.getElementsByTagName(e)[0]; 30 | s.parentNode.insertBefore(t,s)}(window,document,'script', 31 | 'https://connect.facebook.net/en_US/fbevents.js'); 32 | fbq('init', '521127644762074'); 33 | fbq('track', 'PageView'); 34 | -------------------------------------------------------------------------------- /docs/.vuepress/public/scripts/modify.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated from FairwindsOps/documentation-template 3 | * DO NOT EDIT MANUALLY 4 | */ 5 | 6 | document.addEventListener("DOMContentLoaded", function(){ 7 | setTimeout(function() { 8 | var link = document.getElementsByClassName('home-link')[0]; 9 | linkClone = link.cloneNode(true); 10 | linkClone.href = "https://fairwinds.com"; 11 | link.setAttribute('target', '_blank'); 12 | link.parentNode.replaceChild(linkClone, link); 13 | }, 1000); 14 | }); 15 | 16 | -------------------------------------------------------------------------------- /docs/.vuepress/styles/index.styl: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated from FairwindsOps/documentation-template 3 | * DO NOT EDIT MANUALLY 4 | */ 5 | 6 | .github-only { 7 | display: none; 8 | } 9 | 10 | .text-primary { 11 | color: $primaryColor; 12 | } 13 | .text-danger { 14 | color: $dangerColor; 15 | } 16 | .text-warning { 17 | color: $warningColor; 18 | } 19 | .text-info { 20 | color: $infoColor; 21 | } 22 | .text-success { 23 | color: $successColor; 24 | } 25 | 26 | blockquote { 27 | border-left: 0.2rem solid $warningColor; 28 | } 29 | blockquote p { 30 | color: $warningColor; 31 | } 32 | 33 | .theme-default-content:not(.custom), 34 | .page-nav, 35 | .page-edit, 36 | footer { 37 | margin: 0 !important; 38 | } 39 | 40 | .theme-default-content:not(.custom) > h2 { 41 | padding-top: 7rem; 42 | } 43 | 44 | .navbar .site-name { 45 | display: none; 46 | } 47 | 48 | .navbar, .navbar .links { 49 | background-color: $primaryColor !important; 50 | } 51 | 52 | .navbar .links a { 53 | color: #fff; 54 | } 55 | .navbar .links a svg { 56 | display: none; 57 | } 58 | 59 | img { 60 | border: 5px solid #f7f7f7; 61 | } 62 | 63 | .no-border img, 64 | img.no-border, 65 | header img { 66 | border: none; 67 | } 68 | 69 | .mini-img { 70 | text-align: center; 71 | } 72 | 73 | .theme-default-content:not(.custom) .mini-img img { 74 | max-width: 300px; 75 | } 76 | 77 | .page { 78 | padding-bottom: 0 !important; 79 | } 80 | -------------------------------------------------------------------------------- /docs/.vuepress/styles/palette.styl: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated from FairwindsOps/documentation-template 3 | * DO NOT EDIT MANUALLY 4 | */ 5 | 6 | 7 | $primaryColor = #23103A 8 | $dangerColor = #A0204C 9 | $warningColor = #FF6C00 10 | $infoColor = #8BD2DC 11 | $successColor = #28a745 12 | 13 | $accentColor = #FF6C00 14 | $textColor = #2c3e50 15 | $borderColor = #eaecef 16 | $codeBgColor = #282c34 17 | $arrowBgColor = #ccc 18 | $badgeTipColor = #42b983 19 | $badgeWarningColor = darken(#ffe564, 35%) 20 | $badgeErrorColor = #DA5961 21 | 22 | // layout 23 | $navbarHeight = 3.6rem 24 | $sidebarWidth = 20rem 25 | $contentWidth = 740px 26 | $homePageWidth = 960px 27 | 28 | // responsive breakpoints 29 | $MQNarrow = 959px 30 | $MQMobile = 719px 31 | $MQMobileNarrow = 419px 32 | 33 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extend: '@vuepress/theme-default' 3 | } 4 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/layouts/Layout.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 28 | 29 | 46 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | meta: 3 | - name: description 4 | content: "Fairwinds Pluto | Documentation" 5 | --- 6 |
7 | Pluto Logo 8 |
9 |

Find Kubernetes resources that have been deprecated

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | Pluto is a utility to help users find [deprecated Kubernetes apiVersions](https://k8s.io/docs/reference/using-api/deprecation-guide/) in their code repositories and their helm releases. 25 | 26 | 27 | ## Purpose 28 | 29 | Kubernetes sometimes deprecates apiVersions. Most notably, a large number of deprecations happened in the [1.16 release](https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/). This is fine, and it's a fairly easy thing to deal with. However, it can be difficult to find all the places where you might have used a version that will be deprecated in your next upgrade. 30 | 31 | You might think, "I'll just ask the api-server to tell me!", but this is fraught with danger. If you ask the api-server to give you `deployments.v1.apps`, and the deployment was deployed as `deployments.v1beta1.extensions`, the api-server will quite happily convert the api version and return a manifest with `apps/v1`. This is fairly well outlined in the discussion in [this issue](https://github.com/kubernetes/kubernetes/issues/58131#issuecomment-356823588). 32 | 33 | So, long story short, finding the places where you have deployed a deprecated apiVersion can be challenging. This is where `pluto` comes in. You can use pluto to check a couple different places where you might have placed a deprecated version: 34 | * Infrastructure-as-Code repos: Pluto can check both static manifests and Helm charts for deprecated apiVersions 35 | * Live Helm releases: Pluto can check both Helm 2 and Helm 3 releases running in your cluster for deprecated apiVersions 36 | 37 | ## Kubernetes Deprecation Policy 38 | 39 | You can read the full policy [here](https://kubernetes.io/docs/reference/using-api/deprecation-policy/) 40 | 41 | Long story short, apiVersions get deprecated, and then they eventually get removed entirely. Pluto differentiates between these two, and will tell you if a version is `DEPRECATED` or `REMOVED` 42 | 43 | ## GitHub Action Usage 44 | Want to use pluto within your GitHub workflows? 45 | 46 | ```yaml 47 | - name: Download Pluto 48 | uses: FairwindsOps/pluto/github-action@master 49 | 50 | - name: Use pluto 51 | run: | 52 | pluto detect-files -d pkg/finder/testdata 53 | ``` 54 | 55 | 56 | ## Join the Fairwinds Open Source Community 57 | 58 | The goal of the Fairwinds Community is to exchange ideas, influence the open source roadmap, 59 | and network with fellow Kubernetes users. 60 | [Chat with us on Slack](https://join.slack.com/t/fairwindscommunity/shared_invite/zt-e3c6vj4l-3lIH6dvKqzWII5fSSFDi1g) 61 | or 62 | [join the user group](https://www.fairwinds.com/open-source-software-user-group) to get involved! 63 | 64 | 65 | Love Fairwinds Open Source? Share your business email and job title and we'll send you a free Fairwinds t-shirt! 66 | 67 | 68 | ## Other Projects from Fairwinds 69 | 70 | Enjoying Pluto? Check out some of our other projects: 71 | * [Polaris](https://github.com/FairwindsOps/Polaris) - Audit, enforce, and build policies for Kubernetes resources, including over 20 built-in checks for best practices 72 | * [Goldilocks](https://github.com/FairwindsOps/Goldilocks) - Right-size your Kubernetes Deployments by compare your memory and CPU settings against actual usage 73 | * [Nova](https://github.com/FairwindsOps/Nova) - Check to see if any of your Helm charts have updates available 74 | * [rbac-manager](https://github.com/FairwindsOps/rbac-manager) - Simplify the management of RBAC in your Kubernetes clusters 75 | 76 | Or [check out the full list](https://www.fairwinds.com/open-source-software?utm_source=pluto&utm_medium=pluto&utm_campaign=pluto) 77 | ## Fairwinds Insights 78 | If you're interested in running Pluto in multiple clusters, 79 | tracking the results over time, integrating with Slack, Datadog, and Jira, 80 | or unlocking other functionality, check out 81 | [Fairwinds Insights](https://www.fairwinds.com/pluto-user-insights-demo?utm_source=pluto&utm_medium=pluto&utm_campaign=pluto), 82 | a platform for auditing and enforcing policy in Kubernetes clusters. 83 | 84 | 85 | Fairwinds Insights 86 | 87 | -------------------------------------------------------------------------------- /docs/advanced.md: -------------------------------------------------------------------------------- 1 | --- 2 | meta: 3 | - name: description 4 | content: "Fairwinds Pluto | Documentation on customizing behavior and output" 5 | --- 6 | 7 | # Advanced Usage Options 8 | 9 | Pluto has a wide variety of options that can be used to customize behavior and output. 10 | 11 | ## Display Options 12 | 13 | In addition to the standard output, Pluto can output in the following modes: Wide, YAML, JSON, CSV or Markdown. 14 | 15 | `--no-headers` option hides headers in the outputs for Text, CSV and Markdown output. 16 | 17 | ### Wide 18 | 19 | The wide output provides more information about when an apiVersion was removed or deprecated. 20 | 21 | ```shell 22 | $ pluto detect-helm -owide 23 | NAME NAMESPACE KIND VERSION REPLACEMENT DEPRECATED DEPRECATED IN REMOVED REMOVED IN 24 | cert-manager/cert-manager-webhook cert-manager MutatingWebhookConfiguration admissionregistration.k8s.io/v1beta1 admissionregistration.k8s.io/v1 true v1.16.0 false v1.19.0 25 | ``` 26 | 27 | ### JSON 28 | 29 | ```shell 30 | $ pluto detect-helm -ojson | jq . 31 | { 32 | "items": [ 33 | { 34 | "name": "cert-manager/cert-manager-webhook", 35 | "namespace": "cert-manager", 36 | "api": { 37 | "version": "admissionregistration.k8s.io/v1beta1", 38 | "kind": "MutatingWebhookConfiguration", 39 | "deprecated-in": "v1.16.0", 40 | "removed-in": "v1.19.0", 41 | "replacement-api": "admissionregistration.k8s.io/v1", 42 | "component": "k8s" 43 | }, 44 | "deprecated": true, 45 | "removed": false 46 | } 47 | ], 48 | "target-versions": { 49 | "cert-manager": "v0.15.1", 50 | "istio": "v1.6.0", 51 | "k8s": "v1.16.0" 52 | } 53 | } 54 | 55 | ``` 56 | 57 | ### YAML 58 | 59 | ```yaml 60 | items: 61 | - name: cert-manager/cert-manager-webhook 62 | namespace: cert-manager 63 | api: 64 | version: admissionregistration.k8s.io/v1beta1 65 | kind: MutatingWebhookConfiguration 66 | deprecated-in: v1.16.0 67 | removed-in: v1.19.0 68 | replacement-api: admissionregistration.k8s.io/v1 69 | component: k8s 70 | deprecated: true 71 | removed: false 72 | target-versions: 73 | cert-manager: v0.15.1 74 | istio: v1.6.0 75 | k8s: v1.16.0 76 | ``` 77 | 78 | ### Custom columns 79 | 80 | ```shell 81 | $ pluto detect-helm -ocustom --columns NAMESPACE,NAME 82 | NAME NAMESPACE 83 | cert-manager/cert-manager-webhook cert-manager 84 | ``` 85 | 86 | NOTE: Any columns with spaces will need to be escaped or quoted, such as `DEPRECATED\ IN` or `"DEPRECATED IN"` 87 | 88 | ### Markdown 89 | 90 | ```shell 91 | $ pluto detect-files -o markdown 92 | | NAME | NAMESPACE | KIND | VERSION | REPLACEMENT | DEPRECATED | DEPRECATED IN | REMOVED | REMOVED IN | 93 | |-----------|----------------|------------|--------------------|-------------|------------|---------------|---------|------------| 94 | | utilities | | Deployment | extensions/v1beta1 | apps/v1 | true | v1.9.0 | true | v1.16.0 | 95 | | utilities | json-namespace | Deployment | extensions/v1beta1 | apps/v1 | true | v1.9.0 | true | v1.16.0 | 96 | | utilities | yaml-namespace | Deployment | extensions/v1beta1 | apps/v1 | true | v1.9.0 | true | v1.16.0 | 97 | ``` 98 | 99 | ```shell 100 | $ pluto detect-files -o markdown --columns NAMESPACE,NAME,DEPRECATED IN,DEPRECATED,REPLACEMENT,VERSION,KIND,COMPONENT,FILEPATH 101 | | NAME | NAMESPACE | KIND | VERSION | REPLACEMENT | DEPRECATED | DEPRECATED IN | COMPONENT | FILEPATH | 102 | |---------------|-----------------|------------|--------------------|-------------|------------|---------------|-----------|--------------| 103 | | some name one | pluto-namespace | Deployment | extensions/v1beta1 | apps/v1 | true | v1.9.0 | foo | path-to-file | 104 | | some name two | | Deployment | extensions/v1beta1 | apps/v1 | true | v1.9.0 | foo | | 105 | ``` 106 | 107 | ### CSV 108 | 109 | ```shell 110 | $ pluto detect-helm -o csv 111 | NAME,NAMESPACE,KIND,VERSION,REPLACEMENT,DEPRECATED,DEPRECATED IN,REMOVED,REMOVED IN 112 | deploy1,pluto-namespace,Deployment,extensions/v1beta1,apps/v1,true,v1.9.0,true,v1.16.0 113 | deploy1,other-namespace,Deployment,extensions/v1beta1,apps/v1,true,v1.9.0,true,v1.16.0 114 | ``` 115 | 116 | ```shell 117 | $ pluto detect-helm -o csv --columns "KIND,NAMESPACE,NAME,VERSION,REPLACEMENT" 118 | KIND,NAMESPACE,NAME,VERSION,REPLACEMENT 119 | Deployment,pluto-namespace,deploy1,extensions/v1beta1,apps/v1 120 | Deployment,other-namespace,deploy1,extensions/v1beta1,apps/v1 121 | ``` 122 | 123 | ## CI Pipelines 124 | 125 | Pluto has specific exit codes that is uses to indicate certain results: 126 | 127 | - Exit Code 1 - An error. A message will be displayed 128 | - Exit Code 2 - A deprecated apiVersion has been found. 129 | - Exit Code 3 - A removed apiVersion has been found. 130 | - Exit Code 4 - A replacement apiVersion is unavailable in the target version 131 | 132 | If you wish to bypass the generation of exit codes 2 and 3, you may do so with two different flags: 133 | 134 | ```shell 135 | --ignore-deprecations Ignore the default behavior to exit 2 if deprecated apiVersions are found. 136 | --ignore-removals Ignore the default behavior to exit 3 if removed apiVersions are found. 137 | --ignore-unavailable-replacements Ignore the default behavior to exit 4 if deprecated but unavailable apiVersions are found. 138 | ``` 139 | 140 | ## Target Versions 141 | 142 | Pluto was originally designed with deprecations related to Kubernetes v1.16.0. As more deprecations are introduced, we will try to keep it updated. Community contributions are welcome in this area. 143 | 144 | Currently, Pluto defaults to a targetVersion of v1.22.0, however this is configurable (please continue reading) 145 | 146 | You can target the version you are concerned with by using the `--target-versions` or `-t` flag. You must pass the `component=version`, and the version must begin with a `v` (this is a limitation of the semver library we are using to verify). 147 | 148 | For example: 149 | 150 | ```shell 151 | $ pluto detect-helm --target-versions k8s=v1.15.0 152 | No output to display 153 | 154 | $ echo $? 155 | 0 156 | ``` 157 | 158 | Notice that there is no output, despite the fact that we might have recognized apiVersions present in the cluster that are not yet deprecated or removed in v1.15.0. This particular run exited 0. 159 | 160 | ## Components 161 | 162 | By default Pluto will scan for all components in the versionsList that it can find. If you wish to only see deprecations for a specific component, you can use the `--components` flag to specify a list. 163 | 164 | ## Only Show Removed 165 | 166 | If you are targeting an upgrade, you may only wish to see apiVersions that have been `removed` rather than both `deprecated` and `removed`. You can pass the `--only-show-removed` or `-r` flag for this. It will remove any detections that are deprecated, but not yet removed. This will affect the exit code of the command as well as the json and yaml output. 167 | 168 | ## Adding Custom Version Checks 169 | 170 | If you want to check additional apiVersions and/or types, you can pass an additional file with the `--additional-versions` or `-f` flag. 171 | 172 | The file should look something like this: 173 | 174 | ```yaml 175 | target-versions: 176 | custom: v1.0.0 177 | deprecated-versions: 178 | - version: someother/v1beta1 179 | kind: AnotherCRD 180 | deprecated-in: v1.9.0 181 | removed-in: v1.16.0 182 | replacement-api: apps/v1 183 | component: custom 184 | ``` 185 | 186 | You can test that it's working by using `list-versions`: 187 | 188 | ```shell 189 | $ pluto list-versions -f new.yaml 190 | KIND NAME DEPRECATED IN REMOVED IN REPLACEMENT COMPONENT 191 | AnotherCRD someother/v1beta1 v1.9.0 v1.16.0 apps/v1 custom 192 | ``` 193 | 194 | _NOTE: This output is truncated to show only the additional version. Normally this will include the defaults as well_ 195 | 196 | The `target-versions` field in this custom file will set the default target version for that component. You can still override this with `--target-versions custom=vX.X.X` when you run Pluto. 197 | 198 | Please note that we do not allow overriding anything contained in the default `versions.yaml` that Pluto uses. 199 | 200 | ## Kube Context or kubeconfig 201 | 202 | When doing helm or apiVersion detection, you may want to use the `--kube-context` or `--kubeconfig` flags to specify a particular context, or a specific file path, that you wish to use for your kubeconfig. 203 | 204 | ## Environment Variables 205 | 206 | For easier use, you can specify flags by using environment variables. 207 | 208 | ### Precedence 209 | 210 | When you run a command with a flag, the command line option takes precedence over the environment variable. 211 | 212 | ### Supported Environment Variables 213 | 214 | All environment variables are prefixed with `PLUTO` and use `_` instead of `-`. 215 | 216 | | Flag | ENV variable | 217 | | --------------------- | ------------------------- | 218 | | --ignore-deprecations | PLUTO_IGNORE_DEPRECATIONS | 219 | | --ignore-removals | PLUTO_IGNORE_REMOVALS | 220 | | --target-versions | PLUTO_TARGET_VERSIONS | 221 | | --only-show-removed | PLUTO_ONLY_SHOW_REMOVED | 222 | | --additional-versions | PLUTO_ADDITIONAL_VERSIONS | 223 | | --output | PLUTO_OUTPUT | 224 | | --columns | PLUTO_COLUMNS | 225 | | --components | PLUTO_COMPONENTS | 226 | | --no-headers | PLUTO_NO_HEADERS | 227 | | --no-footer | PLUTO_NO_FOOTER | 228 | -------------------------------------------------------------------------------- /docs/contributing/code-of-conduct.md: -------------------------------------------------------------------------------- 1 | --- 2 | meta: 3 | - name: description 4 | content: "Fairwinds Pluto | Contribution Code of Conduct" 5 | --- 6 | # Contributor Covenant Code of Conduct 7 | 8 | ## Our Pledge 9 | 10 | In the interest of fostering an open and welcoming environment, we as 11 | contributors and maintainers pledge to making participation in our project and 12 | our community a harassment-free experience for everyone, regardless of age, body 13 | size, disability, ethnicity, gender identity and expression, level of experience, 14 | nationality, personal appearance, race, religion, or sexual identity and 15 | orientation. 16 | 17 | ## Our Standards 18 | 19 | Examples of behavior that contributes to creating a positive environment 20 | include: 21 | 22 | * Using welcoming and inclusive language 23 | * Being respectful of differing viewpoints and experiences 24 | * Gracefully accepting constructive criticism 25 | * Focusing on what is best for the community 26 | * Showing empathy towards other community members 27 | 28 | Examples of unacceptable behavior by participants include: 29 | 30 | * The use of sexualized language or imagery and unwelcome sexual attention or 31 | advances 32 | * Trolling, insulting/derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or electronic 35 | address, without explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Our Responsibilities 40 | 41 | Project maintainers are responsible for clarifying the standards of acceptable 42 | behavior and are expected to take appropriate and fair corrective action in 43 | response to any instances of unacceptable behavior. 44 | 45 | Project maintainers have the right and responsibility to remove, edit, or 46 | reject comments, commits, code, wiki edits, issues, and other contributions 47 | that are not aligned to this Code of Conduct, or to ban temporarily or 48 | permanently any contributor for other behaviors that they deem inappropriate, 49 | threatening, offensive, or harmful. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies both within project spaces and in public spaces 54 | when an individual is representing the project or its community. Examples of 55 | representing a project or community include using an official project e-mail 56 | address, posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. Representation of a project may be 58 | further defined and clarified by project maintainers. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 64 | complaints will be reviewed and investigated and will result in a response that 65 | is deemed necessary and appropriate to the circumstances. The project team is 66 | obligated to maintain confidentiality with regard to the reporter of an incident. 67 | Further details of specific enforcement policies may be posted separately. 68 | 69 | Project maintainers who do not follow or enforce the Code of Conduct in good 70 | faith may face temporary or permanent repercussions as determined by other 71 | members of the project's leadership. 72 | 73 | ## Attribution 74 | 75 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 76 | available at [http://contributor-covenant.org/version/1/4][version] 77 | 78 | [homepage]: http://contributor-covenant.org 79 | [version]: http://contributor-covenant.org/version/1/4/ 80 | -------------------------------------------------------------------------------- /docs/contributing/guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | meta: 3 | - name: description 4 | content: "Fairwinds Pluto | Contribution Guidelines" 5 | --- 6 | # Contributing Guide 7 | 8 | Issues, whether bugs, tasks, or feature requests are essential for keeping pluto great. We believe it should be as easy as possible to contribute changes that get things working in your environment. There are a few guidelines that we need contributors to follow so that we can keep on top of things. 9 | 10 | ## Code of Conduct 11 | 12 | This project adheres to a [code of conduct](code-of-conduct.md). Please review this document before contributing to this project. 13 | 14 | ## Sign the CLA 15 | Before you can contribute, you will need to sign the [Contributor License Agreement](https://cla-assistant.io/fairwindsops/pluto). 16 | 17 | ## Project Structure 18 | 19 | ### CLI 20 | pluto is a relatively simple cobra cli tool that helps deal with deprecated api versions in Kubernetes. The [/cmd](https://github.com/FairwindsOps/pluto/tree/master/cmd) folder contains the flags and other cobra config, while the [/pkg](https://github.com/FairwindsOps/pluto/tree/master/pkg) folder has the various packages. 21 | 22 | ### API 23 | 24 | This contains the various structs and helper functions to deal with Kubernetes objects and their apiVersions. It assumes that any file we care about will have a `Kind` and an `apiVersion`. 25 | 26 | ### Finder 27 | 28 | This package is for dealing with a set of static files and analyzing the apiVersions in them. It can search through a directory and find any files that conform to the specifications of the versions package. 29 | 30 | ### Helm 31 | 32 | This package deals with finding helm release manifests that are in your running Kubernetes cluster. 33 | 34 | ## Versions Updates 35 | 36 | The versions.yaml file contains the source of truth for the standard list of deprecated versions that Pluto can deal with. If you wish to update it or change the default targetVersions, it is easiest to use Pluto to do that. 37 | 38 | Just create a deprecated-versions yaml file that has the additional versions and/or target versions you wish to edit. Here's an example: 39 | 40 | ``` 41 | target-versions: 42 | other: v1.0.0 43 | deprecated-versions: 44 | - version: someother/v1beta1 45 | kind: AnotherCRD 46 | deprecated-in: v1.9.0 47 | removed-in: v1.16.0 48 | replacement-api: apps/v1 49 | component: new-component 50 | ``` 51 | 52 | Put this somewhere temporary, like /tmp/new.yaml. Then you can run: 53 | 54 | ``` 55 | pluto list-versions -f /tmp/new.yaml -oyaml > versions.yaml 56 | ``` 57 | 58 | This will ensure that you have parsable valid versions, and it should make patching easier. 59 | 60 | ## Getting Started 61 | 62 | We label issues with the ["good first issue" tag](https://github.com/FairwindsOps/pluto/labels/good%20first%20issue) if we believe they'll be a good starting point for new contributors. If you're interested in working on an issue, please start a conversation on that issue, and we can help answer any questions as they come up. 63 | 64 | ## Setting Up Your Development Environment 65 | ### Prerequisites 66 | * A properly configured Golang environment with Go 1.13 or higher 67 | * Install `pkger` - [documentation](https://github.com/markbates/pkger#cli) 68 | 69 | ### Installation 70 | * Clone the project with `go get github.com/fairwindsops/pluto` 71 | * Change into the pluto directory which is installed at `$GOPATH/src/github.com/fairwindsops/pluto` 72 | * Use `make build` to build the binary locally. 73 | * Use `make test` to run the tests and generate a coverage report. 74 | 75 | ## Creating a New Issue 76 | 77 | If you've encountered an issue that is not already reported, please create an issue that contains the following: 78 | 79 | - Clear description of the issue 80 | - Steps to reproduce it 81 | - Appropriate labels 82 | 83 | ## Creating a Pull Request 84 | 85 | Each new pull request should: 86 | 87 | - Reference any related issues 88 | - Add tests that show the issues have been solved 89 | - Pass existing tests and linting 90 | - Contain a clear indication of if they're ready for review or a work in progress 91 | - Be up to date and/or rebased on the master branch 92 | 93 | ## Orb development 94 | 95 | There is a Makefile that can assist in validating and testing the orb locally. See the commands there for more info. 96 | 97 | ## Creating a new release 98 | 99 | Push a new tag and Goreleaser will take care of the rest. 100 | 101 | ## Pre-commit 102 | 103 | This repo contains a pre-commit file for use with [pre-commit](https://pre-commit.com/). Just run `pre-commit install` and you will have the hooks. 104 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | --- 2 | meta: 3 | - name: description 4 | content: "Fairwinds Pluto | Documentation FAQ" 5 | --- 6 | ## Frequently Asked Questions 7 | 8 | ### I updated my deployment method to use the new API version and Pluto doesn't report anything but kubectl still shows the old API. What gives? 9 | 10 | See above in the [Purpose](/#purpose) section of this doc. Kubectl is likely lying to you because it only tells you what the default is for the given kubernetes version even if an object was deployed with a newer API version. 11 | 12 | ### I don't use helm, how can I do in cluster checks? 13 | 14 | Currently, the only in-cluster check we are confident in supporting is helm. If your deployment method can generate yaml manifests for kubernetes, you should be able to use the `detect` or `detect-files` functionality described below after the manifest files have been generated. 15 | 16 | ### I updated the API version of an object, but pluto still reports that the apiVersion needs to be updated. 17 | 18 | Pluto looks at the API Versions of objects in releases that are in a `Deployed` state, and Helm has an issue where it might list old revisions of a release as still being in a `Deployed` state. To fix this, look at the release revision history with `helm history `, and determine if older releases still show a `Deployed` state. If so, delete the Helm release secret(s) associated with the revision number(s). For example, `kubectl delete secret sh.helm.release.v1.my-release.v10` where `10` corresponds to the release number. Then run Pluto again to see if the object has been removed from the report. 19 | 20 | ### Why API is version check on a live cluster using the "last-applied-configuration" annotation not reliable? 21 | 22 | When using `--detect-api-resources` or `--detect-all-in-cluster`, there are some potential issues to be aware of: 23 | 24 | * The annotation `kubectl.kubernetes.io/last-applied-configuration` on an object in your cluster holds the API version by which it was created. In fact, others have pointed out that updating the same object with `kubectl patch` will **remove** the annotation. Hence this is not a reliable method to detect deprecated API's from a live cluster. 25 | * You may get false positives in the first change after fixing the apiVersion. Please see [this issue](https://github.com/FairwindsOps/pluto/issues/495) for more details. 26 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | meta: 3 | - name: description 4 | content: "Fairwinds Pluto | Installation documentation" 5 | --- 6 | # Installation 7 | 8 | ## asdf 9 | 10 | We have an [asdf](https://asdf-vm.com/#/) plugin [here](https://github.com/FairwindsOps/asdf-pluto). You can install with: 11 | 12 | ``` 13 | asdf plugin-add pluto 14 | asdf list-all pluto 15 | asdf install pluto 16 | asdf local pluto 17 | ``` 18 | 19 | ## Binary 20 | 21 | Install the binary from our [releases](https://github.com/FairwindsOps/pluto/releases) page. 22 | 23 | ## Homebrew Tap 24 | 25 | ``` 26 | brew install FairwindsOps/tap/pluto 27 | ``` 28 | 29 | ## Scoop (Windows) 30 | Note: This is not maintained by Fairwinds, but should stay up to date with future releases. 31 | 32 | ``` 33 | scoop install pluto 34 | ``` 35 | 36 | # Verify Artifacts 37 | 38 | Fairwinds signs the Pluto docker image and the checksums file with [cosign](https://github.com/sigstore/cosign). Our public key is available at https://artifacts.fairwinds.com/cosign.pub 39 | 40 | You can verify the checksums file from the [releases](https://github.com/FairwindsOps/pluto/releases) page with the following command: 41 | 42 | ``` 43 | cosign verify-blob checksums.txt --signature=checksums.txt.sig --key https://artifacts.fairwinds.com/cosign.pub 44 | ``` 45 | 46 | Verifying docker images is even easier: 47 | 48 | ``` 49 | cosign verify us-docker.pkg.dev/fairwinds-ops/oss/pluto:v5 --key https://artifacts.fairwinds.com/cosign.pub 50 | ``` 51 | 52 | -------------------------------------------------------------------------------- /docs/main-metadata.md: -------------------------------------------------------------------------------- 1 | --- 2 | meta: 3 | - name: description 4 | content: "Fairwinds Pluto | Documentation" 5 | --- 6 | -------------------------------------------------------------------------------- /docs/orb.md: -------------------------------------------------------------------------------- 1 | --- 2 | meta: 3 | - name: description 4 | content: "Fairwinds Pluto | Fairwinds publishes an orb called fairwinds/pluto to provide easier configuration inside of CircleCI." 5 | --- 6 | # Orb 7 | 8 | CircleCI has introduced the concept of reusable config in the form of [Orbs](https://circleci.com/orbs/). As of pluto v5.1, Fairwinds publishes an orb called [`fairwinds/pluto`](https://circleci.com/orbs/registry/orb/fairwinds/pluto) in order to provide easier configuration inside of CircleCI. 9 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "", 3 | "bugs": { 4 | "url": "https://github.com/FairwindsOps/insights-docs/issues" 5 | }, 6 | "dependencies": { 7 | "vuepress-plugin-check-md": "0.0.2" 8 | }, 9 | "description": "A repository with a Vuepress template for Fairwinds projects", 10 | "devDependencies": { 11 | "vuepress": "^1.9.7", 12 | "vuepress-plugin-clean-urls": "^1.1.1", 13 | "vuepress-plugin-redirect": "^1.2.5" 14 | }, 15 | "directories": { 16 | "doc": "docs" 17 | }, 18 | "homepage": "https://github.com/FairwindsOps/insights-docs#readme", 19 | "license": "MIT", 20 | "main": "index.js", 21 | "name": "fairwinds-docs-template", 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/FairwindsOps/insights-docs.git" 25 | }, 26 | "scripts": { 27 | "build": "npm run build:readme && npm run build:docs", 28 | "build:docs": "vuepress build -d ../dist/", 29 | "build:metadata": "cat main-metadata.md > README.md || true", 30 | "build:readme": "npm run build:metadata && cat ../README.md | grep -v 'ocumentation' | sed \"s/https:\\/\\/\\w\\+.docs.fairwinds.com//g\" >> README.md", 31 | "check-links": "vuepress check-md", 32 | "serve": "npm run build:readme && vuepress dev --port 3003", 33 | "vuepress": "vuepress" 34 | }, 35 | "version": "0.0.1" 36 | } 37 | -------------------------------------------------------------------------------- /docs/quickstart.md: -------------------------------------------------------------------------------- 1 | --- 2 | meta: 3 | - name: description 4 | content: "Fairwinds Pluto | Quickstart Documentation" 5 | --- 6 | # QuickStart 7 | 8 | First, follow the install instructions to install pluto. 9 | 10 | ## File Detection in a Directory 11 | 12 | Run `pluto detect-files -d ` 13 | 14 | You should see an output something like: 15 | 16 | ``` 17 | $ pluto detect-files -d pkg/finder/testdata 18 | NAME KIND VERSION REPLACEMENT REMOVED DEPRECATED 19 | utilities Deployment extensions/v1beta1 apps/v1 true true 20 | utilities Deployment extensions/v1beta1 apps/v1 true true 21 | ``` 22 | 23 | This indicates that we have two files in our directory that have deprecated apiVersions. This will need to be fixed prior to a 1.16 upgrade. 24 | 25 | ### Helm Detection (in-cluster) 26 | 27 | ``` 28 | $ pluto detect-helm -owide 29 | NAME NAMESPACE KIND VERSION REPLACEMENT DEPRECATED DEPRECATED IN REMOVED REMOVED IN 30 | cert-manager/cert-manager-webhook cert-manager MutatingWebhookConfiguration admissionregistration.k8s.io/v1beta1 admissionregistration.k8s.io/v1 true v1.16.0 false v1.19.0 31 | ``` 32 | 33 | This indicates that the StatefulSet audit-dashboard-prod-rabbitmq-ha was deployed with apps/v1beta1 which is deprecated in 1.16 34 | 35 | 36 | If you want to see information for a single namespace, you can pass the `--namespace` or `-n` flag to restrict the output. 37 | 38 | ``` 39 | $ pluto detect-helm -n cert-manager -owide 40 | NAME NAMESPACE KIND VERSION REPLACEMENT DEPRECATED DEPRECATED IN REMOVED REMOVED IN 41 | cert-manager/cert-manager-webhook cert-manager MutatingWebhookConfiguration admissionregistration.k8s.io/v1beta1 admissionregistration.k8s.io/v1 true v1.16.0 false v1.19.0 42 | ``` 43 | 44 | ### Helm Chart Checking (local files) 45 | 46 | You can run `helm template | pluto detect -` 47 | 48 | This will output something like so: 49 | 50 | ``` 51 | $ helm template e2e/tests/assets/helm3chart | pluto detect - 52 | KIND VERSION DEPRECATED DEPRECATED IN RESOURCE NAME 53 | Deployment extensions/v1beta1 true v1.16.0 RELEASE-NAME-helm3chart-v1beta1 54 | ``` 55 | 56 | ### API resources (in-cluster) 57 | ``` 58 | $ pluto detect-api-resources -owide 59 | NAME NAMESPACE KIND VERSION REPLACEMENT DEPRECATED DEPRECATED IN REMOVED REMOVED IN 60 | psp PodSecurityPolicy policy/v1beta1 true v1.21.0 false v1.25.0 61 | ``` 62 | 63 | This indicates that the PodSecurityPolicy was deployed with apps/v1beta1 which is deprecated in 1.21 64 | 65 | ### helm and API resources (in-cluster) 66 | 67 | ``` 68 | $ pluto detect-all-in-cluster -o wide 2>/dev/null 69 | NAME NAMESPACE KIND VERSION REPLACEMENT DEPRECATED DEPRECATED IN REMOVED REMOVED IN 70 | testing/viahelm viahelm Ingress networking.k8s.io/v1beta1 networking.k8s.io/v1 true v1.19.0 true v1.22.0 71 | webapp default Ingress networking.k8s.io/v1beta1 networking.k8s.io/v1 true v1.19.0 true v1.22.0 72 | eks.privileged PodSecurityPolicy policy/v1beta1 true v1.21.0 false v1.25.0 73 | ``` 74 | 75 | This combines all available in-cluster detections, showing results from Helm releases and API resources. 76 | -------------------------------------------------------------------------------- /e2e/pre.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | go get github.com/markbates/pkger/cmd/pkger 6 | make build 7 | docker cp ./ e2e-command-runner:/pluto 8 | -------------------------------------------------------------------------------- /e2e/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | 6 | printf "\n\n" 7 | echo "***************************" 8 | echo "** Install and Run Venom **" 9 | echo "***************************" 10 | printf "\n\n" 11 | 12 | curl -LO https://github.com/ovh/venom/releases/download/v1.1.0/venom.linux-amd64 13 | mv venom.linux-amd64 /usr/local/bin/venom 14 | chmod +x /usr/local/bin/venom 15 | 16 | cp /pluto/pluto /usr/local/bin/pluto 17 | 18 | cd /pluto/e2e 19 | mkdir -p /tmp/test-results 20 | helm delete -n kube-system hostpath-provisioner || true 21 | venom run tests/* --output-dir=/tmp/test-results 22 | exit $? 23 | -------------------------------------------------------------------------------- /e2e/tests/00_static_files.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | name: "Static Code Files (including helm)" 3 | testcases: 4 | - name: static files 5 | steps: 6 | - script: pluto detect-files -d assets/ --target-versions k8s=v1.15.0 7 | assertions: 8 | - result.code ShouldEqual 2 9 | - result.systemout ShouldContainSubstring "NAME KIND VERSION REPLACEMENT REMOVED DEPRECATED" 10 | - result.systemout ShouldContainSubstring "utilities Deployment extensions/v1beta1 apps/v1 false true" 11 | - result.systemout ShouldNotContainSubstring "utilities Deployment apps/v1 false false" 12 | 13 | - name: static files only show removed 14 | steps: 15 | - script: pluto detect-files -d assets/ --target-versions k8s=v1.15.0 -r 16 | assertions: 17 | - result.code ShouldEqual 0 18 | - result.systemout ShouldEqual "No output to display" 19 | 20 | - name: helm template 21 | steps: 22 | - script: helm template assets/chart | pluto detect - 23 | assertions: 24 | - result.systemout ShouldContainSubstring "NAME KIND VERSION REPLACEMENT REMOVED DEPRECATED" 25 | - result.systemout ShouldContainSubstring "release-name-helm3chart-v1beta1 Deployment extensions/v1beta1 apps/v1 true true" 26 | - result.systemout ShouldNotContainSubstring "release-name-helm3chart Deployment apps/v1 false false" 27 | 28 | - name: static files no deprecated 29 | steps: 30 | - script: pluto detect-files -d assets/non-deprecated --target-versions k8s=v1.16.0 31 | assertions: 32 | - result.code ShouldEqual 0 33 | - result.systemout ShouldContainSubstring "There were no resources found with known deprecated apiVersions." 34 | 35 | - name: static files target v1.8.0 not removed or deprecated 36 | steps: 37 | - script: pluto detect-files -d assets/deprecated116 --target-versions k8s=v1.8.0 38 | assertions: 39 | - result.code ShouldEqual 0 40 | - result.systemout ShouldContainSubstring "No output to display" 41 | 42 | - name: static files wide 43 | steps: 44 | - script: pluto detect-files -d assets/ -owide 45 | assertions: 46 | - result.code ShouldEqual 3 47 | - result.systemout ShouldContainSubstring "NAME NAMESPACE KIND VERSION REPLACEMENT DEPRECATED DEPRECATED IN REMOVED REMOVED IN" 48 | - result.systemout ShouldContainSubstring "utilities Deployment extensions/v1beta1 apps/v1 true v1.9.0 true v1.16.0" 49 | 50 | - name: static files custom 51 | steps: 52 | - script: pluto detect-files -d assets/ -o custom --columns "NAME,NAMESPACE,VERSION,KIND,DEPRECATED,DEPRECATED IN,component" 53 | assertions: 54 | - result.code ShouldEqual 3 55 | - result.systemout ShouldContainSubstring "NAME NAMESPACE VERSION KIND DEPRECATED DEPRECATED IN COMPONENT" 56 | - result.systemout ShouldContainSubstring "utilities extensions/v1beta1 Deployment true v1.9.0 k8s" 57 | 58 | - name: static files show file path 59 | steps: 60 | - script: pluto detect-files -d assets/ -o custom --columns "filepath" 61 | assertions: 62 | - result.code ShouldEqual 3 63 | - result.systemout ShouldContainSubstring "FILEPATH" 64 | - result.systemout ShouldContainSubstring "tests/assets/deprecated116/deployment-extensions-v1beta1.yaml" 65 | 66 | - name: static files no output due to no matching components 67 | steps: 68 | - script: pluto detect-files -d assets/ --components=istio 69 | assertions: 70 | - result.code ShouldEqual 0 71 | - result.systemout ShouldEqual "No output to display" 72 | 73 | - name: static files in a List 74 | steps: 75 | - script: pluto detect-files -d assets/list --target-versions k8s=v1.15.0 76 | assertions: 77 | - result.code ShouldEqual 2 78 | - result.systemout ShouldContainSubstring "NAME KIND VERSION REPLACEMENT REMOVED DEPRECATED" 79 | - result.systemout ShouldContainSubstring "utilities Deployment extensions/v1beta1 apps/v1 false true" 80 | - result.systemout ShouldNotContainSubstring "utilities Deployment apps/v1 false false" 81 | 82 | - name: exit code three for single file 83 | steps: 84 | - script: pluto detect assets/deprecated116/deployment-extensions-v1beta1.yaml 85 | assertions: 86 | - result.code ShouldEqual 3 87 | - result.systemout ShouldContainSubstring "extensions/v1beta1" -------------------------------------------------------------------------------- /e2e/tests/01_helm-detect-3.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | name: "Static Code Files (including helm)" 3 | testcases: 4 | - name: install helm3 assets 5 | steps: 6 | - script: | 7 | kubectl create ns demo2 8 | kubectl apply -f assets/helm3 9 | assertions: 10 | - result.code ShouldEqual 0 11 | 12 | - name: helm detect show all 13 | steps: 14 | - script: pluto detect-helm --target-versions k8s=v1.16.0 15 | assertions: 16 | - result.code ShouldEqual 3 17 | - result.systemout ShouldContainSubstring "NAME KIND VERSION REPLACEMENT REMOVED DEPRECATED" 18 | - result.systemout ShouldContainSubstring "test/test-helm3chart-v1beta1 Deployment extensions/v1beta1 apps/v1 true true" 19 | 20 | - name: helm detect 21 | steps: 22 | - script: pluto detect-helm --target-versions k8s=v1.16.0 23 | assertions: 24 | - result.code ShouldEqual 3 25 | - result.systemout ShouldContainSubstring "NAME KIND VERSION REPLACEMENT REMOVED DEPRECATED" 26 | - result.systemout ShouldContainSubstring "test/test-helm3chart-v1beta1 Deployment extensions/v1beta1 apps/v1 true true" 27 | - result.systemout ShouldNotContainSubstring "test/test-helm3chart Deployment apps/v1 false false" 28 | 29 | - name: helm detect ignore deprecations 30 | steps: 31 | - script: pluto detect-helm --ignore-deprecations --target-versions k8s=v1.15.0 32 | assertions: 33 | - result.code ShouldEqual 0 34 | - result.systemout ShouldContainSubstring "NAME KIND VERSION REPLACEMENT REMOVED DEPRECATED" 35 | - result.systemout ShouldContainSubstring "test/test-helm3chart-v1beta1 Deployment extensions/v1beta1 apps/v1 false true" 36 | - result.systemout ShouldNotContainSubstring "test/test-helm3chart Deployment apps/v1 false false" 37 | 38 | - name: helm detect show all ignore removals 39 | steps: 40 | - script: pluto detect-helm --ignore-removals --ignore-deprecations --target-versions k8s=v1.16.0 41 | assertions: 42 | - result.code ShouldEqual 0 43 | - result.systemout ShouldContainSubstring "NAME KIND VERSION REPLACEMENT REMOVED DEPRECATED" 44 | - result.systemout ShouldContainSubstring "test/test-helm3chart-v1beta1 Deployment extensions/v1beta1 apps/v1 true true" 45 | 46 | - name: helm detect -owide 47 | steps: 48 | - script: pluto detect-helm -owide --target-versions k8s=v1.16.0 49 | assertions: 50 | - result.code ShouldEqual 3 51 | - result.systemout ShouldContainSubstring "NAME NAMESPACE KIND VERSION REPLACEMENT DEPRECATED DEPRECATED IN REMOVED REMOVED IN" 52 | - result.systemout ShouldContainSubstring "test/test-helm3chart-v1beta1 default Deployment extensions/v1beta1 apps/v1 true v1.9.0 true v1.16.0" 53 | - result.systemout ShouldContainSubstring "test/test-helm3chart-v1beta1 demo2 Deployment extensions/v1beta1 apps/v1 true v1.9.0 true v1.16.0" 54 | 55 | - name: helm detect -ojson 56 | steps: 57 | - script: pluto detect-helm -ojson --target-versions k8s=v1.16.0 58 | assertions: 59 | - result.code ShouldEqual 3 60 | - result.systemout ShouldEqual {"items":[{"name":"test/test-helm3chart-v1beta1","namespace":"default","api":{"version":"extensions/v1beta1","kind":"Deployment","deprecated-in":"v1.9.0","removed-in":"v1.16.0","replacement-api":"apps/v1","replacement-available-in":"v1.9.0","component":"k8s"},"deprecated":true,"removed":true,"replacementAvailable":true},{"name":"test/test-helm3chart-v1beta1","namespace":"demo2","api":{"version":"extensions/v1beta1","kind":"Deployment","deprecated-in":"v1.9.0","removed-in":"v1.16.0","replacement-api":"apps/v1","replacement-available-in":"v1.9.0","component":"k8s"},"deprecated":true,"removed":true,"replacementAvailable":true}],"target-versions":{"cert-manager":"v1.5.3","istio":"v1.11.0","k8s":"v1.16.0"}} 61 | 62 | - name: helm detect --kube-context=doesnotexist 63 | steps: 64 | - script: pluto detect-helm --kube-context doesnotexist 65 | assertions: 66 | - result.code ShouldEqual 1 67 | - result.systemout ShouldContainSubstring 'context "doesnotexist" does not exist' 68 | 69 | - name: cleanup 70 | steps: 71 | - script: | 72 | kubectl delete -f assets/helm3 73 | kubectl delete ns demo2 74 | -------------------------------------------------------------------------------- /e2e/tests/03-cli-validation.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | name: "CLI Validation" 3 | testcases: 4 | - name: Pass bad semver with no v 5 | steps: 6 | - script: pluto detect-files -d assets/deprecated116 --target-versions foo=1.0.0 7 | assertions: 8 | - result.code ShouldEqual 1 9 | - result.systemerr ShouldContainSubstring "you must use valid semver for all target versions with a leading 'v' - got foo 1.0.0" 10 | - name: Pass bad semver starting with v 11 | steps: 12 | - script: pluto detect-files -d assets/deprecated116 --target-versions foo=vfoo 13 | assertions: 14 | - result.code ShouldEqual 1 15 | - result.systemerr ShouldContainSubstring "you must use valid semver for all target versions with a leading 'v' - got foo vfoo" 16 | - name: list-versions -ojson 17 | steps: 18 | - script: pluto list-versions 19 | assertions: 20 | - result.code ShouldEqual 0 21 | - name: list-versions additional file duplicate 22 | steps: 23 | - script: pluto list-versions -f assets/additional-versions/duplicate.yaml 24 | assertions: 25 | - result.code ShouldEqual 1 26 | - result.systemerr ShouldContainSubstring 'duplicate cannot be added to defaults' 27 | - result.systemerr ShouldContainSubstring 'extensions/v1beta1' 28 | - name: list-versions additional file 29 | steps: 30 | - script: pluto list-versions -f assets/additional-versions/new.yaml 31 | assertions: 32 | - result.code ShouldEqual 0 33 | - result.systemout ShouldContainSubstring "AnotherCRD someother/v1beta1 v1.9.0 v1.16.0 apps/v1" 34 | - name: Pass bad column list to custom 35 | steps: 36 | - script: pluto detect-files -d assets/deprecated116 --target-versions foo=vfoo -o custom --columns "FOO" 37 | assertions: 38 | - result.code ShouldEqual 1 39 | - result.systemerr ShouldContainSubstring "invalid custom column option FOO" 40 | - name: Pass bad column list to markdown 41 | steps: 42 | - script: pluto detect-files -d assets/deprecated116 --target-versions foo=vfoo -o markdown --columns "FOO" 43 | assertions: 44 | - result.code ShouldEqual 1 45 | - result.systemerr ShouldContainSubstring "invalid custom column option FOO" 46 | - name: Custom output with no columns flag 47 | steps: 48 | - script: pluto detect-files -d assets/deprecated116 --target-versions foo=vfoo -o custom 49 | assertions: 50 | - result.code ShouldEqual 1 51 | - result.systemerr ShouldContainSubstring "when --output=custom you must specify --columns" 52 | - name: no output due to bad components list 53 | steps: 54 | - script: pluto detect-files -d assets/ --components=foo 55 | assertions: 56 | - result.code ShouldEqual 1 57 | - result.systemerr ShouldContainSubstring "cannot find deprecations for zero components" 58 | -------------------------------------------------------------------------------- /e2e/tests/assets/additional-versions/duplicate.yaml: -------------------------------------------------------------------------------- 1 | deprecated-versions: 2 | - version: extensions/v1beta1 3 | kind: Deployment 4 | deprecated-in: v1.9.0 5 | removed-in: v1.16.0 6 | replacement-api: apps/v1 7 | component: other 8 | -------------------------------------------------------------------------------- /e2e/tests/assets/additional-versions/new.yaml: -------------------------------------------------------------------------------- 1 | target-versions: 2 | other: v1.0.0 3 | deprecated-versions: 4 | - version: someother/v1beta1 5 | kind: AnotherCRD 6 | deprecated-in: v1.9.0 7 | removed-in: v1.16.0 8 | replacement-api: apps/v1 9 | component: other 10 | -------------------------------------------------------------------------------- /e2e/tests/assets/chart/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /e2e/tests/assets/chart/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: helm3chart 3 | description: A Helm chart for Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | version: 0.1.0 18 | 19 | # This is the version number of the application being deployed. This version number should be 20 | # incremented each time you make changes to the application. 21 | appVersion: 1.16.0 22 | -------------------------------------------------------------------------------- /e2e/tests/assets/chart/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "helm3chart.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "helm3chart.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "helm3chart.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "helm3chart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | echo "Visit http://127.0.0.1:8080 to use your application" 20 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:80 21 | {{- end }} 22 | -------------------------------------------------------------------------------- /e2e/tests/assets/chart/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "helm3chart.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "helm3chart.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "helm3chart.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | 34 | {{/* 35 | Common labels 36 | */}} 37 | {{- define "helm3chart.labels" -}} 38 | helm.sh/chart: {{ include "helm3chart.chart" . }} 39 | {{ include "helm3chart.selectorLabels" . }} 40 | {{- if .Chart.AppVersion }} 41 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 42 | {{- end }} 43 | app.kubernetes.io/managed-by: {{ .Release.Service }} 44 | {{- end -}} 45 | 46 | {{/* 47 | Selector labels 48 | */}} 49 | {{- define "helm3chart.selectorLabels" -}} 50 | app.kubernetes.io/name: {{ include "helm3chart.name" . }} 51 | app.kubernetes.io/instance: {{ .Release.Name }} 52 | {{- end -}} 53 | 54 | {{/* 55 | Create the name of the service account to use 56 | */}} 57 | {{- define "helm3chart.serviceAccountName" -}} 58 | {{- if .Values.serviceAccount.create -}} 59 | {{ default (include "helm3chart.fullname" .) .Values.serviceAccount.name }} 60 | {{- else -}} 61 | {{ default "default" .Values.serviceAccount.name }} 62 | {{- end -}} 63 | {{- end -}} 64 | -------------------------------------------------------------------------------- /e2e/tests/assets/chart/templates/deployment-deprecated.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "helm3chart.fullname" . }}-v1beta1 5 | labels: 6 | {{- include "helm3chart.labels" . | nindent 4 }} 7 | spec: 8 | replicas: {{ .Values.replicaCount }} 9 | selector: 10 | matchLabels: 11 | {{- include "helm3chart.selectorLabels" . | nindent 6 }} 12 | template: 13 | metadata: 14 | labels: 15 | {{- include "helm3chart.selectorLabels" . | nindent 8 }} 16 | spec: 17 | {{- with .Values.imagePullSecrets }} 18 | imagePullSecrets: 19 | {{- toYaml . | nindent 8 }} 20 | {{- end }} 21 | serviceAccountName: {{ include "helm3chart.serviceAccountName" . }} 22 | securityContext: 23 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 24 | containers: 25 | - name: {{ .Chart.Name }} 26 | securityContext: 27 | {{- toYaml .Values.securityContext | nindent 12 }} 28 | image: "{{ .Values.image.repository }}:{{ .Chart.AppVersion }}" 29 | imagePullPolicy: {{ .Values.image.pullPolicy }} 30 | ports: 31 | - name: http 32 | containerPort: 80 33 | protocol: TCP 34 | livenessProbe: 35 | httpGet: 36 | path: / 37 | port: http 38 | readinessProbe: 39 | httpGet: 40 | path: / 41 | port: http 42 | resources: 43 | {{- toYaml .Values.resources | nindent 12 }} 44 | {{- with .Values.nodeSelector }} 45 | nodeSelector: 46 | {{- toYaml . | nindent 8 }} 47 | {{- end }} 48 | {{- with .Values.affinity }} 49 | affinity: 50 | {{- toYaml . | nindent 8 }} 51 | {{- end }} 52 | {{- with .Values.tolerations }} 53 | tolerations: 54 | {{- toYaml . | nindent 8 }} 55 | {{- end }} 56 | -------------------------------------------------------------------------------- /e2e/tests/assets/chart/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "helm3chart.fullname" . }} 5 | labels: 6 | {{- include "helm3chart.labels" . | nindent 4 }} 7 | spec: 8 | replicas: {{ .Values.replicaCount }} 9 | selector: 10 | matchLabels: 11 | {{- include "helm3chart.selectorLabels" . | nindent 6 }} 12 | template: 13 | metadata: 14 | labels: 15 | {{- include "helm3chart.selectorLabels" . | nindent 8 }} 16 | spec: 17 | {{- with .Values.imagePullSecrets }} 18 | imagePullSecrets: 19 | {{- toYaml . | nindent 8 }} 20 | {{- end }} 21 | serviceAccountName: {{ include "helm3chart.serviceAccountName" . }} 22 | securityContext: 23 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 24 | containers: 25 | - name: {{ .Chart.Name }} 26 | securityContext: 27 | {{- toYaml .Values.securityContext | nindent 12 }} 28 | image: "{{ .Values.image.repository }}:{{ .Chart.AppVersion }}" 29 | imagePullPolicy: {{ .Values.image.pullPolicy }} 30 | ports: 31 | - name: http 32 | containerPort: 80 33 | protocol: TCP 34 | livenessProbe: 35 | httpGet: 36 | path: / 37 | port: http 38 | readinessProbe: 39 | httpGet: 40 | path: / 41 | port: http 42 | resources: 43 | {{- toYaml .Values.resources | nindent 12 }} 44 | {{- with .Values.nodeSelector }} 45 | nodeSelector: 46 | {{- toYaml . | nindent 8 }} 47 | {{- end }} 48 | {{- with .Values.affinity }} 49 | affinity: 50 | {{- toYaml . | nindent 8 }} 51 | {{- end }} 52 | {{- with .Values.tolerations }} 53 | tolerations: 54 | {{- toYaml . | nindent 8 }} 55 | {{- end }} 56 | -------------------------------------------------------------------------------- /e2e/tests/assets/chart/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for helm3chart. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | image: 8 | repository: nginx 9 | pullPolicy: IfNotPresent 10 | 11 | imagePullSecrets: [] 12 | nameOverride: "" 13 | fullnameOverride: "" 14 | 15 | serviceAccount: 16 | # Specifies whether a service account should be created 17 | create: true 18 | # Annotations to add to the service account 19 | annotations: {} 20 | # The name of the service account to use. 21 | # If not set and create is true, a name is generated using the fullname template 22 | name: 23 | 24 | podSecurityContext: {} 25 | # fsGroup: 2000 26 | 27 | securityContext: {} 28 | # capabilities: 29 | # drop: 30 | # - ALL 31 | # readOnlyRootFilesystem: true 32 | # runAsNonRoot: true 33 | # runAsUser: 1000 34 | 35 | service: 36 | type: ClusterIP 37 | port: 80 38 | 39 | ingress: 40 | enabled: false 41 | annotations: {} 42 | # kubernetes.io/ingress.class: nginx 43 | # kubernetes.io/tls-acme: "true" 44 | hosts: 45 | - host: chart-example.local 46 | paths: [] 47 | tls: [] 48 | # - secretName: chart-example-tls 49 | # hosts: 50 | # - chart-example.local 51 | 52 | resources: {} 53 | # We usually recommend not to specify default resources and to leave this as a conscious 54 | # choice for the user. This also increases chances charts run on environments with little 55 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 56 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 57 | # limits: 58 | # cpu: 100m 59 | # memory: 128Mi 60 | # requests: 61 | # cpu: 100m 62 | # memory: 128Mi 63 | 64 | nodeSelector: {} 65 | 66 | tolerations: [] 67 | 68 | affinity: {} 69 | -------------------------------------------------------------------------------- /e2e/tests/assets/deprecated116/deployment-extensions-v1beta1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: utilities 5 | labels: 6 | app: utilities 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: utilities 12 | template: 13 | metadata: 14 | labels: 15 | app: utilities 16 | spec: 17 | containers: 18 | - name: utilities 19 | image: quay.io/sudermanjr/utilities:latest 20 | command: [ "/bin/bash", "-c", "--" ] 21 | args: [ "while true; do sleep 30; done;" ] 22 | securityContext: 23 | readOnlyRootFilesystem: true 24 | allowPrivilegeEscalation: false 25 | runAsNonRoot: true 26 | runAsUser: 10324 27 | capabilities: 28 | drop: 29 | - ALL 30 | resources: 31 | requests: 32 | cpu: 30m 33 | memory: 64Mi 34 | limits: 35 | cpu: 100m 36 | memory: 128Mi 37 | -------------------------------------------------------------------------------- /e2e/tests/assets/helm3/secret.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "data": { 4 | "release": "SDRzSUFGc0VvMThBQSsxYjIzTGl5Slo5NzY4Z2ZNN0RURVRiSllGeEdVZlVBOEpHQ0FNMmQ5QnhSNGR1bG1SU2wwWVNJRHJxMzJkbFNnS0ppKzJxcVlsNTZhcHdHS1RVem4xZGUyVXEvZmR2cGRLRnF6akd4VjNwSWpTQzhPSjNlc1YyWHoxYytSdWY4ZTNWWGdiaG43cmhFeTgyZERxeXpKVzVTNjU2eVYrUGVPNk9yOXhWYjY5cUZhNThjM3ZKM2R4eEhKT0NKNG55a3cvcUJqSEM1Skg5cFVCYjJuNW9leTY5TExsQnFCQlMwanpIcDJPelliZ2FSZ0Vkc1pzM3ZlTjZNSkRlNEs5S29oR1dRc3NvS2I1UGJFMmhRa3ZqUWFla3hxVmw1THEyYTlMYmdVSEZPNHFyQjNjdmJxbGtiSHh2R1phZW4rNy83Tlc3RDkvKy9WK0xTRFcwa0pSTXlQTTlQU2hkWGxKdkJyNmlHU1hkZUZVaUVwWXVTZW5sQWpOZDBkRkxGOG9HVjdiM2hRNzhaaG5FcVdpV3NneC9QeDVoVXh0ZHpmaEdJL055VWJyMFNtK0I1L3BLYUgxN3Vmajd5ZzROSi9nUDk4ZVZZNFNLcm9US0ZaWDUvZVhpdjVtMm11Vmg0b2tkMkdISkNrUC83c3NYdnZ6MWlzTi8vdTZXdStWS29WZUtZR1RzUmN1OEsxNHU2UE9aYmFkTW9uNjRmUFdXYTJXcGwvNmRPYVJFaFVMeWkzc0JsMzlucWNSczIrZFNwdW51U2k3LzlyNUlRNFo3SzJNWnBCR0gxbGZjL3M1QlB0UkxMVHhlWW8rWG9GbnBjZWZLL1RPS2IwLzJBbGZsL0IwL2Q0ZS80bS95YzRXeHp6VE0rZWlDM2Z1ZUpicW5MVERBalFoSnJ5QTBQbEdTalB0UEtpZXp1RkJ6NmJBdnZhZlJ3L0FxM095TnAwWW1ycnJvRHEvTlFYbkNTYTJCSnc4RlMyc0pnVEx0V2JwSVZxb3RUTWFMdFRsM0Y2Ym1UaUxWSVpFY0M1d2lUcllZKzZhVytYQStyUzYwcmZkb1ZJSlFFb2tqTmFyVCtYVER5N051cEV5cnJ1Wk10bHBjSmFyVHRGVnhzcEJhZkszaGhsODdReUhHczY0OHJDOFVzYmJWRy9XYjUyRTltampOUUo5T3RoMDZWNlZOdEFyazBQdXRydms2NHg2bGh2RFZpRGxUYzVxUlhCNmJIVmVBcnRhV3liWHJwdElhY0Zvci9Kcm8wajhwVHhjM2JMeFdvZU1ta2R3UWFxOGpiOVdoM3hzRFQ2MzB1STVqcmJSS0gzUHltSzhlWmZJVE85Tm4rajdUUllZUGNuWVJ0ZFVqYkg2eHQxTGRnUVUvYktWRyswa3REOGhZck1WNlF6STdSTGJVMW9UQU4xdDUxaTdENTZUakRxcWFPTTdzSkVaTFdHbHUzeHd0YWcrRFNhMC80dHZqNTJIZlV5b1RXNTcyT0xVaHVQS3NiM2FHZkRTZjhyQk5zT2J3Q1hTRS9EYUJ2eTJ0UEk1R1RqT1VaNzMxSEhOUVBhVUd2NUpFZjZ1V3ErdjViT0E5RDl0Zk8yNXZMVSs3a1ViMWEvWFVicE5EdkdzTGVVSjFHTlFrVzlqcHFjVTBCb0t2T2ozNGRrQmdteWRQTjJHMzNQUG1zelo4Si9PcXVHRTZTWGJpdThlK1o4SnYxMHgrUTNnYWNZUG1qQ1A5VS9hd2VXZmRYMk9YUXpoNXltK25XMEdIalJ5dTgvQzVKWXVER00vQ3BzVFcrWFN3WUxsYTRleFUxemRGdkUzeTZSNjVFUTkyT2ovWjZXZGVlQjZUZnByN2t3QTZtY3AwYnM3TE5lUlkwMWZkcmlrdE5pdklicUFtTFBpTGFQWm5ZbDgzSmVvamZ0Qjhzb1UyL0JYT1p3c1RNcGUwUmlWUkpub3NoTWdyWGtjdDR2dEtzMmxkanMyUldMUGtoN2FsaXMxb1hwN0UwZ1BwbzdiaGx6WnlzbG1lVDBrd243WURlWGlkenBQOUNMSmFHVVAzSnVLbG82WjZYb29KVzh6TDZZaUhXcDRmWUVIZC9WL25ZcXRYbnNmMVVQK0puR3F2Q3prMUdremEwK0c0ZHphdnRJcjhKalc0VUMzb1FHdStPc1JjWkQ1REhCWlZTd1ZlYUdMekRiYlhYb2MvcmhlZEEzRUpOWEVEUDQ1TkNiakFmbHFJaTZORGhnVjVBMkkwNnBGV0dWaDZpK0xUWVo1VU1VWm5PU3JkMTMwcUY3Nk1FandDeGpnVW16aWIvcHpKMTRJL25seG01dzZiTmVTRzdoRFlXRjJybFRiM0NmeTZSODRpRnlieGNGSzN6OGppakpsQU1xeVVaMVlpdXlrOEQ1cTFwLzZZYjU2TWkxaGJhTWlCenJUZ2U0YTdZMmNTeU5NbStrMzFLWjlYc052c1ROZW01RFRYV3FPNjFLZkFkcWRLYUQyZzVsZWRNcFUxcGo1RFRWYmZvUHNDdmNaRzd3clVVUSsrYWlKbnFvVzRkY1J6c2dpTEU0M0w4K3hVdmpCOTdBL3h4K0VoczduQVBhdmpNRnVCcXd1RzlmT3l0WUtQcDhxczV5T1hQYjAxV0QvWnQ2dnVTS3AwM3VwUnQzRzllWG96MTAvM2RlREFyYW5QYU82U2xUNlR6UGxNV0tzaWVZTmNYeTFmMjZ3UDduejg0MzVONm9sM0VMc0tjbnlCbnRnSDNyMk9GczNlWUZpLzd0NUREK2l5eTVja0Z4OHZVbnJ4L2ZmUDBKSS93Y3A4TUtPcjBDZW5tQWtpdDNxMEJhQWpkd09VSVBDS284RHJlb3VzNVJFZndnTWNLdGlUUjU3NWFOZlNibno3VjJOQkVhRkpPelJEUkZidFEyRWxwd2dKVDF2b3FsSEQ5bFpVNndTOUIwU21pSFkrUThMaTJDYXZ0b0IyaXhRRjBpeklJVHQ3YmxTUmllYTJmWmtpTy9TaHlBbTBLM2RqNFJib0dpdFRmcVJQWlVlWm1hWmtzd3dxc2dyMm5iR3IxYVBwMzZORE1VU1pENFdGUEpVdGZicmhnUDRVZ2FwU3E4bWpJbjNZUVdBN21Gdzl0ZjM2Y1lMSTZxMDJqd3hPbmtmVjlkNjZxUy93MjVrZ2V4aXJRL2JDM29lUXg3VllkU1ljN2NTcEQrRi9aQTJZR0d3R1FteDhkVXF6dTU5a3BHajVxR0tQZGdoOGhvekIwemdXcUIrQVpCclFGMWtlTDZMR0FzalMybldFVERiWVpCWHhSS1hGUXVwN0lmRzlTSUJPdXErS1lLTE9tR1Urc3k5bW5aQ24xek03OTNrQS80a1Q2RnY5Q0xGemNTMnlWOWxGSG9pc2NwNTErRUp6Q0VXUS9mZ1B4eVl4MW9FKzB2MGNqSE5OWStDcmt4NHZPN0lQOUxjN1E3Q0IyUUh5NW5LZ0xTWjU5ZlNXMklNWUJXREtVVC9OWWRaTkM2ejVIWDJQRUgyUStPQUVtdTExT2thSzFBN2tVcGZsRU10cmg0VGp5c1JCN2wxTERTa3MralZoUmprNzF4Z1BoalJIemtQbklSaU1mV0tlVEw4Zm53KzEvN252VEovWWUreGpaUUQwUks0bE9TS2xmcDh6Wms5cnVFZVJGWG5memZMUE5vWUZQREhCdUlDNDY4L2lTdnBiK3RBdmg3SE92azlvak10c2xYWkw2NG15RFlyZ0VwYmx5UGxYeWY2Si9BT1NzKzltaHFYSWx5bFA1NkNzRWZuWi9XU05RUmZFVTR0VCtZNkZldWZBRk14VjZxOGJLVm1sZ2FXaWhpaExTc1pzTTN4T2Y4T1dhelAxNTN1ZGZNdHl1REpZYVl2YzNQWitsVWFaK1I2djBlbWIrN2hTK2JTTGRwd0M3bUVGZTd2U016Ky9lUW1iUFNlaklXejBhWTFqclBGd1pYaVdwZkJnRlUwWEdCb0NOMitPMkNnQkUzUmxQKzNNWi9OM1hLYTFnMjVma2RJODNHdy9tWWVuL1RaN1QyZFdHeC9HTDhIa1hReG9QNG9ZSzVtMUl4bTFoaDdwcTNFTnF5VGF4NnR2dEkrZlhDMGMrdk5NVDJROUo2MWJ1bEpoMzF0Ny80RXJ2S21WU1lTK3dxbXh3RVArNTNMWnpXUk1oSG01dDBJOWNhUE10bjN0K0xLZHcrRVR6M1NjWGd4ODR6Sk1acXN1NXNNOW4zaDh4NS83SGpPbU5lR2ZaT0RqNWh0V243eTY4My9lZDV2dGUzTkx6dTd6YVhiL3J1d3NMa2R4K3ZaanJERFpXM1VNTjd6RXg2V2g0YXArRlN2T1NZNkltdlVudWZyRWlnTjhwTHBWcGpYd2lGcTVpOXpWeFlkTnd3bkJRWUE1RHdPS2t5c0RmUVYyUEtKWEl2ZXdBaE1mYmloclpuYWxlZmdPMWpqZ1hVRWh2NGY4Zmk0cUo2M0JKOU5qcSt1MC81NnZsWFI4c2hLaGZBYTFLdEpjcmFjck03WTd3M1JFdnFkc3Y1dmh4VDVXMmIxeGtuUFpTa3piNHdPVEliRWRoY0diOHBEVy9OYmI3UVo4cE90ZVZydWI5b1FFbzhIWDREUHdVc3g3UDg5MnNlanFCem9oNzBmcEhPSitGWlQ1QitPeS9wTHE5eXQxTWZlN0txMjlIN09kUkwxTU9LVlIySGtNNTFPZGpGdUllYk1IYkVSY2Q3dVBpVjdnN1paY252UnBIZ0I3MytCM0x1L0RuV3l4Sm9QREJPL3JWRnhGWlRLTzYyN0FlTkdUL2NHT3hIdDR4YkI0NTNOZ2Y0OEg1K0NNTWVPRnhHajFqLzNmR3F5bTAyYW81bjNrWWozRmZET0psZG1nMm1jN1hwTnJZT3N0TUpqdGJxQyt6S2RHWWI0ZHgwY1AyTzVqVDMrNFF1MGRycXZ5ZmtuamVEejN2WGV3by9XZS9oL3JqcFgzOGJ4aWtodFBkdDFPZXRWdXZZZTFFUHI0c0JyTE0yRUZQS0k1V2FYMWwrNjgzS2ZZSVdndFlVcnRweGpGZG9zS2ZqaklMYkZHZCtPclNjeVBjeFJ4b0d1WnZscmUrUFBLNG9TZjJPN3RnYS9aVDVqMnlodEp0RGk5VlQveTNVRzgrblFuQjNaZmQrdEhzdWc4c1FvT2dUd05vT3U0engvSXcxb1JQQ3FpdTFualZudUZkVndoejVJeFZBK2hKYy82eC9kb1BRQkRsSHZQN0t4UHpyOWluUEtVTGVER3dIakVkYkxWZU9oWmJwTmpmeVE3THZSTnlBbGZVZm5vRWVhTjFMZzk1YWQxNHB0RVJsRjNnZTdncmZSWit3MzRmR3hYVXZ1Y3loUEVZcDNQcC94ekI5anhFR2U0bnBOUlFTNTcrVjZRN043M0tKZDZBdzRWbnhHcksvU1gwWTc3dmYwa2RqVU8zNGprK2gzVzdQQlozaWFMcmp0VWlqZkRBaTdzcjQ5K0lZYWU4UXZrQmNqbjVBMlhXOFQxZzN2dll1RkJuNzdlKzFjb2NuL3oyMDl6b0U4VEg3cDRZWVNuN3krcGc1Q01Ed0NpUUsyUWtEcXI0UncwWGZGekpQNkFkR1pOL2FCWkMra0M2eXhoQ3M0MzV5UmdiSE9JTlZCYU1Na3JTRFJVOHdCa2QvZjZDUkhOOU5rZUpiTElzMWNZb3dNaTlnbGR6eTBrQzgyaGx6WFNGcjVESjdwNWtNMXhTQ3BUQUR3aVdyOUtsM3lUM2Z2eGZBR2NKQzl4b1NDVFpqWmh6UVVnZ1lYTDdLQ0pIQUxYT3pxZEFnZ1dzL1RWVno5Ym1EMThMaWZQRWFOc0czOFBqQitSaEVPQXlSRnBzYmFndmdFQSszcUwzTk5HU0JjWDU0dWV4bmkzdVFZQ1ZnUVArQ3JhazdmRFRaMjhYM2JrNzJqdUUwM3J2UDRmNjc3cDJzZnpKcm1CR0RUYVp3Z0gzV2lxYmVGVE5GeEdPRzdlM1JTWmNmWkJROHpuVmtaY2poWXlxUjZSeGtpNHNLS0xHdU9VbjlqcjZoT05OVWNvMlN1eFU2UWxIeStTRW9oN2MzMlNmSUMwd004Z1JPc2JxVG00SHgvSWczNWxtY3FwOVBxYVU3UGwwVEVaWlhvODZPUVVVYVVrZ3I0eUFzRmFuU1kvc1BQK3RDMTBBMFFXeWY4YnVkSlFneHA5WmJuOUVRS2VleTUrbjNpZklSTVJPMDZSMi9qS1AzTklmbjRXdTQ2YStic0x4eWJka0k0bzNoUnhZWC85VjJMb0diOXdxQ21pT1lnbjJ3UXAyRks4OTRzV3JEdHl3Mzcva1I1VFdpa2tZbWVVZHFleGxOZFgyN1hEbUY3YkVhR0wxNGdRU29HZVZzWnlhZXRHN21RZVBTL29LS2FSRTRKTFBoNTQ5b2l0eGV5MDNtdlBDNStYUmdDMmxDZEtTOFAzQWp2MGxteVVhOXJ1NW9pRUpkS2ZJVzlvYUVzalpDZXEvdGpmZFUzSURZcXpLNjdyaGV5Y1ZsQ3dBN2NNVjFFSk8xcjRxcERBeU4yeHZDRE1IOWNxTXNEZEVLb3BPMloyYVd3VWV2andpbmlha3VkL2lRT1UwRXBVelYzL3Z2djhSMjdpa0JRRzdpMC83M0hYMDQyaFFRd052aXRHeXZkMCtDbGFJb1FOencyTlRWaThENWZUTTJ3TkwzTHBIVDUzSS9DaXBXWVVQWFlSdkNjc01KWXJXenNNdmNkTy9OMXllUlBURTNRTkVnV2hzWlNlajhPY3lxcHJXcXJhNStLSmxBQWR4OVZ3R2VXam1SSjJlaVR2YUtyUUk4WnlKeTUxZkhhV0w5QXN3MUdLcC9sZWJmTFJTYjRyZW56Uk5sMXZhWnhhQ2tpeDBFZnI0T2krT3NxZDdtMzdjcm02MHVpNzBqS29reTNZK3BRRXJFWEVBajMxc3FTdFhJdXZIL0hzV0JISkZsU0oxOUs5QW55bSswbUJKT3FCV3BaQVo1c2NxQmQ5aHhzazcyMEJIek9acEd2aTRuMzYzcjN1dlFIR2lGeE9ZRVpxV0d1MEtFZVpXZlM5V1VwSEgzekEzYlBxYktxU1dJdEFnZGFaRFpCTE5KdlMvQ3JwbUY0MGFQWmV4NVFDTzJPcTd6MDlkVWlweFdUUkcwbml3QWZWZXV3NGdFR3N6ZGx2a2JpQS8xanVlOUhjOVdPME5QWmJtZW9SMXVta1lWYzlPYVpqTFRkM2JhczcxMmk5WFJOVU1WU3AzbUxibXBkRFhrdlBIV2hyNzYrTzI2dWcvZjdWY2RxV3dyNFB3dVI3RGZLMXgwZVgrWFFLS3VTRDBtNmxCL0l3bUVFL2wrNDcrS0NqZmN4TEFLa1BtTXNESmVYaG05cGZtTCtzbFh0WWs0OVhoZlZpQXFtN002K080dHF2Um9JVWw1ZVhMKzYvU2tOV1duZWwvUm5YTDUvWlRYOXg5MGRXNzBvb1A4T2xINE12SzE0MVFvVi9jUmUycTkrVjduY1NYdHpzaUMwN3NreXpFNlVCWlM3M1UxL3VuaTZWaUtJYUpEbmVYR0xhWFFYV0Z6WXFyK3dsTzN5YkREcDlramsvK3R5NDdEeHpvdEM1VWF2TTNKZjBIRzV5SFBuVVVEZ2FyVUcvVk9NN2R2ejN4UTE4UTJQR3BEQVhBT0hvMXlERnl0UlFSd2sxcTVNMy9mTjJmYzZ5TExyWmhQbWcwSCtrT1BtUFRQOVoxKzU4d2I0VTBMVjNLaTMyUXd1SW45UHg3Ky9aWnczM0ZOdEZxSEszTDB2blZYNVBiRkUwL2NkYVBzMEFSZ251aW5tUUcvSzhZeGwzcFR6SHlBK2s3U2c0bUcybmFSajZ4VHM1MDU0OVdnUzMzT0VBZittRm51YVJ1OUtvOFp5L1NleVY0WUtMUEM4OTFUaVlrYzRrR29kV1F4aG93bDNweTlGbE52ZWhlbWgxdXYxL1BFTktBazZFNThlZzdBUitJVzBwY3YwY2FQMERWditBMVQ5ZzlROVlmUktza2o5azIvLzVVUG4zN0UvZDJGOHlKWDhnNW5qbGk5KysvL1kvUFgwWlBnWTNBQUE9Cg==" 5 | }, 6 | "kind": "Secret", 7 | "metadata": { 8 | "labels": { 9 | "name": "test", 10 | "owner": "helm", 11 | "status": "deployed", 12 | "version": "1" 13 | }, 14 | "name": "sh.helm.release.v1.test.v1", 15 | "namespace": "default" 16 | }, 17 | "type": "helm.sh/release.v1" 18 | } 19 | -------------------------------------------------------------------------------- /e2e/tests/assets/helm3/secret_duplicate_name.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "data": { 4 | "release": "SDRzSUFBQUFBQUFDLyt4N1czT3FTcmYyWDdINDNvdHZWeVdaZ0RFeldyVXVoRVRFS0ltb0hQck5XNnZvaGdEYURTdzVLTTZhLzMxWE4zZzJoN24yM0xWdlZxcFNFV2pHcVVjLzR4bHQ1d2NYT2NUak9sem1wUmwzeFlYUlc4eDFmbkJ2NFRMTi9uUzlCTWVsNTNJZFR1UkYvcHB2WFF1M1U0SHZDTTFPNi82bTNlVEZ1L3RyL3E3RDg5d1ZoNTFmZnNYMXNKZXh3ZXdpUmNzd3ljSTQ0anFjR3FXWmczRUR4U1NoZzdnckxzMmNMRSs1RHJkVGNzVkZjZWJSVzhKTlEvR3lSaFo0RFNkSmNJZ2NLcWN4MDRjTldEYVdlUlNGa1U4ZnB4NFZTWnpJVFR1dlVhUGhyWk40bVRWZW5oLysxTHFqeHovKzlmOFhPZlJRaGh1K2x6V1MyRTBiMTljMFNHbmlJSy9oZW05T2pyUEdOVzY4Y2s2UzNORFJ5OGpMdlBRbWpML1JnWDhFSGlaTkZEakw3T3A4UkVqOWlwRDNCNDM0SzllNGpodnpOSTRTSnd2K2VPViszSVNaUjlKLzgvKzVJVjdtdUU3bTNGQ1pQMSs1LzJMV29pQnV2SEpHbUlaWkk4aXlwUFB0bXlCK3YrRnYrQnVoYzgvZjg0MHNidVNwMXlqamZIa1lpbGVPdnIvMTdaSkxOQTdYYi9GeTVTemR4cisyQVdsUW9aMTcvalhpZmw1eHpDdWFJRnZyNk9jNmhmWnVjMWRjNFMzVGFpTDVHK0dHUDV2ZWJxUHZZZEpnd3h0djhiTHh0SXNTZDhVNVNXanNCQlFpdTVQczd3ZzN3aDJUbVpVSjFYemdKalVTeDJqQmRhSWM0eXN1ODBpQ0haWWovejVJOXZybU4rMTUramk1eWRiVTVNb2RialM1OVhYUjROVytIb09KRktDK2xEcW1GcmdLTG1Bb0diUEZ5cmVqaFk4aUk0Y0U1NkNVZUVjeE5tQWl6YUVvWkxiWldxQk4vT1ExMDB4Vk1GSGxsbW1iYXdGWW85d3hXeEVpeGdhVkxReEpMNFNLc1ZEN1FsdU9zdS9EaVZUYVppc0NrKzdDVWRvYlYrN2V2VXk2dVVGNnFXc2FteUhWMVJ4ZzFCemw3SGwvNUw5Wi9KTXFTOSs5a3ZjUjZlVkFuUG5EU0FwY0pkZ3d1V0hYZC9vNmovclo5OHFXOFVWNXJySm00MUdUampOeUlFdnR0MmxjRE9tMXJNZXdxZkZERWhTb09mYmZMT0c3VjNienJmekt6L3FkY2NKc0FXYnIwQzhNK3hwbStoV3RnSkVlT0dacm84cURaeWpxZUthMFMxZFcvU0VHQWV3YkdKV3REYkFHb21OcWVCanBMYVRNdG41aXJ5OFZLQnI3MDBYN1VUZmE0Nmt3bUwxTXhySFRORUpnYWp5VXBRaFlZMzg0RVhMYkZEQnFTb0V0enFpTi9oQVBNRlNNQUltemZFcDZHYkMwbFcxcW1OcXB5a0toS3NrR2lxMlZiZW54eTJUd2ZSaHBLMkNPY2tUdDYydHcxT056U05vTFlGQWI5TFlhU2pzN1VVbm5RRW9nMFZMWDFMRXFEMkpncnJPUnFNVzJOZUNIQkFoUVdUT2IxTENLM2RNNDlsWEZ1R1h5WmVsNXl1czlpOGZqUy80d3ZkYm85L2hGTUE5TVlXTnVKSGNZYWJ4dDZRSXFXd0ZROUJKWTJnWllsYSsycVM5WXJqYjVzTFoxN2lqM1ZUNDl4TVd3MUhjMlA0ZjFaMEY2bWVGeG5mdEdpc1NaNzVpMmI0dnQzRlY2Q1l4R3ZycFlGN2FweTdhNURpRFJNQXEvTXZkZFg2VXhFdlRlY3lnTlhGbktiR3ZodTBwdlNkZW9xZ0RzbGxMbW1DM0JWWXlOcW9BQ2hYUmR6dnlwMGc3QTR5Q0FTaSszUmFOVUgvRlk3ZXVGcWd5d3F2UkUyOFNwYlE1U01MbXQ5V3gvSlFDYk05OFdlN25hZHdOWDBlSWFFemF1MHVOZGErUkQwVDdCZ203MFA4N0Z2aWJhWlRkei8wWk9EVlpIT1RYVmpZRTVtV252NWhWcWdya3E4eGs4c29HdStkWUVtR3RzV3hvZUxsb0JOSTBOVW5wek1KSGFiNU5mdDR2cWNCVWpROG82Y0pXWnIwYlpkL2JiSHdTUXVGaVZnd1FTSFh0eU4wZE5QWEQ3Rko5Tzg2U1ZRT0t5SEZVZnVnbVY2elhUdk1JakNVTkNzWWtQNmU4NytYb1VqK2VJK2JuRFppUWFwVXZ3SEV4YUs5Z2M4Ri9BcndmWU56YXVZcFFUb3h1K0k0djNMQWx2c1JKWVFTVzdKNzNvdmZiemVDYjBMczZMMGw2Z3Nwc056YVBZTTl5ZEVTTUZabThESnEzbnc3eDZzM2gvYUs1OGxmUldTRzR0WFhPQUVXbGh1aDZHQkJkRGtjcWEwWmo1anRtYXc3NnhBSk51NkNoR0NxZmEzRkY2cFN1M2p1WnRxTHduQzdONW92UHlZbDNLRjJaUCtDbitFQUc3U205aFczb3dKTXpYdGhvdUdOYmJZbENvOHNCMExDMXhaU2wyKy9ycU9id3ZSbE8xT1p4Mzg1Rjh1MzZlKzZ2bmg2N3ZLdmUrYTlIY3hZVnJxYjV0U1N1bzRMbHQ2UWtVYjBOV0IzY3gvdlc0VnV0SklMQTVhTnJXWUtISytuaks2Mi9UUlUvVEo5M2IwWU8vb3JiczhxWEt4U2Z1NTlVRjB2Rm40T0hFVzZZM1dZTDN2TU5ycHNWVEtJbU95ZCtwZlEyN3NrUWNjNDNkUGw2QnFaQzVsc2JicGhhRGFldy9oZTI2MXQ3L0pTL29ldS9SK3N2d2pxM2xpVlNBR3Y5c01RaFFOTTdsTUM2b1RSVTI2eGhRdkhwLy9yUGpzVDBCOXNmK2NGR3Y4WHFPRDNDYnZUZHRBb3lpUVFJb2Jzc1NUM0hSTHJ2aXFKVHUxYjVlT3FZd2RVMUFITXYzMVpEbHh6Rm5ZTmVNT3hWUGZ2S0FpTUh3d3A1SUMyQ0N3RFhYdktvd2ZHbXAvWjVnbStzRUVJeUJUSGxhdC9iOTlza1FaNzdiSHdpUWFOWDcxdGpYNXFNNkZpUGZKc2JjdGhobjIwQlJ3T3BqSnRqRUtDRXhlRnBuNnhnU3h6UlMwQi81dGtWcnhUcUJKczNkY1pWdlNwQ2dzaHRUL0hlVkFLdVArdk9zbEdnY0VraVFqNW9TdHN0RkxpOHdVZnM3dk4vS25rT3h4ZHNtemxFcDFiR1hxdGdybUZmN2JnS1ZsVytUR2N0cjVsL0o2cHhBNzIvOTNPY0I3d1BGSUk3WitneVBEK2IxbUp1Q3lFaWh3dGJGaTB1TUVoRk04V0UvL3RPeDFSeTdabXV1UHRnYnRiK2ljNUJBUXhNQUFZa25kOFBoUlBXSDFnbXVIdVRBUUtueTZubGUrV05iUnVySzNYeGM1ekNybFVlYytBTjd6L0JhcjJKd0FhdjJOcDNqUU8ySEFNbUk1UkRMYTRLeldkTWdnT0JiVlZhejQ3aFd2T2ZBenhVaU9IY1YyMWREWXpPY0dCdUtyMmQ2dHZiOXVyNGN5Ris3WnZhVThkTzRPY0MycFdOVnFYSkVyZU51TTk1TzE3QkdjZE8zcmRFMi8wSnZjb1FuUGxSNklUQlhYOFdWK3EvNmFWeE81M3A3YmRBNUZsa1Bkay9YRStVU0ZKL1ZNQTFWZWZDbWhuOGovOGk0dXZhM1dLb1YwQlNvRHNvSk1leVB2cmpHMW9GTmpCU1Z0WHdTWUtqd09STDlvbzdYblZyMVlMa3RyZ1dLajJvMVpyUEY1L3B2cU1xM2ZoM1BqK3IwaHVWd1V5L1E0a0IzdU8vQktPL2U0M1Z2aFhyN2VhWHlhWTBja2lQY3l4M3p2bkMzY1o3SEZWZDlUNFlzclYyenpUTk9lTnIzdmN0QmhBQ1NYZ1RNY1daSGk3c3pyb2sxakNLUTFIWDMzZnlkaVhUdGFEeHNxblVlcmpkZnpNUExjYk0rc3BtdGpVL25yOExrM1J6UWVwUXp6bUVOY21EcHRFWW1zR3dua1BaY1ptdE82L2pGWHVBMG51L1VSRlp6Nm5WTCt4QjIzZC9IenphMU9Xd2F1U3RMUEN3bEFZbXpyK1Z5dEpWaFNMYW9GYTdaNHFkYjMvWnJKd0hoQVE1ZmVHZEl0QktZUFg2THlheW5ZakhjODRtbkQrSzVyekV6dWlhU2kveDYxcHZiWWx1QXUvZ2Z4bTY5K1VpM1NuYWZ3MStYdloyWHMzbjY0ekxucTdZc2lSZGwxNjZYTEQza1pKNTdVenJrZ0FIYWxwUVlCNnNQV0FFUHpOYkdNZHM1S3R2aXlCeGdWM2xjeXlSTElCbmZxWTg2UmNIQ013VU1vL0VUTkEzZU52WEFWUjd2S09ObFZ0ZFo5Z0dTRU5kY3AwZlpPeEgydXFpY2VvVTkrekhyak92cSt2NUtxTWRYWFFSbEt6Z0hDczNFYnQxVnNaMFZaaU1pUnMzVVIxczAyTS9FOXRtc3lxaHRGNFgycTUvSlVObHVnRDUzSHVzVnZZbDNuZnhudHU1bERVWTE0bGNJTEV1NVk3WVdnT3A5c0xjN1VMUnpXVUdseDROcHJVUFpkekRiK0tqeXJuclU5djFPVy96OWpraC9IOGZ0THFBcll0NlJqM1lOTTl0MDhheHZwTENuWWJzNXdQdWR3OG91eHhRQ0lCcGptZ2N6MFpnall2Q0hNZHpKVnRyQU5vWDBZNXVPTzZDdGpQTlZwVFBXOHh4K3NwdndFUm94cE4zRmZBTk1UVUFFODk2TXNUN3M5Y2ZuOGUvcmhXbjJNbmdZbzBncUFJdU5VVHFXM2hxejNTcmoxcFdsZTFWcHNaMEpHSTM5Wi9sSTM0N0JBMnV3MmM4OS9lV1AxdDVwMTNRWWwzb2V6M1UveENlN1VSL1ovN250bzZsNnJsZXBjdU01N0laVkpkcDFjd2swZXhHWXRFcGdTUVVTTWMzSkZsMS85YTdKUTQwZEV1cExKdldmWWhUYjZUbUt3MGx1S2UzVU1iVldOZWZuT1RxTUpOcXBqS0c0VHV6bTRrS2MyTTdyU2F6WmIxWlh3anRWQ1hpMzN6MkwzY2w4aldGendEK0gzZHRSOTB3VzFWUENwbDdZWWp0OURxWFpXRGlScDZ3VGx4ZzVzTFROckQ4b2JESTd5ck5xRExWRDZnTnJmUDZNcmdlbHh6c1BzVDljWGRSZk1NWjR5WmYrQU51bW5rQmliSkFnbFZBYzRQTjRWTHNsdW1qd0YySkY1UWV1NHQrcDh2MmxPSzJxMkZReWptMlhTbUJwaFdzTjVzQWFuZnRWclgwZUNqaUE1dW93bnc3Zk84R094M0tMNndjeW1vNmx4NGUxb05wNTF5aFRtcnRLKy9nZHBWVUF4Wmp1bU4zOGIyS1hmUHB0eGtHOTY3dUpxL2lIUGdXMHE0QVVieVpIdUxDL1AvMk5HUHBPWEZ5bG5RSnJVSDA3RlIzaitzbXpEN0h3cEU3Zjd1TXJIVE43LzQ5UEdjNG50SVkySG96T2pKTWxkZjg1bEI2QkphV3dpVE1hQ3BtY2xGVGxhd1Q4aERCdVMvWkpLWmJxNXVoZE9wUytYM3FyNldBYk82dzgwdVZRZlRtSU5sc3lmNWp5MWJOeFJTSzM5bXpPMGxRUjJKY0wweE9hOVFWYjMyc0NqNkJmMjViSnZvNmhKYVcwOGQvcU9LV01OYnlkMGFqZlpjdGhDZDNIOGYzMHZraE55cVBsVnBVcWc1V09LVEMxRWxnbkplSVVsajZ3NmRMeVozTldmeWsxM2paVmoxL0x5ZmRvejNhRGZROTduMUdBVS9nNG9NbEtlMEZqNDFxRHhPM2pCMXJtYU92dy9wS21jN3piR01Qb0JCcUdrMm9qY2x1bWp6ZGtEdU95bzNabnVpK1VwUGZ0Lzl6MjlTZzgxMXZseHV4T2xRZnYwSWtCUmtwNzQxaDZnU0pHSis0KzNOQ3crUENrM0IzbTFwYVduTFVwdFIwNVloUmJLbWpMNGwyS0Uvc2krVUxaUEtDTDdNdXFTNVRrY0w1d1RROGUvTlZGYXRFZkZLN1Nua054ZGFmMjlJZlppVHlvWUJGUU9VMXRqRWc3Qk5OenFzbnNlSFR4SlJwS0tZSnQ2ZkZ6MkMwdVU1dDI2VDVjOWdVUkl3QUsvaitqVGtoc0M0aG8rQmpiUHFQWEIrK1ZIOVBxZDZoQ3pnNDZIR3hhSGI1elNtMytMbmFkbGVvUDI4SWVBUVRuRkcrT2NXRi8vM2RpNkR0eDRhRzR4b2owK0dxTDQ4aVg0MmUvcVIzbGZ2N25paXNjbkhzcDEvbkJPVzl2WVJSbUpkZjU4Zk9LZThzeHByVG11ZkNXeTlEMXFtTnBJWEY4anc1T2NveGZZaHlpa3V0dzZwc1daeTlMTC9XaWpMdmlsbDRTcDJFV0wrbXp5QStqTmZlemZ2VWx4M2ppb2FXWHBWem4zLys1NHNMSVgzcHBwVCtLNG95ZFcwb3JFN3pJZ2RoenVjNmJnMVB2aWd2aU5LdU9MdEZQWEtjNmdIWHRyUjJTWU84R3g4aWhUQ3R4c29CSnArNWx1UHA0eFowN0U4V3VOL0d3aDdKNFdXbE1ZbmZpb1h3WlpxVWNSNW0zenFyN1M0K2RxWkxqUE1xNGprQnZwSEcrUkY1dGFYcnBwZFJiRmlHcW9oVXZNNjV6eis4T2FjazRUek52cWI1dys0RmRoQ3I1RjBLQmxwNlRlVnduVytiZVZVMDNveHpqbjFkY0ZtTnZ1UjNMUEUxUjRCRm5lK2pyTGNUSEI3NXVBZytUMEkvaXBiZm5wV29walcxTDU0RTFvTG5IdzFKS2dOZ3FFSm41cmhoZ0dFcWhhK0tVNFZVcHJXeFRXOUs2Z3NyYko3V1VabzZDTjJwZkUxRGRscXA5TFFibU9sVVZONFdpNmtPeng5dGlrRUNDMHVvTHdCN3ZXQURYN2RmeGM2V1hnMjQ4VjVVV0JtS1Y4Nm9jckJBeGlHTUZHTWhiYnZTWURFUHBCWkoxUzFYYU9aZ3dMR1krcUgwSm81Qnl6aFllK25HdTk3UzNHZVZqWkVidGZZQ2lrTkU2Wnl5MHFhcm9DWXBHVDBQaUpxNThYLzFWY0FSSnV3VGpPTGVqcEJ5dXFyK082ZWF3T2NCeTJJcEJTY2NHMGNHOWpVdHVDemtjK1dPeG5VRnF0eklJYkRFVFVQMEZObHJGZncwanJZbTY4VjlETWdnY2RxMW4xWFc3ZEV6MDlCU3htSnEyTlVoZzA5aW9qL2hSdDBaUHc0aTJ1QW0ybStPbkljRUxZRDRXY2hqbnJpS3NFR24vSlljdEVZbGFBWlJaVWJVbU5CdUlFNFZ2SGxzdjE5ZlhyOUgvYTB4WTZuWWErek9LMzc2eU4vc2E3VThpZGhyZU92TWkrakg5VmdqUXl4emhOVnFFa2R0cFBPd2t2RWJiazVIc2RDbk52MDRqODlMc2VxLzZldmQybzRFZDZPSHFKR3FEV1hlVEJ0L1lxRU5qcjlraHltclE1VU9uaDZQZkc3YzllbG9aOU42b1l1dnVhMzNPc2pvNWVta29jU0xIOTl4cldIYlljYzdYS0UwOHhKeXA0U1B0TkppYmFZMDV0YVBFeVZBd1BIVDk2MzU5emJQdDdHNFZIazRLL2NISHluOUYvVmREdTRzRnV6Z0NQTzFTV3V5SEhpSHJnWTAvZm00L296aktuRER5bG9jdVhEZmVOL2tqc2NlaTZROHJYRFFEV0NuckhPZkJ3WkNYWFVuc05BNHI0dUZBV2dqU0UyMDdTN01zT1g1eTROcExUQmZCUFg4NklGbkdXWXhpM0dsTTVaZkRoemdzdk1oTDA1ZGxETDBUalZTVDRwMTYzV2pReXRscGZEdTd6WFNmbXJmMEhEZjhYOVpRRjlrTDAvTnJVSFlCdjV3a29jajE5MERySDdENkI2eitBYXQvd09xTFlIWDRMeUJDUmQvWmY1dXcvOTVoLzIvQy9menZBQUFBLy85TmZjQ25mVFFBQUE9PQ==" 5 | }, 6 | "kind": "Secret", 7 | "metadata": { 8 | "labels": { 9 | "name": "test", 10 | "owner": "helm", 11 | "status": "deployed", 12 | "version": "1" 13 | }, 14 | "name": "sh.helm.release.v1.test.v2", 15 | "namespace": "demo2" 16 | }, 17 | "type": "helm.sh/release.v1" 18 | } 19 | -------------------------------------------------------------------------------- /e2e/tests/assets/list/list-deployment-deprecated-and-non-deprecated.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | items: 3 | - apiVersion: extensions/v1beta1 4 | kind: Deployment 5 | metadata: 6 | name: utilities 7 | labels: 8 | app: utilities 9 | spec: 10 | replicas: 1 11 | selector: 12 | matchLabels: 13 | app: utilities 14 | template: 15 | metadata: 16 | labels: 17 | app: utilities 18 | spec: 19 | containers: 20 | - name: utilities 21 | image: quay.io/sudermanjr/utilities:latest 22 | command: [ "/bin/bash", "-c", "--" ] 23 | args: [ "while true; do sleep 30; done;" ] 24 | securityContext: 25 | readOnlyRootFilesystem: true 26 | allowPrivilegeEscalation: false 27 | runAsNonRoot: true 28 | runAsUser: 10324 29 | capabilities: 30 | drop: 31 | - ALL 32 | resources: 33 | requests: 34 | cpu: 30m 35 | memory: 64Mi 36 | limits: 37 | cpu: 100m 38 | memory: 128Mi 39 | - apiVersion: apps/v1 40 | kind: Deployment 41 | metadata: 42 | name: utilities 43 | labels: 44 | app: utilities 45 | spec: 46 | replicas: 1 47 | selector: 48 | matchLabels: 49 | app: utilities 50 | template: 51 | metadata: 52 | labels: 53 | app: utilities 54 | spec: 55 | containers: 56 | - name: utilities 57 | image: quay.io/sudermanjr/utilities:latest 58 | command: [ "/bin/bash", "-c", "--" ] 59 | args: [ "while true; do sleep 30; done;" ] 60 | securityContext: 61 | readOnlyRootFilesystem: true 62 | allowPrivilegeEscalation: false 63 | runAsNonRoot: true 64 | runAsUser: 10324 65 | capabilities: 66 | drop: 67 | - ALL 68 | resources: 69 | requests: 70 | cpu: 30m 71 | memory: 64Mi 72 | limits: 73 | cpu: 100m 74 | memory: 128Mi 75 | kind: List 76 | metadata: 77 | resourceVersion: '' 78 | selfLink: '' -------------------------------------------------------------------------------- /e2e/tests/assets/list/list-deployment-non-deprecated.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | items: 3 | - apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: utilities 7 | labels: 8 | app: utilities 9 | spec: 10 | replicas: 1 11 | selector: 12 | matchLabels: 13 | app: utilities 14 | template: 15 | metadata: 16 | labels: 17 | app: utilities 18 | spec: 19 | containers: 20 | - name: utilities 21 | image: quay.io/sudermanjr/utilities:latest 22 | command: [ "/bin/bash", "-c", "--" ] 23 | args: [ "while true; do sleep 30; done;" ] 24 | securityContext: 25 | readOnlyRootFilesystem: true 26 | allowPrivilegeEscalation: false 27 | runAsNonRoot: true 28 | runAsUser: 10324 29 | capabilities: 30 | drop: 31 | - ALL 32 | resources: 33 | requests: 34 | cpu: 30m 35 | memory: 64Mi 36 | limits: 37 | cpu: 100m 38 | memory: 128Mi 39 | kind: List 40 | metadata: 41 | resourceVersion: '' 42 | selfLink: '' -------------------------------------------------------------------------------- /e2e/tests/assets/non-deprecated/deployment-apps-v1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: utilities 5 | labels: 6 | app: utilities 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: utilities 12 | template: 13 | metadata: 14 | labels: 15 | app: utilities 16 | spec: 17 | containers: 18 | - name: utilities 19 | image: quay.io/sudermanjr/utilities:latest 20 | # Just spin & wait forever 21 | command: [ "/bin/bash", "-c", "--" ] 22 | args: [ "while true; do sleep 30; done;" ] 23 | securityContext: 24 | readOnlyRootFilesystem: true 25 | allowPrivilegeEscalation: false 26 | runAsNonRoot: true 27 | runAsUser: 10324 28 | capabilities: 29 | drop: 30 | - ALL 31 | resources: 32 | requests: 33 | cpu: 30m 34 | memory: 64Mi 35 | limits: 36 | cpu: 100m 37 | memory: 128Mi 38 | -------------------------------------------------------------------------------- /embed.go: -------------------------------------------------------------------------------- 1 | // plutoversionsfile makes the Pluto versions.yaml file available to the pluto 2 | // binary and others that import Pluto packages. 3 | package plutoversionsfile 4 | 5 | import ( 6 | _ "embed" 7 | ) 8 | 9 | var ( 10 | //go:embed versions.yaml 11 | plutoVersionsFileContent []byte 12 | ) 13 | 14 | // Content returns the Pluto versions.yaml file as a slice of bytes. 15 | func Content() []byte { 16 | return plutoVersionsFileContent 17 | } 18 | -------------------------------------------------------------------------------- /fairwinds-insights.yaml: -------------------------------------------------------------------------------- 1 | options: 2 | organization: fairwinds-production 3 | baseBranch: master 4 | repositoryName: FairwindsOps/pluto 5 | 6 | # These images will be scanned for vulnerabilities. 7 | images: 8 | docker: 9 | - quay.io/fairwinds/pluto:$CIRCLE_SHA1 10 | -------------------------------------------------------------------------------- /github-action/action.yml: -------------------------------------------------------------------------------- 1 | name: "Download pluto binary" 2 | description: "Downloads the pluto binary by pulling the container image" 3 | inputs: 4 | IMAGE_PULL_URL: 5 | description: "pull url for container image, i.e.: us-docker.pkg.dev/fairwinds-ops/oss/pluto" 6 | default: "us-docker.pkg.dev/fairwinds-ops/oss/pluto" 7 | required: true 8 | IMAGE_TAG: 9 | description: "tag for container image, i.e.: v5" 10 | default: "v5" 11 | required: true 12 | runs: 13 | using: "composite" 14 | steps: 15 | - name: Download pluto binary 16 | shell: bash 17 | run: | 18 | podman pull ${{ inputs.IMAGE_PULL_URL }}:${{ inputs.IMAGE_TAG }} 19 | podman cp $(podman create --rm ${{ inputs.IMAGE_PULL_URL }}:${{ inputs.IMAGE_TAG }}):/pluto /usr/local/bin/pluto 20 | 21 | pluto --help 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/fairwindsops/pluto/v5 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.1 6 | 7 | require ( 8 | github.com/olekukonko/tablewriter v0.0.5 9 | github.com/spf13/cobra v1.9.1 10 | github.com/spf13/pflag v1.0.6 11 | github.com/spf13/viper v1.20.1 12 | github.com/stretchr/testify v1.10.0 13 | github.com/thoas/go-funk v0.9.3 14 | golang.org/x/mod v0.24.0 15 | gopkg.in/yaml.v3 v3.0.1 16 | helm.sh/helm/v3 v3.17.3 17 | k8s.io/api v0.33.0 18 | k8s.io/apimachinery v0.33.0 19 | k8s.io/client-go v0.33.0 20 | k8s.io/klog/v2 v2.130.1 21 | sigs.k8s.io/controller-runtime v0.20.4 22 | ) 23 | 24 | require ( 25 | github.com/Masterminds/semver/v3 v3.3.1 // indirect 26 | github.com/Masterminds/squirrel v1.5.4 // indirect 27 | github.com/cyphar/filepath-securejoin v0.4.1 // indirect 28 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 29 | github.com/emicklei/go-restful/v3 v3.12.2 // indirect 30 | github.com/fsnotify/fsnotify v1.9.0 // indirect 31 | github.com/fxamacker/cbor/v2 v2.8.0 // indirect 32 | github.com/go-gorp/gorp/v3 v3.1.0 // indirect 33 | github.com/go-logr/logr v1.4.2 // indirect 34 | github.com/go-openapi/jsonpointer v0.21.1 // indirect 35 | github.com/go-openapi/jsonreference v0.21.0 // indirect 36 | github.com/go-openapi/swag v0.23.1 // indirect 37 | github.com/go-viper/mapstructure/v2 v2.2.1 // indirect 38 | github.com/gogo/protobuf v1.3.2 // indirect 39 | github.com/google/gnostic-models v0.6.9 // indirect 40 | github.com/google/go-cmp v0.7.0 // indirect 41 | github.com/google/uuid v1.6.0 // indirect 42 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 43 | github.com/jmoiron/sqlx v1.4.0 // indirect 44 | github.com/josharian/intern v1.0.0 // indirect 45 | github.com/json-iterator/go v1.1.12 // indirect 46 | github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect 47 | github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect 48 | github.com/lib/pq v1.10.9 // indirect 49 | github.com/mailru/easyjson v0.9.0 // indirect 50 | github.com/mattn/go-runewidth v0.0.16 // indirect 51 | github.com/mitchellh/copystructure v1.2.0 // indirect 52 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 53 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 54 | github.com/modern-go/reflect2 v1.0.2 // indirect 55 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 56 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect 57 | github.com/pkg/errors v0.9.1 // indirect 58 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 59 | github.com/rivo/uniseg v0.4.7 // indirect 60 | github.com/rubenv/sql-migrate v1.8.0 // indirect 61 | github.com/sagikazarmark/locafero v0.9.0 // indirect 62 | github.com/sourcegraph/conc v0.3.0 // indirect 63 | github.com/spf13/afero v1.14.0 // indirect 64 | github.com/spf13/cast v1.8.0 // indirect 65 | github.com/subosito/gotenv v1.6.0 // indirect 66 | github.com/x448/float16 v0.8.4 // indirect 67 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect 68 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect 69 | github.com/xeipuuv/gojsonschema v1.2.0 // indirect 70 | go.uber.org/multierr v1.11.0 // indirect 71 | golang.org/x/net v0.40.0 // indirect 72 | golang.org/x/oauth2 v0.30.0 // indirect 73 | golang.org/x/sys v0.33.0 // indirect 74 | golang.org/x/term v0.32.0 // indirect 75 | golang.org/x/text v0.25.0 // indirect 76 | golang.org/x/time v0.11.0 // indirect 77 | golang.org/x/tools v0.33.0 // indirect 78 | google.golang.org/protobuf v1.36.6 // indirect 79 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 80 | gopkg.in/inf.v0 v0.9.1 // indirect 81 | k8s.io/apiextensions-apiserver v0.33.0 // indirect 82 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect 83 | k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 // indirect 84 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect 85 | sigs.k8s.io/randfill v1.0.0 // indirect 86 | sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect 87 | sigs.k8s.io/yaml v1.4.0 // indirect 88 | ) 89 | -------------------------------------------------------------------------------- /img/pluto-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FairwindsOps/pluto/36af4af118b8c9b218e329d020926051159fee61/img/pluto-logo.png -------------------------------------------------------------------------------- /orb/@orb.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | description: | 4 | Pluto is a utility to help users find deprecated Kubernetes apiVersions in their code repositories and their helm releases. 5 | display: 6 | home_url: https://pluto.docs.fairwinds.com 7 | source_url: https://github.com/FairwindsOps/pluto 8 | -------------------------------------------------------------------------------- /orb/commands/configure_env.yml: -------------------------------------------------------------------------------- 1 | description: Configures pluto environment variables. 2 | parameters: 3 | executor: 4 | description: The name of custom executor to use. Only recommended for development. 5 | type: executor 6 | default: default 7 | ignore-deprecations: 8 | type: boolean 9 | default: false 10 | description: Exit Code 3 is ignored, useful if you do not want the job to fail if deprecated APIs are detected. 11 | ignore-removals: 12 | type: boolean 13 | default: false 14 | description: Exit Code 3 is ignored, useful if you do not want the job to fail if removed APIs are detected. 15 | target-versions: 16 | description: You can target the Kubernetes version you are concerned with. If blank defaults to latest. 17 | type: string 18 | default: "" 19 | steps: 20 | - run: 21 | name: configure Pluto env vars 22 | command: | 23 | #!/bin/bash 24 | 25 | set -e 26 | 27 | echo PLUTO_IGNORE_DEPRECATIONS=<> >> $BASH_ENV 28 | echo PLUTO_IGNORE_REMOVALS=<> >> $BASH_ENV 29 | echo PLUTO_TARGET_VERSIONS=<> >> $BASH_ENV 30 | -------------------------------------------------------------------------------- /orb/commands/detect.yml: -------------------------------------------------------------------------------- 1 | description: Detecting deprecated Kubernetes apiVersions within your repository. 2 | parameters: 3 | file: 4 | description: The file to scan. 5 | type: string 6 | default: "" 7 | executor: 8 | description: The name of custom executor to use. Only recommended for development. 9 | type: executor 10 | default: default 11 | steps: 12 | - run: 13 | name: Pluto detect 14 | environment: 15 | PLUTO_FILE: <> 16 | command: <> 17 | -------------------------------------------------------------------------------- /orb/commands/detect_files.yml: -------------------------------------------------------------------------------- 1 | description: Detecting deprecated Kubernetes apiVersions within your repository. 2 | parameters: 3 | directory: 4 | description: The directory to scan. If blank defaults to current directory. 5 | type: string 6 | default: "" 7 | executor: 8 | description: The name of custom executor to use. Only recommended for development. 9 | type: executor 10 | default: default 11 | steps: 12 | - run: 13 | name: Pluto detect-files 14 | environment: 15 | PLUTO_DIRECTORY: <> 16 | command: <> 17 | -------------------------------------------------------------------------------- /orb/commands/install.yml: -------------------------------------------------------------------------------- 1 | description: Installs the pluto command. 2 | parameters: 3 | executor: 4 | description: The name of custom executor to use. Only recommended for development. 5 | type: executor 6 | default: default 7 | version: 8 | description: The version of pluto to install. Defaults to latest stable. 9 | type: string 10 | default: "" 11 | steps: 12 | - run: 13 | name: Install Pluto 14 | environment: 15 | VERSION: <> 16 | command: <> 17 | -------------------------------------------------------------------------------- /orb/examples/detect.yml: -------------------------------------------------------------------------------- 1 | description: | 2 | A workflow for detecting deprecated Kubernetes apiVersions for a specific file. 3 | usage: 4 | version: 2.1 5 | orbs: 6 | pluto: fairwinds/pluto@5 7 | workflows: 8 | detect_files: 9 | jobs: 10 | - pluto/detect_files: 11 | file: ./K8s/Descriptors/ingress.yml 12 | ignore-deprecations: true 13 | ignore-removals: false 14 | target-versions: "k8s=v1.21" 15 | -------------------------------------------------------------------------------- /orb/examples/detect_files.yml: -------------------------------------------------------------------------------- 1 | description: | 2 | A workflow for detecting deprecated Kubernetes apiVersions within your repository. 3 | usage: 4 | version: 2.1 5 | orbs: 6 | pluto: fairwinds/pluto@5 7 | workflows: 8 | detect_files: 9 | jobs: 10 | - pluto/detect_files: 11 | directory: ./K8s/Descriptors 12 | ignore-deprecations: true 13 | ignore-removals: false 14 | target-versions: "k8s=v1.21" 15 | -------------------------------------------------------------------------------- /orb/executors/default.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | version: 3 | type: string 4 | default: "stable" 5 | docker: 6 | - image: cimg/base:<> 7 | -------------------------------------------------------------------------------- /orb/jobs/detect.yml: -------------------------------------------------------------------------------- 1 | description: > 2 | A workflow for detecting deprecated Kubernetes apiVersions within your repository. 3 | parameters: 4 | checkout: 5 | type: boolean 6 | default: true 7 | description: "Perform checkout as first step in job." 8 | executor: 9 | description: The name of custom executor to use. Only recommended for development. 10 | type: executor 11 | default: default 12 | file: 13 | description: The file to scan. Required. 14 | type: string 15 | default: "" 16 | use-external-context: 17 | type: boolean 18 | default: false 19 | description: If this is true, then the configure_env step will be skipped. 20 | ignore-deprecations: 21 | type: boolean 22 | default: false 23 | description: Exit Code 3 is ignored, useful if you do not want the job to fail if deprecated APIs are detected. 24 | ignore-removals: 25 | type: boolean 26 | default: false 27 | description: Exit Code 3 is ignored, useful if you do not want the job to fail if removed APIs are detected. 28 | target-versions: 29 | description: You can target the Kubernetes version you are concerned with. If blank defaults to latest. 30 | type: string 31 | default: "" 32 | version: 33 | description: Version of Pluto to use. Defaults to latest stable. 34 | type: string 35 | default: "" 36 | executor: <> 37 | steps: 38 | - when: 39 | condition: << parameters.checkout >> 40 | steps: 41 | - checkout 42 | - install: 43 | version: <> 44 | - when: 45 | condition: 46 | not: << parameters.use-external-context >> 47 | steps: 48 | - configure_env: 49 | ignore-deprecations: << parameters.ignore-deprecations >> 50 | ignore-removals: << parameters.ignore-removals >> 51 | target-versions: << parameters.target-versions >> 52 | - detect: 53 | file: <> 54 | -------------------------------------------------------------------------------- /orb/jobs/detect_files.yml: -------------------------------------------------------------------------------- 1 | description: > 2 | A workflow for detecting deprecated Kubernetes apiVersions within your repository. 3 | parameters: 4 | checkout: 5 | type: boolean 6 | default: true 7 | description: "Perform checkout as first step in job." 8 | directory: 9 | description: The directory to scan. If blank defaults to current directory. 10 | type: string 11 | default: "" 12 | executor: 13 | description: The name of custom executor to use. Only recommended for development. 14 | type: executor 15 | default: default 16 | use-external-context: 17 | type: boolean 18 | default: false 19 | description: If this is true, then the configure_env step will be skipped. 20 | ignore-deprecations: 21 | type: boolean 22 | default: false 23 | description: Exit Code 3 is ignored, useful if you do not want the job to fail if deprecated APIs are detected. 24 | ignore-removals: 25 | type: boolean 26 | default: false 27 | description: Exit Code 3 is ignored, useful if you do not want the job to fail if removed APIs are detected. 28 | target-versions: 29 | description: You can target the Kubernetes version you are concerned with. If blank defaults to latest. 30 | type: string 31 | default: "" 32 | version: 33 | description: Version of Pluto to use. Defaults to latest stable. 34 | type: string 35 | default: "" 36 | executor: <> 37 | steps: 38 | - when: 39 | condition: << parameters.checkout >> 40 | steps: 41 | - checkout 42 | - install: 43 | version: <> 44 | - when: 45 | condition: 46 | not: << parameters.use-external-context >> 47 | steps: 48 | - configure_env: 49 | ignore-deprecations: << parameters.ignore-deprecations >> 50 | ignore-removals: << parameters.ignore-removals >> 51 | target-versions: << parameters.target-versions >> 52 | - detect_files: 53 | directory: <> 54 | -------------------------------------------------------------------------------- /orb/scripts/detect.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [[ -z "${PLUTO_FILE}" ]]; then 5 | echo "Error: requires a file argument" 6 | exit 1 7 | fi 8 | 9 | pluto detect "$PLUTO_FILE" 10 | -------------------------------------------------------------------------------- /orb/scripts/detect_files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [[ -n "${PLUTO_DIRECTORY}" ]]; then 5 | PLUTO_ARGS="$PLUTO_ARGS --directory ${PLUTO_DIRECTORY}" 6 | fi 7 | 8 | export PLUTO_ARGS 9 | 10 | pluto detect-files "$PLUTO_ARGS" 11 | -------------------------------------------------------------------------------- /orb/scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ -z "${VERSION}" ]]; then 4 | VERSION=latest 5 | fi 6 | 7 | curl -s https://api.github.com/repos/FairwindsOps/pluto/releases/${VERSION} \ 8 | | grep "browser_download_url.*linux_amd64.tar.gz" \ 9 | | cut -d '"' -f 4 \ 10 | | wget -qi - 11 | 12 | tarball="$(find . -name "*linux_amd64.tar.gz")" 13 | tar -xzf $tarball 14 | 15 | sudo mv pluto /bin 16 | 17 | location="$(which pluto)" 18 | echo "Pluto binary location: $location" 19 | 20 | version="$(pluto version)" 21 | echo "Pluto binary version: $version" 22 | -------------------------------------------------------------------------------- /pkg/api/columns.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 FairwindsOps Inc 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 api 16 | 17 | import "fmt" 18 | 19 | // Column is an interface for printing columns 20 | type column interface { 21 | header() string 22 | value(output *Output) string 23 | } 24 | 25 | type columnList map[int]column 26 | 27 | // PossibleColumnNames is the list of implmented columns 28 | var PossibleColumnNames = []string{ 29 | "NAME", 30 | "FILEPATH", 31 | "NAMESPACE", 32 | "KIND", 33 | "VERSION", 34 | "REPLACEMENT", 35 | "DEPRECATED", 36 | "DEPRECATED IN", 37 | "REMOVED", 38 | "REMOVED IN", 39 | "COMPONENT", 40 | "REPL AVAIL", 41 | "REPL AVAIL IN", 42 | } 43 | 44 | var possibleColumns = []column{ 45 | new(name), 46 | new(namespace), 47 | new(kind), 48 | new(version), 49 | new(replacement), 50 | new(deprecated), 51 | new(deprecatedIn), 52 | new(removed), 53 | new(removedIn), 54 | new(component), 55 | new(filepath), 56 | new(replacementAvailable), 57 | new(replacementAvailableIn), 58 | } 59 | 60 | // name is the output name 61 | type name struct{} 62 | 63 | func (n name) header() string { return "NAME" } 64 | func (n name) value(output *Output) string { return output.Name } 65 | 66 | // filepath is the full path of the file 67 | type filepath struct{} 68 | 69 | func (f filepath) header() string { return "FILEPATH" } 70 | func (f filepath) value(output *Output) string { 71 | if output.FilePath == "" { 72 | return "" 73 | } 74 | return output.FilePath 75 | } 76 | 77 | // namespace is the output namespace if available 78 | type namespace struct{} 79 | 80 | func (ns namespace) header() string { return "NAMESPACE" } 81 | func (ns namespace) value(output *Output) string { 82 | if output.Namespace == "" { 83 | return "" 84 | } 85 | return output.Namespace 86 | } 87 | 88 | // kind is the output apiVersion kind 89 | type kind struct{} 90 | 91 | func (k kind) header() string { return "KIND" } 92 | func (k kind) value(output *Output) string { return output.APIVersion.Kind } 93 | 94 | // version is the output apiVersion 95 | type version struct{} 96 | 97 | func (v version) header() string { return "VERSION" } 98 | func (v version) value(output *Output) string { return output.APIVersion.Name } 99 | 100 | // replacement is the output replacement apiVersion 101 | type replacement struct{} 102 | 103 | func (r replacement) header() string { return "REPLACEMENT" } 104 | func (r replacement) value(output *Output) string { return output.APIVersion.ReplacementAPI } 105 | 106 | // deprecated is the output for the boolean Deprecated 107 | type deprecated struct{} 108 | 109 | func (d deprecated) header() string { return "DEPRECATED" } 110 | func (d deprecated) value(output *Output) string { return fmt.Sprintf("%t", output.Deprecated) } 111 | 112 | // removed is the output for the boolean Deprecated 113 | type removed struct{} 114 | 115 | func (r removed) header() string { return "REMOVED" } 116 | func (r removed) value(output *Output) string { return fmt.Sprintf("%t", output.Removed) } 117 | 118 | // deprecatedIn is the string value of when an output was deprecated 119 | type deprecatedIn struct{} 120 | 121 | func (di deprecatedIn) header() string { return "DEPRECATED IN" } 122 | func (di deprecatedIn) value(output *Output) string { return output.APIVersion.DeprecatedIn } 123 | 124 | // removedIn is the string value of when an output was deprecated 125 | type removedIn struct{} 126 | 127 | func (ri removedIn) header() string { return "REMOVED IN" } 128 | func (ri removedIn) value(output *Output) string { return output.APIVersion.RemovedIn } 129 | 130 | // component is the component that the deprecation came from 131 | type component struct{} 132 | 133 | func (c component) header() string { return "COMPONENT" } 134 | func (c component) value(output *Output) string { return output.APIVersion.Component } 135 | 136 | // replacementAvailable is the output for the boolean ReplacementAvailable 137 | type replacementAvailable struct{} 138 | 139 | func (ra replacementAvailable) header() string { return "REPL AVAIL" } 140 | func (ra replacementAvailable) value(output *Output) string { 141 | return fmt.Sprintf("%t", output.ReplacementAvailable) 142 | } 143 | 144 | // replacementAvailableIn is the string value of when an output was ReplacementAvailableIn 145 | type replacementAvailableIn struct{} 146 | 147 | func (rai replacementAvailableIn) header() string { return "REPL AVAIL IN" } 148 | func (rai replacementAvailableIn) value(output *Output) string { 149 | return output.APIVersion.ReplacementAvailableIn 150 | } 151 | 152 | // normalColumns returns the list of columns for -onormal 153 | func (instance *Instance) normalColumns() columnList { 154 | columnList := columnList{ 155 | 0: new(name), 156 | 1: new(kind), 157 | 2: new(version), 158 | 3: new(replacement), 159 | 4: new(removed), 160 | 5: new(deprecated), 161 | 6: new(replacementAvailable), 162 | } 163 | return columnList 164 | } 165 | 166 | // wideColumns returns the list of columns for -owide 167 | func (instance *Instance) wideColumns() columnList { 168 | columnList := columnList{ 169 | 0: new(name), 170 | 1: new(namespace), 171 | 2: new(kind), 172 | 3: new(version), 173 | 4: new(replacement), 174 | 5: new(deprecated), 175 | 6: new(deprecatedIn), 176 | 7: new(removed), 177 | 8: new(removedIn), 178 | 9: new(replacementAvailable), 179 | 10: new(replacementAvailableIn), 180 | } 181 | return columnList 182 | } 183 | 184 | // customColumns returns a custom list of columns based on names 185 | func (instance *Instance) customColumns() columnList { 186 | outputColumns := make(map[int]column) 187 | for i, d := range instance.CustomColumns { 188 | for _, c := range possibleColumns { 189 | if d == c.header() { 190 | outputColumns[i] = c 191 | } 192 | } 193 | } 194 | return outputColumns 195 | } 196 | -------------------------------------------------------------------------------- /pkg/api/helpers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 FairwindsOps Inc 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 api 16 | 17 | import ( 18 | "os" 19 | ) 20 | 21 | // IsFileOrStdin detects if a file exists, or returns true if - is passed 22 | func IsFileOrStdin(name string) bool { 23 | if name == "-" { 24 | return true 25 | } 26 | info, err := os.Stat(name) 27 | if os.IsNotExist(err) { 28 | return false 29 | } 30 | return !info.IsDir() 31 | } 32 | 33 | // StringInSlice returns true if the string is contained in the slice 34 | func StringInSlice(s string, slice []string) bool { 35 | for _, v := range slice { 36 | if s == v { 37 | return true 38 | } 39 | } 40 | return false 41 | } 42 | -------------------------------------------------------------------------------- /pkg/api/helpers_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 FairwindsOps Inc 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 api 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func Test_StringInSlice(t *testing.T) { 24 | type args struct { 25 | a string 26 | list []string 27 | } 28 | tests := []struct { 29 | name string 30 | args args 31 | want bool 32 | }{ 33 | { 34 | name: "test true", 35 | args: args{ 36 | a: "string", 37 | list: []string{"test", "string"}, 38 | }, 39 | want: true, 40 | }, 41 | { 42 | name: "test false", 43 | args: args{ 44 | a: "string", 45 | list: []string{"test", "nothere"}, 46 | }, 47 | want: false, 48 | }, 49 | } 50 | for _, tt := range tests { 51 | t.Run(tt.name, func(t *testing.T) { 52 | if got := StringInSlice(tt.args.a, tt.args.list); got != tt.want { 53 | assert.EqualValues(t, tt.want, got) 54 | } 55 | }) 56 | } 57 | } 58 | 59 | func TestIsFileOrStdin(t *testing.T) { 60 | tests := []struct { 61 | name string 62 | input string 63 | want bool 64 | }{ 65 | {"stdin", "-", true}, 66 | {"no file", "notafile.foo", false}, 67 | {"file", "helpers.go", true}, 68 | } 69 | for _, tt := range tests { 70 | t.Run(tt.name, func(t *testing.T) { 71 | got := IsFileOrStdin(tt.input) 72 | assert.Equal(t, tt.want, got) 73 | }) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /pkg/api/output.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 FairwindsOps Inc 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 api 16 | 17 | import ( 18 | "encoding/csv" 19 | "encoding/json" 20 | "fmt" 21 | "os" 22 | "sort" 23 | "text/tabwriter" 24 | 25 | "github.com/olekukonko/tablewriter" 26 | 27 | "gopkg.in/yaml.v3" 28 | ) 29 | 30 | var padChar = byte(' ') 31 | 32 | // Output is a thing that has an apiVersion in it 33 | type Output struct { 34 | // Name is the name of the object in question. 35 | // This might be an object name, or a release 36 | Name string `json:"name,omitempty" yaml:"name,omitempty"` 37 | // FilePath is the full path of the file if the output came from a file 38 | FilePath string `json:"filePath,omitempty" yaml:"filePath,omitempty"` 39 | // Namespace is the namespace that the object is in 40 | // The output may resolve this to UNKNOWN if there is no way of determining it 41 | Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` 42 | // APIVersion is the version object corresponding to this output 43 | APIVersion *Version `json:"api,omitempty" yaml:"api,omitempty"` 44 | // Deprecated is a boolean indicating whether or not the version is deprecated 45 | Deprecated bool `json:"deprecated" yaml:"deprecated"` 46 | // Removed is a boolean indicating whether or not the version has been removed 47 | Removed bool `json:"removed" yaml:"removed"` 48 | // ReplacementAvailable is a boolean indicating whether or not the replacement is available 49 | ReplacementAvailable bool `json:"replacementAvailable" yaml:"replacementAvailable"` 50 | // CustomColumns is a list of column headers to be displayed with -ocustom or -omarkdown 51 | CustomColumns []string `json:"-" yaml:"-"` 52 | } 53 | 54 | // Instance is an instance of the API. This holds configuration for a "run" of Pluto 55 | type Instance struct { 56 | Outputs []*Output `json:"items,omitempty" yaml:"items,omitempty"` 57 | IgnoreDeprecations bool `json:"-" yaml:"-"` 58 | IgnoreRemovals bool `json:"-" yaml:"-"` 59 | IgnoreUnavailableReplacements bool `json:"-" yaml:"-"` 60 | OnlyShowRemoved bool `json:"-" yaml:"-"` 61 | NoHeaders bool `json:"-" yaml:"-"` 62 | OutputFormat string `json:"-" yaml:"-"` 63 | TargetVersions map[string]string `json:"target-versions,omitempty" yaml:"target-versions,omitempty"` 64 | DeprecatedVersions []Version `json:"-" yaml:"-"` 65 | CustomColumns []string `json:"-" yaml:"-"` 66 | Components []string `json:"-" yaml:"-"` 67 | } 68 | 69 | // DisplayOutput prints the output based on desired variables 70 | func (instance *Instance) DisplayOutput() error { 71 | if len(instance.Outputs) == 0 && (instance.OutputFormat == "normal" || instance.OutputFormat == "wide") { 72 | fmt.Println("There were no resources found with known deprecated apiVersions.") 73 | return nil 74 | } 75 | 76 | instance.FilterOutput() 77 | var err error 78 | var outData []byte 79 | switch instance.OutputFormat { 80 | case "normal": 81 | c := instance.normalColumns() 82 | t := instance.tabOut(c) 83 | err = t.Flush() 84 | if err != nil { 85 | return err 86 | } 87 | return nil 88 | case "wide": 89 | c := instance.wideColumns() 90 | t := instance.tabOut(c) 91 | err = t.Flush() 92 | if err != nil { 93 | return err 94 | } 95 | return nil 96 | case "custom": 97 | c := instance.customColumns() 98 | t := instance.tabOut(c) 99 | err = t.Flush() 100 | if err != nil { 101 | return err 102 | } 103 | case "json": 104 | outData, err = json.Marshal(instance) 105 | if err != nil { 106 | return err 107 | } 108 | fmt.Println(string(outData)) 109 | case "yaml": 110 | outData, err = yaml.Marshal(instance) 111 | if err != nil { 112 | return err 113 | } 114 | fmt.Println(string(outData)) 115 | case "markdown": 116 | var c columnList 117 | if len(instance.CustomColumns) >= 1 { 118 | c = instance.customColumns() 119 | } else { 120 | c = instance.wideColumns() 121 | } 122 | t := instance.markdownOut(c) 123 | if t != nil { 124 | t.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) 125 | t.SetCenterSeparator("|") 126 | t.Render() 127 | } 128 | case "csv": 129 | var c columnList 130 | if len(instance.CustomColumns) >= 1 { 131 | c = instance.customColumns() 132 | } else { 133 | c = instance.wideColumns() 134 | } 135 | csvWriter, err := instance.csvOut(c) 136 | if err != nil { 137 | return err 138 | } 139 | 140 | csvWriter.Flush() 141 | 142 | err = csvWriter.Error() 143 | if err != nil { 144 | return err 145 | } 146 | } 147 | return nil 148 | } 149 | 150 | // FilterOutput filters the outputs that get printed 151 | // first it fills out the Deprecated and Removed booleans 152 | // then it returns the outputs that are either deprecated or removed 153 | // and in the component list 154 | // additionally, if instance.OnlyShowDeprecated is true, it will remove the 155 | // apiVersions that are deprecated but not removed 156 | func (instance *Instance) FilterOutput() { 157 | var usableOutputs []*Output 158 | for _, output := range instance.Outputs { 159 | output.Deprecated = output.APIVersion.isDeprecatedIn(instance.TargetVersions) 160 | output.Removed = output.APIVersion.isRemovedIn(instance.TargetVersions) 161 | output.ReplacementAvailable = output.APIVersion.isReplacementAvailableIn(instance.TargetVersions) 162 | switch instance.OnlyShowRemoved { 163 | case false: 164 | if output.Deprecated || output.Removed { 165 | if StringInSlice(output.APIVersion.Component, instance.Components) { 166 | usableOutputs = append(usableOutputs, output) 167 | } 168 | } 169 | case true: 170 | if output.Removed { 171 | if StringInSlice(output.APIVersion.Component, instance.Components) { 172 | usableOutputs = append(usableOutputs, output) 173 | } 174 | } 175 | } 176 | } 177 | instance.Outputs = usableOutputs 178 | } 179 | 180 | // removeDeprecatedOnly is a list replacement operation 181 | func (instance *Instance) tabOut(columns columnList) *tabwriter.Writer { 182 | w := new(tabwriter.Writer) 183 | w.Init(os.Stdout, 0, 15, 2, padChar, 0) 184 | 185 | if len(instance.Outputs) == 0 { 186 | _, _ = fmt.Fprintln(w, "No output to display") 187 | return w 188 | } 189 | 190 | columnIndexes := make([]int, 0, len(columns)) 191 | for k := range columns { 192 | columnIndexes = append(columnIndexes, k) 193 | } 194 | sort.Ints(columnIndexes) 195 | 196 | if !instance.NoHeaders { 197 | var headers string 198 | for _, k := range columnIndexes { 199 | if k == 0 { 200 | headers = fmt.Sprintf("%s\t", columns[k].header()) 201 | } else { 202 | headers = fmt.Sprintf("%s %s\t", headers, columns[k].header()) 203 | } 204 | } 205 | _, _ = fmt.Fprintln(w, headers) 206 | } 207 | 208 | var rows string 209 | for _, o := range instance.Outputs { 210 | var row string 211 | for _, k := range columnIndexes { 212 | if k == 0 { 213 | row = fmt.Sprintf("%s\t", columns[k].value(o)) 214 | } else { 215 | row = fmt.Sprintf("%s %s\t", row, columns[k].value(o)) 216 | } 217 | } 218 | rows = rows + row + "\n" 219 | } 220 | 221 | _, _ = fmt.Fprintln(w, rows) 222 | 223 | return w 224 | } 225 | 226 | func (instance *Instance) markdownOut(columns columnList) *tablewriter.Table { 227 | table := tablewriter.NewWriter(os.Stdout) 228 | 229 | if len(instance.Outputs) == 0 { 230 | _, _ = fmt.Println("No output to display") 231 | return nil 232 | } 233 | 234 | columnIndexes := make([]int, 0, len(columns)) 235 | for k := range columns { 236 | columnIndexes = append(columnIndexes, k) 237 | } 238 | sort.Ints(columnIndexes) 239 | 240 | if !instance.NoHeaders { 241 | var headers []string 242 | for _, k := range columnIndexes { 243 | headers = append(headers, columns[k].header()) 244 | } 245 | 246 | table.SetHeader(headers) 247 | } 248 | 249 | for _, o := range instance.Outputs { 250 | var row []string 251 | for _, k := range columnIndexes { 252 | row = append(row, columns[k].value(o)) 253 | } 254 | table.Append(row) 255 | } 256 | 257 | return table 258 | } 259 | 260 | func (instance *Instance) csvOut(columns columnList) (*csv.Writer, error) { 261 | csvWriter := csv.NewWriter(os.Stdout) 262 | 263 | if len(instance.Outputs) == 0 { 264 | _, _ = fmt.Println("No output to display") 265 | } 266 | 267 | columnIndexes := make([]int, 0, len(columns)) 268 | for k := range columns { 269 | columnIndexes = append(columnIndexes, k) 270 | } 271 | sort.Ints(columnIndexes) 272 | 273 | var csvData [][]string 274 | 275 | if !instance.NoHeaders { 276 | var headers []string 277 | for _, k := range columnIndexes { 278 | headers = append(headers, columns[k].header()) 279 | } 280 | 281 | csvData = append(csvData, headers) 282 | } 283 | 284 | for _, o := range instance.Outputs { 285 | var row []string 286 | for _, k := range columnIndexes { 287 | row = append(row, columns[k].value(o)) 288 | } 289 | csvData = append(csvData, row) 290 | } 291 | 292 | for i := range csvData { 293 | if err := csvWriter.Write(csvData[i]); err != nil { 294 | return nil, err 295 | } 296 | } 297 | 298 | return csvWriter, nil 299 | } 300 | 301 | // GetReturnCode checks for deprecated versions and returns a code. 302 | // takes a boolean to ignore any errors. 303 | // exit 2 - version deprecated 304 | // exit 3 - version removed 305 | // exit 4 - replacement is unavailable in target version 306 | func (instance *Instance) GetReturnCode() int { 307 | returnCode := 0 308 | var deprecations int 309 | var removals int 310 | var unavailableReplacements int 311 | for _, output := range instance.Outputs { 312 | if output.APIVersion.isRemovedIn(instance.TargetVersions) { 313 | removals = removals + 1 314 | } 315 | if output.APIVersion.isDeprecatedIn(instance.TargetVersions) { 316 | if output.APIVersion.isReplacementAvailableIn(instance.TargetVersions) || !instance.IgnoreUnavailableReplacements { 317 | deprecations = deprecations + 1 318 | } 319 | } 320 | if !output.APIVersion.isReplacementAvailableIn(instance.TargetVersions) { 321 | unavailableReplacements = unavailableReplacements + 1 322 | } 323 | } 324 | 325 | if deprecations > 0 && !instance.IgnoreDeprecations { 326 | returnCode = 2 327 | } 328 | if removals > 0 && !instance.IgnoreRemovals { 329 | returnCode = 3 330 | } 331 | if unavailableReplacements > 0 && !instance.IgnoreUnavailableReplacements { 332 | returnCode = 4 333 | } 334 | return returnCode 335 | } 336 | -------------------------------------------------------------------------------- /pkg/api/test_data/versions.yaml: -------------------------------------------------------------------------------- 1 | deprecated-versions: 2 | - version: extensions/v1beta1 3 | kind: Deployment 4 | deprecated-in: v1.9.0 5 | removed-in: v1.16.0 6 | replacement-api: apps/v1 7 | component: k8s 8 | - version: apps/v1beta2 9 | kind: Deployment 10 | deprecated-in: v1.9.0 11 | removed-in: v1.16.0 12 | replacement-api: apps/v1 13 | component: k8s 14 | - version: networking.istio.io/v1alpha3 15 | kind: "" 16 | deprecated-in: v1.5.0 17 | removed-in: "" 18 | replacement-api: networking.istio.io/v1beta1 19 | component: istio 20 | - version: certmanager.k8s.io/v1alpha1 21 | kind: Challenge 22 | deprecated-in: v0.11.0 23 | removed-in: v0.11.0 24 | replacement-api: cert-manager.io/v1alpha2 25 | component: cert-manager 26 | target-versions: 27 | cert-manager: v1.5.3 28 | istio: v1.11.0 29 | k8s: v1.22.0 30 | -------------------------------------------------------------------------------- /pkg/discovery-api/discovery_api.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 FairwindsOps Inc 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 | // Copyright 2020 Fairwinds 16 | // 17 | // Licensed under the Apache License, Version 2.0 (the "License"); 18 | // you may not use this file except in compliance with the License. 19 | // You may obtain a copy of the License at 20 | // 21 | // http://www.apache.org/licenses/LICENSE-2.0 22 | // 23 | // Unless required by applicable law or agreed to in writing, software 24 | // distributed under the License is distributed on an "AS IS" BASIS, 25 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26 | // See the License for the specific language governing permissions and 27 | // limitations under the License 28 | 29 | package discoveryapi 30 | 31 | import ( 32 | "context" 33 | "encoding/json" 34 | 35 | apierrors "k8s.io/apimachinery/pkg/api/errors" 36 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 37 | "k8s.io/apimachinery/pkg/runtime/schema" 38 | "k8s.io/client-go/discovery" 39 | "k8s.io/client-go/dynamic" 40 | "k8s.io/client-go/rest" 41 | "k8s.io/klog/v2" 42 | 43 | "github.com/fairwindsops/pluto/v5/pkg/api" 44 | "github.com/fairwindsops/pluto/v5/pkg/kube" 45 | ) 46 | 47 | // DiscoveryClient is the declaration to hold objects needed for client-go/discovery. 48 | type DiscoveryClient struct { 49 | ClientSet dynamic.Interface 50 | restConfig *rest.Config 51 | DiscoveryClient discovery.DiscoveryInterface 52 | Instance *api.Instance 53 | namespace string 54 | } 55 | 56 | // NewDiscoveryClient returns a new struct with config portions complete. 57 | func NewDiscoveryClient(namespace string, kubeContext string, instance *api.Instance, kubeConfigPath string) (*DiscoveryClient, error) { 58 | cl := &DiscoveryClient{ 59 | Instance: instance, 60 | } 61 | 62 | var err error 63 | cl.restConfig, err = kube.GetConfig(kubeContext, kubeConfigPath) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | if cl.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(cl.restConfig); err != nil { 69 | return nil, err 70 | } 71 | 72 | cl.namespace = namespace 73 | 74 | cl.ClientSet, err = dynamic.NewForConfig(cl.restConfig) 75 | if err != nil { 76 | return nil, err 77 | } 78 | return cl, nil 79 | } 80 | 81 | // GetApiResources discovers the api-resources for a cluster 82 | func (cl *DiscoveryClient) GetApiResources() error { 83 | resourcelist, err := cl.DiscoveryClient.ServerPreferredResources() 84 | if err != nil { 85 | if apierrors.IsNotFound(err) { 86 | return err 87 | } 88 | if apierrors.IsForbidden(err) { 89 | klog.Error("Failed to list objects for Name discovery. Permission denied! Please check if you have the proper authorization") 90 | return err 91 | } 92 | } 93 | 94 | gvrs := []schema.GroupVersionResource{} 95 | for _, rl := range resourcelist { 96 | for i := range rl.APIResources { 97 | if cl.namespace != "" && !rl.APIResources[i].Namespaced { 98 | continue 99 | } 100 | gv, _ := schema.ParseGroupVersion(rl.GroupVersion) 101 | ResourceName := rl.APIResources[i].Name 102 | g := schema.GroupVersionResource{Group: gv.Group, Version: gv.Version, Resource: ResourceName} 103 | gvrs = append(gvrs, g) 104 | } 105 | } 106 | 107 | var results []map[string]interface{} 108 | for _, g := range gvrs { 109 | nri := cl.ClientSet.Resource(g) 110 | var ri dynamic.ResourceInterface = nri 111 | if cl.namespace != "" { 112 | ri = nri.Namespace(cl.namespace) 113 | } 114 | klog.V(2).Infof("Retrieving : %s.%s.%s", g.Resource, g.Version, g.Group) 115 | rs, err := ri.List(context.TODO(), metav1.ListOptions{}) 116 | if err != nil { 117 | klog.V(2).Info("Failed to retrieve: ", g, err) 118 | continue 119 | } 120 | 121 | if len(rs.Items) == 0 { 122 | klog.V(2).Infof("No annotations for ResourceVer %s", rs.GetAPIVersion()) 123 | obj := rs.UnstructuredContent() 124 | data, err := json.Marshal(obj) 125 | if err != nil { 126 | klog.Error("Failed to marshal data ", err.Error()) 127 | return err 128 | } 129 | output, err := cl.Instance.IsVersioned(data) 130 | if err != nil { 131 | return err 132 | } 133 | if output == nil { 134 | continue 135 | } 136 | cl.Instance.Outputs = append(cl.Instance.Outputs, output...) 137 | 138 | } else { 139 | for _, r := range rs.Items { 140 | if jsonManifest, ok := r.GetAnnotations()["kubectl.kubernetes.io/last-applied-configuration"]; ok { 141 | var manifest map[string]interface{} 142 | 143 | err := json.Unmarshal([]byte(jsonManifest), &manifest) 144 | if err != nil { 145 | klog.Errorf("failed to parse 'last-applied-configuration' annotation of resource %s/%s: %s", r.GetNamespace(), r.GetName(), err.Error()) 146 | continue 147 | } 148 | if r.Object["kind"] != manifest["kind"] { 149 | klog.V(2).Infof("Object Kind %s does not match last-applied-configuration-kind %s. Skipping", r.Object["kind"], manifest["kind"]) 150 | continue 151 | } 152 | data, err := json.Marshal(manifest) 153 | if err != nil { 154 | klog.Error("Failed to marshal data ", err.Error()) 155 | return err 156 | } 157 | output, err := cl.Instance.IsVersioned(data) 158 | if err != nil { 159 | return err 160 | } 161 | cl.Instance.Outputs = append(cl.Instance.Outputs, output...) 162 | } 163 | } 164 | } 165 | 166 | } 167 | 168 | klog.V(6).Infof("Result from resources: %d", len(results)) 169 | return nil 170 | } 171 | -------------------------------------------------------------------------------- /pkg/discovery-api/discovery_api_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 FairwindsOps Inc 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 | // Copyright 2020 Fairwinds 16 | // 17 | // Licensed under the Apache License, Version 2.0 (the "License"); 18 | // you may not use this file except in compliance with the License. 19 | // You may obtain a copy of the License at 20 | // 21 | // http://www.apache.org/licenses/LICENSE-2.0 22 | // 23 | // Unless required by applicable law or agreed to in writing, software 24 | // distributed under the License is distributed on an "AS IS" BASIS, 25 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26 | // See the License for the specific language governing permissions and 27 | // limitations under the License 28 | 29 | package discoveryapi 30 | 31 | import ( 32 | "testing" 33 | 34 | "k8s.io/apimachinery/pkg/runtime" 35 | discoveryFake "k8s.io/client-go/discovery/fake" 36 | "k8s.io/client-go/dynamic/fake" 37 | ) 38 | 39 | func TestNewDiscoveryAPIClientValidEmpty(t *testing.T) { 40 | 41 | scheme := runtime.NewScheme() 42 | clientset := fake.NewSimpleDynamicClient(scheme) 43 | discoveryClient := discoveryFake.FakeDiscovery{} 44 | testOpts := DiscoveryClient{ 45 | ClientSet: clientset, 46 | DiscoveryClient: &discoveryClient, 47 | } 48 | 49 | err := testOpts.GetApiResources() 50 | if err != nil { 51 | t.Errorf("Unable to fetch API Resources") 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /pkg/finder/finder.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 FairwindsOps Inc 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 | // Copyright 2020 Fairwinds 16 | // 17 | // Licensed under the Apache License, Version 2.0 (the "License"); 18 | // you may not use this file except in compliance with the License. 19 | // You may obtain a copy of the License at 20 | // 21 | // http://www.apache.org/licenses/LICENSE-2.0 22 | // 23 | // Unless required by applicable law or agreed to in writing, software 24 | // distributed under the License is distributed on an "AS IS" BASIS, 25 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26 | // See the License for the specific language governing permissions and 27 | // limitations under the License 28 | 29 | package finder 30 | 31 | import ( 32 | "fmt" 33 | "os" 34 | "path/filepath" 35 | 36 | "k8s.io/klog/v2" 37 | 38 | "github.com/fairwindsops/pluto/v5/pkg/api" 39 | ) 40 | 41 | // Dir is the finder dirlication 42 | type Dir struct { 43 | RootPath string 44 | FileList []string 45 | Instance *api.Instance 46 | } 47 | 48 | // NewFinder returns a new struct with config portions complete. 49 | func NewFinder(path string, instance *api.Instance) *Dir { 50 | cfg := &Dir{ 51 | Instance: instance, 52 | } 53 | if path == "" { 54 | cwd, err := os.Getwd() 55 | if err != nil { 56 | klog.Fatal(err) 57 | } 58 | cfg.RootPath = cwd 59 | } else { 60 | cfg.RootPath = path 61 | } 62 | return cfg 63 | } 64 | 65 | // FindVersions runs the finder. This will populate the 66 | // dir struct with any files that might be versioned. 67 | func (dir *Dir) FindVersions() error { 68 | err := dir.listFiles() 69 | if err != nil { 70 | return err 71 | } 72 | err = dir.scanFiles() 73 | if err != nil { 74 | return err 75 | } 76 | if dir.Instance.Outputs != nil { 77 | for _, file := range dir.Instance.Outputs { 78 | klog.V(6).Infof("%s - %s - %s", file.APIVersion.DeprecatedIn, file.APIVersion.Name, file.Name) 79 | } 80 | } 81 | return nil 82 | } 83 | 84 | // listFiles gets a list of all the files in the directory. 85 | func (dir *Dir) listFiles() error { 86 | var files []string 87 | 88 | if _, err := os.Stat(dir.RootPath); os.IsNotExist(err) { 89 | return fmt.Errorf("specified path does not exist") 90 | } 91 | err := filepath.Walk(dir.RootPath, func(path string, info os.FileInfo, err error) error { 92 | if !info.IsDir() { 93 | files = append(files, path) 94 | } 95 | return nil 96 | }) 97 | if err != nil { 98 | return err 99 | } 100 | dir.FileList = files 101 | return nil 102 | } 103 | 104 | // scanFiles loops through the file list and finds versioned files 105 | // to add to the dir struct 106 | func (dir *Dir) scanFiles() error { 107 | for _, file := range dir.FileList { 108 | klog.V(8).Infof("processing file: %s", file) 109 | apiFile, err := dir.CheckForAPIVersion(file) 110 | if err != nil { 111 | klog.V(2).Infof("error scanning file %s: %s", file, err.Error()) 112 | } 113 | if apiFile != nil { 114 | dir.Instance.Outputs = append(dir.Instance.Outputs, apiFile...) 115 | } 116 | } 117 | return nil 118 | } 119 | 120 | // CheckForAPIVersion checks a filename to see if 121 | // it is an api-versioned Kubernetes object. 122 | // Returns the File object if it is. 123 | func (dir *Dir) CheckForAPIVersion(file string) ([]*api.Output, error) { 124 | data, err := os.ReadFile(file) 125 | if err != nil { 126 | return nil, err 127 | } 128 | outputs, err := dir.Instance.IsVersioned(data) 129 | if err != nil { 130 | return nil, err 131 | } 132 | 133 | cwd, err := os.Getwd() 134 | if err != nil { 135 | return nil, err 136 | } 137 | 138 | filePath := fmt.Sprintf("%s/%s", cwd, file) 139 | for _, output := range outputs { 140 | output.FilePath = filePath 141 | } 142 | return outputs, nil 143 | } 144 | -------------------------------------------------------------------------------- /pkg/finder/finder_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 FairwindsOps Inc 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 | // Copyright 2020 Fairwinds 16 | // 17 | // Licensed under the Apache License, Version 2.0 (the "License"); 18 | // you may not use this file except in compliance with the License. 19 | // You may obtain a copy of the License at 20 | // 21 | // http://www.apache.org/licenses/LICENSE-2.0 22 | // 23 | // Unless required by applicable law or agreed to in writing, software 24 | // distributed under the License is distributed on an "AS IS" BASIS, 25 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26 | // See the License for the specific language governing permissions and 27 | // limitations under the License 28 | 29 | package finder 30 | 31 | import ( 32 | "os" 33 | "testing" 34 | 35 | "github.com/fairwindsops/pluto/v5/pkg/api" 36 | "github.com/stretchr/testify/assert" 37 | ) 38 | 39 | var testPath = "testdata" 40 | 41 | var deploymentExtensionsV1Yaml = "testdata/deployment-extensions-v1beta1.yaml" 42 | var deploymentExtensionsV1YamlFile = []*api.Output{{ 43 | Name: "utilities", 44 | Namespace: "yaml-namespace", 45 | APIVersion: &api.Version{ 46 | Name: "extensions/v1beta1", 47 | Kind: "Deployment", 48 | DeprecatedIn: "v1.9.0", 49 | RemovedIn: "v1.16.0", 50 | ReplacementAPI: "apps/v1", 51 | Component: "k8s", 52 | }}, 53 | } 54 | 55 | var deploymentExtensionsV1JSON = "testdata/deployment-extensions-v1beta1.json" 56 | var deploymentExtensionsV1JSONFile = []*api.Output{{ 57 | Name: "utilities", 58 | Namespace: "json-namespace", 59 | APIVersion: &api.Version{ 60 | Name: "extensions/v1beta1", 61 | Kind: "Deployment", 62 | DeprecatedIn: "v1.9.0", 63 | RemovedIn: "v1.16.0", 64 | ReplacementAPI: "apps/v1", 65 | Component: "k8s", 66 | }}, 67 | } 68 | 69 | var testFiles = []string{ 70 | deploymentExtensionsV1JSON, 71 | deploymentExtensionsV1Yaml, 72 | "testdata/other.txt", 73 | } 74 | 75 | var testOutput = []*api.Output{ 76 | deploymentExtensionsV1JSONFile[0], 77 | deploymentExtensionsV1YamlFile[0], 78 | } 79 | 80 | var testVersionDeployment = api.Version{ 81 | Name: "extensions/v1beta1", 82 | Kind: "Deployment", 83 | DeprecatedIn: "v1.9.0", 84 | RemovedIn: "v1.16.0", 85 | ReplacementAPI: "apps/v1", 86 | Component: "k8s", 87 | } 88 | 89 | func newMockFinder(path string) *Dir { 90 | dir := &Dir{ 91 | RootPath: path, 92 | Instance: &api.Instance{ 93 | TargetVersions: map[string]string{ 94 | "k8s": "v1.16.0", 95 | "istio": "1.6.1", 96 | "cert-manager": "v0.15.0", 97 | }, 98 | DeprecatedVersions: []api.Version{ 99 | testVersionDeployment, 100 | }, 101 | IgnoreDeprecations: false, 102 | IgnoreRemovals: false, 103 | OutputFormat: "normal", 104 | }, 105 | } 106 | return dir 107 | } 108 | 109 | // patchFilePath exists because the filePath will be different 110 | // on every system. This asserts that the current working directory 111 | // is in the file path and then sets it to an empty string 112 | func patchFilePath(t *testing.T, outputs []*api.Output) { 113 | cwd, _ := os.Getwd() 114 | // Account for current working dir 115 | for _, output := range outputs { 116 | assert.Contains(t, output.FilePath, cwd) 117 | output.FilePath = "" 118 | } 119 | } 120 | 121 | func TestNewFinder(t *testing.T) { 122 | wd, _ := os.Getwd() 123 | tests := []struct { 124 | name string 125 | path string 126 | want *Dir 127 | }{ 128 | { 129 | name: "one", 130 | path: testPath, 131 | want: newMockFinder(testPath), 132 | }, 133 | { 134 | name: "cwd", 135 | path: "", 136 | want: &Dir{ 137 | RootPath: wd, 138 | Instance: &api.Instance{ 139 | TargetVersions: map[string]string{ 140 | "k8s": "v1.16.0", 141 | "istio": "1.6.1", 142 | "cert-manager": "v0.15.0", 143 | }, 144 | DeprecatedVersions: []api.Version{ 145 | testVersionDeployment, 146 | }, 147 | IgnoreDeprecations: false, 148 | IgnoreRemovals: false, 149 | OutputFormat: "normal", 150 | }, 151 | }, 152 | }, 153 | } 154 | for _, tt := range tests { 155 | t.Run(tt.name, func(t *testing.T) { 156 | got := NewFinder(tt.path, &api.Instance{ 157 | TargetVersions: map[string]string{ 158 | "k8s": "v1.16.0", 159 | "istio": "1.6.1", 160 | "cert-manager": "v0.15.0", 161 | }, 162 | DeprecatedVersions: []api.Version{ 163 | testVersionDeployment, 164 | }, 165 | IgnoreDeprecations: false, 166 | IgnoreRemovals: false, 167 | OutputFormat: "normal", 168 | }, 169 | ) 170 | assert.Equal(t, tt.want, got) 171 | }) 172 | } 173 | } 174 | 175 | func TestDir_listFiles(t *testing.T) { 176 | 177 | tests := []struct { 178 | name string 179 | wantErr bool 180 | fileList []string 181 | directory string 182 | }{ 183 | { 184 | name: "pass", 185 | wantErr: false, 186 | fileList: testFiles, 187 | directory: testPath, 188 | }, 189 | { 190 | name: "fail", 191 | wantErr: true, 192 | fileList: []string{}, 193 | directory: "foo", 194 | }, 195 | } 196 | for _, tt := range tests { 197 | t.Run(tt.name, func(t *testing.T) { 198 | dir := newMockFinder(testPath) 199 | dir.RootPath = tt.directory 200 | err := dir.listFiles() 201 | if tt.wantErr { 202 | assert.Error(t, err) 203 | } else { 204 | assert.NoError(t, err) 205 | assert.EqualValues(t, tt.fileList, dir.FileList) 206 | } 207 | }) 208 | } 209 | } 210 | 211 | func Test_checkForAPIVersion(t *testing.T) { 212 | tests := []struct { 213 | name string 214 | file string 215 | want []*api.Output 216 | wantErr bool 217 | }{ 218 | { 219 | name: "deployments extensions/v1beta1", 220 | file: deploymentExtensionsV1Yaml, 221 | wantErr: false, 222 | want: deploymentExtensionsV1YamlFile, 223 | }, 224 | { 225 | name: "file dne", 226 | file: "foo", 227 | wantErr: true, 228 | }, 229 | } 230 | for _, tt := range tests { 231 | t.Run(tt.name, func(t *testing.T) { 232 | dir := newMockFinder(testPath) 233 | got, err := dir.CheckForAPIVersion(tt.file) 234 | if tt.wantErr { 235 | assert.Error(t, err) 236 | } else { 237 | assert.NoError(t, err) 238 | patchFilePath(t, got) 239 | assert.EqualValues(t, tt.want, got) 240 | } 241 | }) 242 | } 243 | } 244 | 245 | func TestDir_scanFiles(t *testing.T) { 246 | 247 | tests := []struct { 248 | name string 249 | wantErr bool 250 | fileList []string 251 | want []*api.Output 252 | }{ 253 | { 254 | name: "pass", 255 | wantErr: false, 256 | fileList: []string{deploymentExtensionsV1Yaml}, 257 | want: deploymentExtensionsV1YamlFile, 258 | }, 259 | } 260 | dir := newMockFinder(testPath) 261 | for _, tt := range tests { 262 | t.Run(tt.name, func(t *testing.T) { 263 | dir.FileList = tt.fileList 264 | err := dir.scanFiles() 265 | if tt.wantErr { 266 | assert.Error(t, err) 267 | } else { 268 | assert.NoError(t, err) 269 | assert.EqualValues(t, tt.fileList, dir.FileList) 270 | patchFilePath(t, dir.Instance.Outputs) 271 | assert.EqualValues(t, tt.want, dir.Instance.Outputs) 272 | } 273 | }) 274 | } 275 | } 276 | 277 | func TestDir_FindVersions(t *testing.T) { 278 | tests := []struct { 279 | name string 280 | wantErr bool 281 | path string 282 | want []*api.Output 283 | }{ 284 | { 285 | name: "pass", 286 | wantErr: false, 287 | path: testPath, 288 | want: testOutput, 289 | }, 290 | { 291 | name: "fail", 292 | wantErr: true, 293 | path: "foo", 294 | want: testOutput, 295 | }, 296 | } 297 | 298 | for _, tt := range tests { 299 | t.Run(tt.name, func(t *testing.T) { 300 | dir := newMockFinder(tt.path) 301 | dir.Instance.Outputs = nil 302 | err := dir.FindVersions() 303 | if tt.wantErr { 304 | assert.Error(t, err) 305 | } else { 306 | assert.NoError(t, err) 307 | patchFilePath(t, dir.Instance.Outputs) 308 | assert.EqualValues(t, tt.want, dir.Instance.Outputs) 309 | } 310 | }) 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /pkg/finder/testdata/deployment-extensions-v1beta1.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "extensions/v1beta1", 3 | "kind": "Deployment", 4 | "metadata": { 5 | "labels": { 6 | "app": "utilities" 7 | }, 8 | "name": "utilities", 9 | "namespace": "json-namespace" 10 | }, 11 | "spec": { 12 | "replicas": 1, 13 | "selector": { 14 | "matchLabels": { 15 | "app": "utilities" 16 | } 17 | }, 18 | "template": { 19 | "metadata": { 20 | "labels": { 21 | "app": "utilities" 22 | } 23 | }, 24 | "spec": { 25 | "containers": [ 26 | { 27 | "args": [ 28 | "while true; do sleep 30; done;" 29 | ], 30 | "command": [ 31 | "/bin/bash", 32 | "-c", 33 | "--" 34 | ], 35 | "image": "quay.io/sudermanjr/utilities:latest", 36 | "name": "utilities", 37 | "resources": { 38 | "limits": { 39 | "cpu": "100m", 40 | "memory": "128Mi" 41 | }, 42 | "requests": { 43 | "cpu": "30m", 44 | "memory": "64Mi" 45 | } 46 | }, 47 | "securityContext": { 48 | "allowPrivilegeEscalation": false, 49 | "capabilities": { 50 | "drop": [ 51 | "ALL" 52 | ] 53 | }, 54 | "readOnlyRootFilesystem": true, 55 | "runAsNonRoot": true, 56 | "runAsUser": 10324 57 | } 58 | } 59 | ] 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /pkg/finder/testdata/deployment-extensions-v1beta1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: utilities 5 | namespace: yaml-namespace 6 | labels: 7 | app: utilities 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: utilities 13 | template: 14 | metadata: 15 | labels: 16 | app: utilities 17 | spec: 18 | containers: 19 | - name: utilities 20 | image: quay.io/sudermanjr/utilities:latest 21 | command: [ "/bin/bash", "-c", "--" ] 22 | args: [ "while true; do sleep 30; done;" ] 23 | securityContext: 24 | readOnlyRootFilesystem: true 25 | allowPrivilegeEscalation: false 26 | runAsNonRoot: true 27 | runAsUser: 10324 28 | capabilities: 29 | drop: 30 | - ALL 31 | resources: 32 | requests: 33 | cpu: 30m 34 | memory: 64Mi 35 | limits: 36 | cpu: 100m 37 | memory: 128Mi 38 | -------------------------------------------------------------------------------- /pkg/finder/testdata/other.txt: -------------------------------------------------------------------------------- 1 | This is a dummy file. 2 | There shouldn't be anything to get here. 3 | extensions/v1beta1 4 | -------------------------------------------------------------------------------- /pkg/helm/helm.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 FairwindsOps Inc 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 | // Copyright 2020 Fairwinds 16 | // 17 | // Licensed under the Apache License, Version 2.0 (the "License"); 18 | // you may not use this file except in compliance with the License. 19 | // You may obtain a copy of the License at 20 | // 21 | // http://www.apache.org/licenses/LICENSE-2.0 22 | // 23 | // Unless required by applicable law or agreed to in writing, software 24 | // distributed under the License is distributed on an "AS IS" BASIS, 25 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26 | // See the License for the specific language governing permissions and 27 | // limitations under the License 28 | 29 | package helm 30 | 31 | import ( 32 | "context" 33 | "encoding/json" 34 | "fmt" 35 | 36 | "github.com/thoas/go-funk" 37 | "helm.sh/helm/v3/pkg/release" 38 | "helm.sh/helm/v3/pkg/releaseutil" 39 | helmstoragev3 "helm.sh/helm/v3/pkg/storage" 40 | driverv3 "helm.sh/helm/v3/pkg/storage/driver" 41 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 42 | "k8s.io/client-go/kubernetes" 43 | "k8s.io/klog/v2" 44 | 45 | "github.com/fairwindsops/pluto/v5/pkg/api" 46 | "github.com/fairwindsops/pluto/v5/pkg/kube" 47 | ) 48 | 49 | // Helm represents all current releases that we can find in the cluster 50 | type Helm struct { 51 | Releases []*Release 52 | Kube *kube.Kube 53 | Namespace string 54 | Instance *api.Instance 55 | } 56 | 57 | // Release represents a single helm release 58 | type Release struct { 59 | Name string `json:"name"` 60 | Namespace string `json:"namespace"` 61 | Chart *Chart `json:"chart"` 62 | Manifest string `json:"manifest"` 63 | } 64 | 65 | // Chart represents a single helm chart 66 | type Chart struct { 67 | Metadata *ChartMeta `json:"metadata"` 68 | } 69 | 70 | // ChartMeta is the metadata of a Helm chart 71 | type ChartMeta struct { 72 | Name string `json:"name"` 73 | Version string `json:"version"` 74 | } 75 | 76 | // NewHelm returns a basic helm struct with the version of helm requested 77 | func NewHelm(namespace string, kubeContext string, instance *api.Instance, kubeConfigPath string) (*Helm, error) { 78 | config, err := kube.GetConfigInstance(kubeContext, kubeConfigPath) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | return &Helm{ 84 | Kube: config, 85 | Namespace: namespace, 86 | Instance: instance, 87 | }, nil 88 | } 89 | 90 | // NewHelmWithKubeClient returns a helm struct with version of helm requested 91 | // and uses the passed in kube client as the cluster to operate on 92 | func NewHelmWithKubeClient(version string, store string, namespace string, instance *api.Instance, kubeClient kubernetes.Interface) *Helm { 93 | return &Helm{ 94 | Kube: &kube.Kube{ 95 | Client: kubeClient, 96 | }, 97 | Namespace: namespace, 98 | Instance: instance, 99 | } 100 | } 101 | 102 | // FindVersions is the primary method in the package. 103 | // As of helm 2 being deprecated, this is just a passthrough to getReleasesVersionThree. It has been 104 | // left in place to ensure api backward compatibility. 105 | func (h *Helm) FindVersions() error { 106 | return h.getReleasesVersionThree() 107 | } 108 | 109 | // getReleasesVersionThree retrieves helm 3 releases from Secrets 110 | func (h *Helm) getReleasesVersionThree() error { 111 | hs := driverv3.NewSecrets(h.Kube.Client.CoreV1().Secrets(h.Namespace)) 112 | helmClient := helmstoragev3.Init(hs) 113 | namespaces, err := h.Kube.Client.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{}) 114 | if err != nil { 115 | return err 116 | } 117 | releases, err := helmClient.ListDeployed() 118 | if err != nil { 119 | return err 120 | } 121 | for _, namespace := range namespaces.Items { 122 | ns := namespace.Name 123 | if h.Namespace != "" && ns != h.Namespace { 124 | continue 125 | } 126 | filteredReleases := h.deployedReleasesPerNamespace(ns, releases) 127 | for _, r := range filteredReleases { 128 | rel, err := helmToRelease(r) 129 | if err != nil { 130 | return fmt.Errorf("error converting helm r '%s/%s' to internal object\n %w", r.Namespace, r.Name, err) 131 | } 132 | if funk.Contains(h.Releases, rel) { 133 | klog.Warningf("found duplicate release %s/%s in a deployed state - this may produce inconsistent results", rel.Namespace, rel.Name) 134 | } 135 | h.Releases = append(h.Releases, rel) 136 | } 137 | } 138 | if err := h.findVersions(); err != nil { 139 | return err 140 | } 141 | return nil 142 | } 143 | 144 | func (h *Helm) deployedReleasesPerNamespace(namespace string, releases []*release.Release) []*release.Release { 145 | return releaseutil.All(deployed, relNamespace(namespace)).Filter(releases) 146 | } 147 | 148 | func deployed(rls *release.Release) bool { 149 | return rls.Info.Status == release.StatusDeployed 150 | } 151 | 152 | func relNamespace(ns string) releaseutil.FilterFunc { 153 | return func(rls *release.Release) bool { 154 | return rls.Namespace == ns 155 | } 156 | } 157 | 158 | func (h *Helm) findVersions() error { 159 | for _, r := range h.Releases { 160 | klog.V(2).Infof("parsing r %s", r.Name) 161 | outList, err := h.checkForAPIVersion([]byte(r.Manifest)) 162 | if err != nil { 163 | return fmt.Errorf("error parsing r '%s/%s'\n %w", r.Namespace, r.Name, err) 164 | } 165 | for _, out := range outList { 166 | out.Name = r.Name + "/" + out.Name 167 | out.Namespace = r.Namespace 168 | } 169 | h.Instance.Outputs = append(h.Instance.Outputs, outList...) 170 | 171 | } 172 | return nil 173 | } 174 | 175 | // checkForAPIVersion calls the api pkg to parse our releases for deprecated APIs 176 | func (h *Helm) checkForAPIVersion(manifest []byte) ([]*api.Output, error) { 177 | outputs, err := h.Instance.IsVersioned(manifest) 178 | if err != nil { 179 | return nil, err 180 | } 181 | if len(outputs) < 1 { 182 | return nil, nil 183 | } 184 | return outputs, nil 185 | } 186 | 187 | func helmToRelease(helmRelease interface{}) (*Release, error) { 188 | jsonRel, err := json.Marshal(helmRelease) 189 | if err != nil { 190 | return nil, fmt.Errorf("error marshaling release: %s", err.Error()) 191 | } 192 | return marshalToRelease(jsonRel) 193 | } 194 | 195 | // marshalToRelease marshals release data into the Pluto Release type so we have a common type regardless of helm version 196 | func marshalToRelease(jsonRel []byte) (*Release, error) { 197 | var ret = new(Release) 198 | err := json.Unmarshal(jsonRel, ret) 199 | return ret, err 200 | } 201 | -------------------------------------------------------------------------------- /pkg/kube/kube.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 FairwindsOps Inc 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 | // Copyright 2020 Fairwinds 16 | // 17 | // Licensed under the Apache License, Version 2.0 (the "License"); 18 | // you may not use this file except in compliance with the License. 19 | // You may obtain a copy of the License at 20 | // 21 | // http://www.apache.org/licenses/LICENSE-2.0 22 | // 23 | // Unless required by applicable law or agreed to in writing, software 24 | // distributed under the License is distributed on an "AS IS" BASIS, 25 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26 | // See the License for the specific language governing permissions and 27 | // limitations under the License 28 | 29 | package kube 30 | 31 | import ( 32 | "flag" 33 | "sync" 34 | 35 | "k8s.io/client-go/kubernetes" 36 | "k8s.io/client-go/rest" 37 | "k8s.io/klog/v2" 38 | 39 | // This is required to auth to cloud providers (i.e. GKE) 40 | _ "k8s.io/client-go/plugin/pkg/client/auth" 41 | "sigs.k8s.io/controller-runtime/pkg/client/config" 42 | ) 43 | 44 | type Kube struct { 45 | Client kubernetes.Interface 46 | } 47 | 48 | var kubeClient *Kube 49 | var once sync.Once 50 | 51 | // GetConfigInstance returns a Pluto Kubernetes interface based on the current configuration 52 | func GetConfigInstance(kubeContext string, kubeConfigPath string) (*Kube, error) { 53 | var err error 54 | var client kubernetes.Interface 55 | var kubeConfig *rest.Config 56 | 57 | kubeConfig, err = GetConfig(kubeContext, kubeConfigPath) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | once.Do(func() { 63 | if kubeClient == nil { 64 | client, err = GetKubeClient(kubeConfig) 65 | 66 | kubeClient = &Kube{ 67 | Client: client, 68 | } 69 | } 70 | }) 71 | if err != nil { 72 | return nil, err 73 | } 74 | return kubeClient, nil 75 | } 76 | 77 | // GetConfig returns the current kube config with a specific context 78 | func GetConfig(kubeContext string, kubeConfigPath string) (*rest.Config, error) { 79 | 80 | if kubeContext != "" { 81 | klog.V(3).Infof("using kube context: %s", kubeContext) 82 | } 83 | 84 | fs := flag.NewFlagSet("fs", flag.ContinueOnError) 85 | fs.String("kubeconfig", kubeConfigPath, "") 86 | config.RegisterFlags(fs) 87 | 88 | kubeConfig, err := config.GetConfigWithContext(kubeContext) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | return kubeConfig, nil 94 | } 95 | 96 | // GetKubeClient returns a Kubernetes.Interface based on the current configuration 97 | func GetKubeClient(kubeConfig *rest.Config) (kubernetes.Interface, error) { 98 | clientset, err := kubernetes.NewForConfig(kubeConfig) 99 | if err != nil { 100 | return nil, err 101 | } 102 | return clientset, nil 103 | } 104 | -------------------------------------------------------------------------------- /pkg/kube/kube_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 FairwindsOps Inc 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 kube 16 | 17 | import ( 18 | "os" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | _ "k8s.io/client-go/plugin/pkg/client/auth" 23 | ) 24 | 25 | func Test_getKubeClient(t *testing.T) { 26 | tests := []struct { 27 | name string 28 | kubeContext string 29 | kubeConfig string 30 | kubeConfigPath string 31 | wantErr bool 32 | }{ 33 | { 34 | name: "context does not exist", 35 | kubeContext: "farglebargle", 36 | kubeConfig: "testdata/kubeconfig", 37 | kubeConfigPath: "", 38 | wantErr: true, 39 | }, 40 | { 41 | name: "context exists", 42 | kubeContext: "kind-kind", 43 | kubeConfig: "testdata/kubeconfig", 44 | kubeConfigPath: "", 45 | wantErr: false, 46 | }, 47 | { 48 | name: "invalid kubeconfig", 49 | kubeContext: "kind-kind", 50 | kubeConfig: "testdata/kubeconfig_invalid", 51 | kubeConfigPath: "", 52 | wantErr: true, 53 | }, 54 | { 55 | name: "invalid kubeconfig", 56 | kubeContext: "kind-kind", 57 | kubeConfig: "testdata/kubeconfig_invalid", 58 | kubeConfigPath: "", 59 | wantErr: true, 60 | }, 61 | { 62 | name: "valid kubeconfig path", 63 | kubeContext: "kind-kind", 64 | kubeConfig: "testdata/kubeconfig_invalid", 65 | kubeConfigPath: "testdata/kubeconfig", 66 | wantErr: false, 67 | }, 68 | { 69 | name: "invalid kubeconfig path", 70 | kubeContext: "kind-kind", 71 | kubeConfig: "testdata/kubeconfig", 72 | kubeConfigPath: "testdata/kubeconfig_invalid", 73 | wantErr: true, 74 | }, 75 | } 76 | for _, tt := range tests { 77 | t.Run(tt.name, func(t *testing.T) { 78 | os.Setenv("KUBECONFIG", tt.kubeConfig) 79 | _, err := GetConfig(tt.kubeContext, tt.kubeConfigPath) 80 | if tt.wantErr { 81 | assert.Error(t, err) 82 | } else { 83 | assert.NoError(t, err) 84 | } 85 | }) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /pkg/kube/testdata/kubeconfig: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM1ekNDQWMrZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJeU1ETXdNekU1TURNek5sb1hEVE15TURJeU9URTVNRE16Tmxvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBS0haCjhyVXFhN1JadVNNUlU2eXNNcDJLc1U3cnJHQTdGWmpMVjZzRG1qUG5Xb2U3enZEdGlkQUhwQ2FpM0lxblBMOVgKMTBmV2RFQnU3MjN1YWFYV3VTNVRBQWNkZSt4NU45WmE5S3hTRXV2OG1zeE1wUjl0ZmJsR2ltSURiNSs3YW5PTgo4ZVV2UzBVR05pNENIWUl2aGM3c2wxbTNBaDRIUWVBMzBnL2xORHJOTytkVlYwaVczbkpNNE8yL1JBOW54UlVZCmQxbmxwQ1R1SkZmaThKeXlkZnpwd0x0QnROMW5zNVRLUThsdlJEWStZTER3OGY0aVhMSzB5YVVhUkZmVkhWSzkKRk43WWRCM2tnbXAxU25YYUNFVngrWkZRd0VkR2EzcEZ1ZHJiL01DMDZ6cFhnQzluTCtoRy82U1dCK3VXS2hiQwpmYXVFcnRNeVBVbmJJUUFzMTEwQ0F3RUFBYU5DTUVBd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0hRWURWUjBPQkJZRUZCUVI2WXpWRDJUOVVlK0tacVJyNU1ZSEY4bm1NQTBHQ1NxR1NJYjMKRFFFQkN3VUFBNElCQVFBZ3VkelhtYXpRb1gyWDB0QnY0WG9ieWN6ZmMyUTFSd29PUVY2TW5aNUE5NnJrS0kzawpHNklRS3ptWTFWNDhRRDhPTlZxdGdaRnNrU1ZrK05tSGM1c1Q2eE1YSWtDa2xFcUJLbFh4NHk5WDNHdkRjbERqClFtc044V0lHTThhbmFNRmtkajRqcDhZZEZWWktxcEpuaXljOVpIL1ozRjlITnFyMWlaL2VwbkZXczRMcnl4bkcKOWZjNlBjVnEvQVROdUhxalpGNjRFTkxxeHUwVUJvTFlCMFFuU1ZlalM3RnpRMzV4RHI5dGRpVnFKUzhhcmJRcgo0RHpuaU1rendzNUlQcGlvelNVSUlTeVVSc0pra2R0bW5OU3pwcWc3Y09Xc3NlYktXUU8rbURMSHcyMVVxb0JrCkZZbUhDVnNTRHlOaXdGYWtadUhZRGN5dDdjbDMwY1cvYjhmcAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== 5 | server: https://127.0.0.1:54753 6 | name: kind-kind 7 | contexts: 8 | - context: 9 | cluster: kind-kind 10 | user: kind-kind 11 | name: kind-kind 12 | current-context: kind-kind 13 | kind: Config 14 | preferences: {} 15 | users: 16 | - name: kind-kind 17 | user: 18 | client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJVENDQWdtZ0F3SUJBZ0lJU2pHek1STjQ4aTR3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TWpBek1ETXhPVEF6TXpaYUZ3MHlNekF6TURNeE9UQXpNemxhTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQTBaVXlOUXdRYk1hbFppRkUKUCthZThxY091TFF3QXUzU3FVNGRkcjNWL0hHeVJRWk4vZXRCTDRBaGFnK0E1ZHl3L3g5bnFJK1pTZStjNXVQUwpHUHNqaE96SFdnYi9XNHdvcXZ4cXdLQXd2cEFrZW9CeTEwYWxWaEVOcHVTbWk5cDRxalVpNm84ZmdSUDJCK1BlCkI5QmFLSDBQcUtMZDc4Q1hJci9rcVhkY0dqWHNZMjZNU1FSYVF0VFExY0xaR0VOakRNbnBxNnppb256NHoxZXEKTXlZRnB6eHJDZ0dqVTZvVU45cTJBRFpQZ1pyREJUdzFudTB6ZEJTRHZhVkQrV2pqN3VPMUI1WCt0Qml3NjJnZApPUEZnOE9LcThZUitSNUZpSzQrNE5jSjgvVzVkVWg3ZlZQRHVZL2VSMDg2M1NMdUlQS0srcDZaMW9mSEx5YnZWCm1jMm50d0lEQVFBQm8xWXdWREFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RBWURWUjBUQVFIL0JBSXdBREFmQmdOVkhTTUVHREFXZ0JRVUVlbU0xUTlrL1ZIdmltYWthK1RHQnhmSgo1akFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBRTdNL21iSDh5UzIrelRCYTMycCtxMXl2TUFGbC9tbHVVaVZGCjJjVks5SWRDbHpYTGZMWTJWdVphbGdWOEdJdmYxTi9IK1dpQW5DYUdKeUN0R0c0VTVQQWJoVlRaNHFkVGRDcG8KU1R6Y0JXczJJeXRMSU1OclMzU2cwQXVLZ2ovZzZRVGxSaXN6SHZvazkrQURzenQvem1LeFZlbEM0aGFnYXVRVwpOaktZenFFWEN1SmFnUkQxY1lhYksxQlgyQ2cxYytpY0F3ZTdBazFKSmcxQ2JFVTNZSUZ2Yks1THJvK0hDSjArCk1RdUZ1T1VpQm1ScFlwY1ZUMkpCbHVmdjYvNENvcmZtdUdCeXUyN3E1ZmNUVkFGSmlKaGgxYWRBTGY5cURpY1kKUFpoYXNNTysvcTlpSkhRUk1KWklvNVNLWnBUMzg3WEE4SUI3THMvUjg2QVVTL3BVUWc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== 19 | client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcGdJQkFBS0NBUUVBMFpVeU5Rd1FiTWFsWmlGRVArYWU4cWNPdUxRd0F1M1NxVTRkZHIzVi9IR3lSUVpOCi9ldEJMNEFoYWcrQTVkeXcveDlucUkrWlNlK2M1dVBTR1BzamhPekhXZ2IvVzR3b3F2eHF3S0F3dnBBa2VvQnkKMTBhbFZoRU5wdVNtaTlwNHFqVWk2bzhmZ1JQMkIrUGVCOUJhS0gwUHFLTGQ3OENYSXIva3FYZGNHalhzWTI2TQpTUVJhUXRUUTFjTFpHRU5qRE1ucHE2emlvbno0ejFlcU15WUZwenhyQ2dHalU2b1VOOXEyQURaUGdackRCVHcxCm51MHpkQlNEdmFWRCtXamo3dU8xQjVYK3RCaXc2MmdkT1BGZzhPS3E4WVIrUjVGaUs0KzROY0o4L1c1ZFVoN2YKVlBEdVkvZVIwODYzU0x1SVBLSytwNloxb2ZITHlidlZtYzJudHdJREFRQUJBb0lCQVFDeHR5Vi92ZDhmbmNJYQp2QkszYk1OVFZ6MHdlZnBNUVlYa2NveWhaNC9RSkVqYUp1SnpjYzB3amlISFlhWVhRL0FDSllzc2I3ZTM3aXJKCnpsMFZPYitBczViSFRDKzBYVUVPM0VOWlczeDUycGhVR0I5SlZHcFdkTmR5c1doWUpzVXk3eVBRYWRSNUM0Z1EKWUxTaFE1ZHZ4YnhGYnZWcW01Q01zdE1lc0V1MUhJT0VmMUNEVHMzVTJON2VrVVJFUXdXcXlyVmUyWFN2R3grZAp4SHpiUzFMRzZjcGdpT1BCVXBxUitpcDZBZFlkd0FoWW9kQ1ZXUWVtQWw5TDhNMW5TNUVRME0xcUNzR0VidUpCClZ1cGszaWlmeUViL2VqZ1kvUkMrY01aelZkNEFVTVlsenJGaFNvYk9RQk1NMHFRbFkyVHQ2bGR1OHR4YzNMRDYKTG9oL2pWUmhBb0dCQU8ydW9tbmMvYk81bGRpSTBJZUt6bzQvcEUvYWlJUlRxUjRuTjVOUVdkLzkya3NSenNtNApRL0tiMjJhNVlwRU93TWphZVFpdjJ3UGdrUzRJYTBVaVJyZlA3VjdEMzRwVG5wTytBVGtqV1B3TkJNMjNzc01pClpNZnJlWUMzSjlnQUhDQ0hjZW5VSXRGSW9zeUtncXhxVExCbEFqTTYvNHVydHNlc3ovUFFBMGxkQW9HQkFPRzgKTEdQY2FhSDN5dlVycjU2dDFjaHBEVm5MeEZUVmhzZ2lTdmxzUWpjQ1dCT1RnRDE4dFRmd2phY1VENEFyTjdRUgp4QndUMHhhNUppNitXT1ArRUhaNFpXYmovR3dXc2YxQzlLRVJhUEJhQ0MxWWViV2FPNU5Ka2NmMVNibzA5T1ZLCll0bm1kdjRYWCtUd1laUWd5Z0hMSXlWUkppY0NvSEliTVFyNit5QWpBb0dCQUw4LzdCUCs3RzIzZ1dtUS9TcHoKZUsxaGJGZU00cGlIc09kQWF5bXdXQUV5aEhvdlJCTnduOHdSdVVNNUhLVFlHQ3dHRDZuN3h1R2h5M0FjK2lQUQo1dEoxRHUxWDh3R2RNMVp5WncyUThjTXV2cFRCb2FmK2Z0V1hGbGVLQ1JkM1R5cWxrZld5NnFrNEp6Y3FwT2NUClNjNE9HTzBjSmhBL0JYNDZBY0tQdUhWSkFvR0JBS2IzZzdIOWgxaVpLMUw3RkRSL2lpQnBxOGxla2dMWnlZN2cKNXFuazdIazV6Nkh6T1NqQnhGenpIaU9XRC8wU2VtcER0ZFc2eUNrSG0vbDVLOUMvekxlRVNDUzV2NnpIZ0xHKwp3NmtiY281TldiMElzMFdqSml2RWdBTHlLSzJGbkJxNVViS3c2QlQ3ZngrY3VlQlIvSGtsSXdMb0toc3lzekNUCmNrd1g1b1lYQW9HQkFMVHhmQ2cwaGY4bGJwUmlNeVFQc2NBMlJUS003T3pmZCtFZ1NFOHNxWHU0akxiQ3FJR2kKSC95TlhIRHZ6Y2tkZ1BkRW1BOVRqWit4SXVnK2JDMnVidGNCeHpMS1VPSko1MVplNEZNNU81eFY0MWpiTDdIcgo5QUdYdFA2UlEwU0hTeEQvZjBtQkMwdGdQZHcrajN4SHd4R3FKTTZkSWp2U0EwUmJFNG9HU0FWNAotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= 20 | -------------------------------------------------------------------------------- /pkg/kube/testdata/kubeconfig_invalid: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM1ekNDQWMrZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJeU1ETXdNekU1TURNek5sb1hEVE15TURJeU9URTVNRE16Tmxvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBS0haCjhyVXFhN1JadVNNUlU2eXNNcDJLc1U3cnJHQTdGWmpMVjZzRG1qUG5Xb2U3enZEdGlkQUhwQ2FpM0lxblBMOVgKMTBmV2RFQnU3MjN1YWFYV3VTNVRBQWNkZSt4NU45WmE5S3hTRXV2OG1zeE1wUjl0ZmJsR2ltSURiNSs3YW5PTgo4ZVV2UzBVR05pNENIWUl2aGM3c2wxbTNBaDRIUWVBMzBnL2xORHJOTytkVlYwaVczbkpNNE8yL1JBOW54UlVZCmQxbmxwQ1R1SkZmaThKeXlkZnpwd0x0QnROMW5zNVRLUThsdlJEWStZTER3OGY0aVhMSzB5YVVhUkZmVkhWSzkKRk43WWRCM2tnbXAxU25YYUNFVngrWkZRd0VkR2EzcEZ1ZHJiL01DMDZ6cFhnQzluTCtoRy82U1dCK3VXS2hiQwpmYXVFcnRNeVBVbmJJUUFzMTEwQ0F3RUFBYU5DTUVBd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0hRWURWUjBPQkJZRUZCUVI2WXpWRDJUOVVlK0tacVJyNU1ZSEY4bm1NQTBHQ1NxR1NJYjMKRFFFQkN3VUFBNElCQVFBZ3VkelhtYXpRb1gyWDB0QnY0WG9ieWN6ZmMyUTFSd29PUVY2TW5aNUE5NnJrS0kzawpHNklRS3ptWTFWNDhRRDhPTlZxdGdaRnNrU1ZrK05tSGM1c1Q2eE1YSWtDa2xFcUJLbFh4NHk5WDNHdkRjbERqClFtc044V0lHTThhbmFNRmtkajRqcDhZZEZWWktxcEpuaXljOVpIL1ozRjlITnFyMWlaL2VwbkZXczRMcnl4bkcKOWZjNlBjVnEvQVROdUhxalpGNjRFTkxxeHUwVUJvTFlCMFFuU1ZlalM3RnpRMzV4RHI5dGRpVnFKUzhhcmJRcgo0RHpuaU1rendzNUlQcGlvelNVSUlTeVVSc0pra2R0bW5OU3pwcWc3Y09Xc3NlYktXUU8rbURMSHcyMVVxb0JrCkZZbUhDVnNTRHlOaXdGYWtadUhZRGN5dDdjbDMwY1cvYjhmcAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== 5 | server: https://127.0.0.1:54753 6 | name: kind-kind 7 | contexts: 8 | - context: 9 | cluster: kind-kind 10 | user: kind-kind 11 | name: kind-kind 12 | current-context: kind-kind 13 | kind: Config 14 | preferences: {} 15 | users: 16 | - name: kind-kind 17 | user: 18 | client-certificate-data: fargle 19 | client-key-data: bargle 20 | -------------------------------------------------------------------------------- /release-orb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function version_gt() { 4 | test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"; 5 | } 6 | 7 | echo "Starting release." 8 | 9 | command -v circleci >/dev/null 2>&1 || { echo >&2 "I require circleci but it's not installed. Aborting."; exit 1; } 10 | echo "Found circleci command." 11 | 12 | cli_version=$(circleci version | cut -d+ -f1) 13 | required_version=0.1.5705 14 | 15 | if version_gt "$required_version" "$cli_version"; then 16 | echo "This script requires circleci version greater than or equal to 0.1.5705!" 17 | exit 1 18 | fi 19 | 20 | commit=$(git log -n1 --pretty='%h') 21 | tag=$(git describe --exact-match --tags "$commit") 22 | 23 | retVal=$? 24 | echo "retVal = $retVal" 25 | if [ $retVal -ne 0 ]; then 26 | echo "You need to checkout a valid tag for this to work." 27 | exit $retVal 28 | fi 29 | 30 | echo "Release: $commit - $tag" 31 | 32 | echo "Validating..." 33 | make orb-validate || { echo 'Orb failed to validate.' ; exit 1; } 34 | 35 | echo "Releasing..." 36 | circleci orb publish orb.yml "fairwinds/pluto@${tag:1}" 37 | --------------------------------------------------------------------------------