├── .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 |
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 |
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 |
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 |
2 |
3 |
4 |
13 |
14 |
15 |
16 |
17 |
28 |
29 |
46 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | meta:
3 | - name: description
4 | content: "Fairwinds Pluto | Documentation"
5 | ---
6 |
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 |
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 |
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 |
--------------------------------------------------------------------------------