Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Factory
chart-testing
chart-testing-3.11.0.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File chart-testing-3.11.0.obscpio of Package chart-testing
07070100000000000081A40000000000000000000000016624BE4500000021000000000000000000000000000000000000002300000000chart-testing-3.11.0/.dockerignore.circleci .history .idea .vscode 07070100000001000081A40000000000000000000000016624BE4500000111000000000000000000000000000000000000002300000000chart-testing-3.11.0/.editorconfig# EditorConfig is awesome: http://EditorConfig.org root = true [*] indent_style = space indent_size = 4 trim_trailing_whitespace = true insert_final_newline = true charset = utf-8 [*.{yml, yaml}] indent_size = 2 [*.go] indent_style = tab [Makefile] indent_style = tab 07070100000002000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000001D00000000chart-testing-3.11.0/.github07070100000003000081A40000000000000000000000016624BE45000004B8000000000000000000000000000000000000002F00000000chart-testing-3.11.0/.github/ISSUE_TEMPLATE.md<!-- Thanks for filing an issue! Before hitting the button, please answer these questions. It's helpful to search the existing GitHub issues first. It's likely that another user has already reported the issue you're facing, or it's a known issue that we're already aware of--> **Is this a request for help?**: --- **Is this a BUG REPORT or FEATURE REQUEST?** (choose one): <!-- If this is a BUG REPORT, please: - Fill in as much of the template below as you can. If you leave out information, we can't help you as well. If this is a FEATURE REQUEST, please: - Describe *in detail* the feature/behavior/change you'd like to see. In both cases, be ready for followup questions, and please respond in a timely manner. If we can't reproduce a bug or think a feature already exists, we might close your issue. If we're wrong, PLEASE feel free to reopen it and explain why. --> **Version of Helm and Kubernetes**: <!-- To get current helm, kubernetes and ct versions: helm version kubectl version ct version and paste output here. --> **What happened**: **What you expected to happen**: **How to reproduce it** (as minimally and precisely as possible): **Anything else we need to know**: 07070100000004000081A40000000000000000000000016624BE450000031D000000000000000000000000000000000000003600000000chart-testing-3.11.0/.github/PULL_REQUEST_TEMPLATE.md<!-- Thank you for contributing to helm/chart-testing. When updates to your PR are requested, please add new commits and do not squash the history. This will make it easier to identify new changes. The PR will be squashed anyways when it is merged. Thanks. Please make sure you test your changes before you push them. Once pushed, a CircleCI will run across your changes and do some initial checks and linting. These checks run very quickly. Please check the results. We would like these checks to pass before we even continue reviewing your changes. --> **What this PR does / why we need it**: **Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes # **Special notes for your reviewer**: 07070100000005000081A40000000000000000000000016624BE450000025D000000000000000000000000000000000000002C00000000chart-testing-3.11.0/.github/dependabot.yml--- version: 2 updates: - package-ecosystem: gomod directory: "/" schedule: interval: weekly open-pull-requests-limit: 10 groups: gomod: update-types: - "patch" - package-ecosystem: "github-actions" directory: "/" schedule: interval: weekly open-pull-requests-limit: 10 groups: actions: update-types: - "patch" - "minor" - package-ecosystem: "docker" directory: "/" schedule: interval: weekly groups: docker: update-types: - "patch" - "minor" 07070100000006000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000002700000000chart-testing-3.11.0/.github/workflows07070100000007000081A40000000000000000000000016624BE4500000D07000000000000000000000000000000000000002F00000000chart-testing-3.11.0/.github/workflows/ci.yamlname: ci on: push: branches: - 'main' pull_request: branches: - '*' jobs: build: runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 with: fetch-depth: 0 - name: shellcheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 - name: Setup go uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version-file: './go.mod' check-latest: true - uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 # v4.2.0 with: version: v3.14.4 - name: Install GoReleaser uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0 with: install-only: true - name: Install cosign uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0 - name: Install syft uses: anchore/sbom-action/download-syft@ab5d7b5f48981941c4c5d6bf33aeb98fe3bae38c # v0.15.10 - name: Install k8s Kind uses: helm/kind-action@99576bfa6ddf9a8e612d83b513da5a75875caced # v1.3.0 with: install_only: true - name: Install tools run: | curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl chmod +x ./kubectl sudo mv ./kubectl /usr/local/bin/kubectl ./setup.sh - name: Test run: | ./e2e-kind.sh - name: Lint run: | go vet -v ./... goimports -w -l . go mod tidy git diff --exit-code - name: Set up QEMU uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - name: Build run: | set -o nounset set -o pipefail echo "Building snapshot..." ./build.sh check-docs: name: check-docs runs-on: ubuntu-latest steps: - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version-file: './go.mod' check-latest: true - name: generate docs run: | go build -o ct-bin ./ct/main.go ./ct-bin doc-gen git_status="$(git status --porcelain)" if [[ ${git_status} ]]; then echo -e 'Documentation is outdated. Please update the docs\n' echo "${git_status}" exit 1 fi golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version-file: './go.mod' check-latest: true - name: golangci-lint uses: golangci/golangci-lint-action@3cfe3a4abbb849e10058ce4af15d205b6da42804 # v3 with: version: v1.57 07070100000008000081A40000000000000000000000016624BE450000094D000000000000000000000000000000000000003400000000chart-testing-3.11.0/.github/workflows/release.yamlname: release on: workflow_dispatch: inputs: version: description: Version required: true jobs: build: runs-on: ubuntu-latest permissions: id-token: write contents: write steps: - name: checkout uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 with: fetch-depth: 0 - name: shellcheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 - name: Setup go uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version-file: './go.mod' check-latest: true - name: Install GoReleaser uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0 with: install-only: true - name: Install cosign uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0 - name: Install syft uses: anchore/sbom-action/download-syft@ab5d7b5f48981941c4c5d6bf33aeb98fe3bae38c # v0.15.10 - name: Install tools run: | ./setup.sh - name: Lint run: | go vet -v ./... goimports -w -l . go mod tidy git diff --exit-code - name: Set up QEMU uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - name: Login to registry uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 with: registry: quay.io username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Tag run: | git config user.name "$GITHUB_ACTOR" git config user.email "$GITHUB_ACTOR@users.noreply.github.com" tag='${{ github.event.inputs.version }}' git tag --annotate --message "Tag for release $tag" "$tag" git push origin "refs/tags/$tag" - name: Build env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -o nounset set -o pipefail echo "Building release ${{ github.event.inputs.version }}" ./build.sh --release 07070100000009000081A40000000000000000000000016624BE45000003D6000000000000000000000000000000000000003200000000chart-testing-3.11.0/.github/workflows/stale.yamlname: 'Close stale issues and PRs' on: schedule: - cron: '30 1 * * *' jobs: stale: runs-on: ubuntu-latest permissions: issues: write pull-requests: write steps: - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0 with: stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.' stale-pr-message: 'This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.' close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.' close-pr-message: 'This PR was closed because it has been stalled for 10 days with no activity.' days-before-issue-stale: 30 days-before-pr-stale: 45 days-before-issue-close: 5 days-before-pr-close: 5 0707010000000A000081A40000000000000000000000016624BE4500000040000000000000000000000000000000000000002000000000chart-testing-3.11.0/.gitignore.idea .project .settings .vscode /dist /config.* vendor /ct-bin 0707010000000B000081A40000000000000000000000016624BE45000001E1000000000000000000000000000000000000002300000000chart-testing-3.11.0/.golangci.yml--- linters: enable: - asciicheck - errcheck - errorlint - gofmt - goimports - gosec - gocritic - importas - prealloc - revive - misspell - stylecheck - unconvert - unused - whitespace output: uniq-by-line: false issues: exclude-rules: - path: _test\.go linters: - errcheck - gosec - revive max-issues-per-linter: 0 max-same-issues: 0 run: issues-exit-code: 1 timeout: 10m 0707010000000C000081A40000000000000000000000016624BE4500000DDA000000000000000000000000000000000000002500000000chart-testing-3.11.0/.goreleaser.ymlproject_name: chart-testing env: - COSIGN_YES=true before: hooks: - go mod download sboms: - artifacts: archive builds: - main: ct/main.go binary: ct env: - CGO_ENABLED=0 goarch: - amd64 - arm64 - arm goos: - linux - darwin - windows flags: - -trimpath mod_timestamp: '{{ .CommitTimestamp }}' ldflags: - >- -X github.com/helm/chart-testing/v3/ct/cmd.Version={{ .Tag }} -X github.com/helm/chart-testing/v3/ct/cmd.GitCommit={{ .Commit }} -X github.com/helm/chart-testing/v3/ct/cmd.BuildDate={{ .Date }} archives: - format_overrides: - goos: windows format: zip files: - LICENSE - README.md - etc/chart_schema.yaml - etc/lintconf.yaml checksum: name_template: 'checksums.txt' snapshot: name_template: "{{ .Tag }}-next" dockers: - skip_push: false use: buildx dockerfile: Dockerfile image_templates: - quay.io/helmpack/chart-testing:{{ .Tag }}-amd64 - quay.io/helmpack/chart-testing:latest-amd64 build_flag_templates: - --platform=linux/amd64 - --label=org.opencontainers.image.version={{ .Version }} - --label=org.opencontainers.image.revision={{ .Commit }} - --label=org.opencontainers.image.title={{ .ProjectName }} - --label=org.opencontainers.image.created={{ .Date }} - --label=org.opencontainers.image.description=ct - The chart testing tool - --label=org.opencontainers.image.vendor=Helm - --label=org.opencontainers.image.licenses=Apache-2.0 - --label=org.opencontainers.image.source=https://github.com/helm/chart-testing - --label=org.opencontainers.image.authors=The Helm Authors extra_files: - etc/chart_schema.yaml - etc/lintconf.yaml - skip_push: false goarch: arm64 use: buildx dockerfile: Dockerfile image_templates: - quay.io/helmpack/chart-testing:{{ .Tag }}-arm64 - quay.io/helmpack/chart-testing:latest-arm64 build_flag_templates: - --platform=linux/arm64 - --label=org.opencontainers.image.version={{ .Version }} - --label=org.opencontainers.image.revision={{ .Commit }} - --label=org.opencontainers.image.title={{ .ProjectName }} - --label=org.opencontainers.image.created={{ .Date }} - --label=org.opencontainers.image.description=ct - The chart testing tool - --label=org.opencontainers.image.vendor=Helm - --label=org.opencontainers.image.licenses=Apache-2.0 - --label=org.opencontainers.image.source=https://github.com/helm/chart-testing - --label=org.opencontainers.image.authors=The Helm Authors extra_files: - etc/chart_schema.yaml - etc/lintconf.yaml docker_manifests: - name_template: quay.io/helmpack/chart-testing:latest image_templates: - quay.io/helmpack/chart-testing:latest-amd64 - quay.io/helmpack/chart-testing:latest-arm64 - name_template: quay.io/helmpack/chart-testing:{{ .Tag }} image_templates: - quay.io/helmpack/chart-testing:{{ .Tag }}-amd64 - quay.io/helmpack/chart-testing:{{ .Tag }}-arm64 signs: - id: all signature: "${artifact}.sig" certificate: "${artifact}.pem" cmd: cosign args: ["sign-blob", "--output-signature", "${artifact}.sig", "--output-certificate", "${artifact}.pem", "${artifact}"] artifacts: all docker_signs: - id: images cmd: cosign args: ["sign", "${artifact}"] artifacts: manifests changelog: use: github-native 0707010000000D000081A40000000000000000000000016624BE45000005D4000000000000000000000000000000000000002000000000chart-testing-3.11.0/DockerfileFROM alpine:3.19 RUN apk --no-cache add \ bash \ curl>7.77.0-r0 \ git \ libc6-compat \ openssh-client \ py3-pip \ py3-wheel \ python3 \ yamllint=1.33.0-r0 # Install Yamale YAML schema validator ARG yamale_version=4.0.4 LABEL yamale-version=$yamale_version RUN pip install --break-system-packages "yamale==$yamale_version" ARG TARGETPLATFORM # Install kubectl ARG kubectl_version=v1.30.0 LABEL kubectl-version=$kubectl_version RUN curl -LO "https://storage.googleapis.com/kubernetes-release/release/$kubectl_version/bin/$TARGETPLATFORM/kubectl" && \ chmod +x kubectl && \ mv kubectl /usr/local/bin/ # Install Helm ARG helm_version=v3.14.4 LABEL helm-version=$helm_version RUN targetArch=$(echo $TARGETPLATFORM | cut -f2 -d '/') \ && if [ ${targetArch} = "amd64" ]; then \ HELM_ARCH="linux-amd64"; \ elif [ ${targetArch} = "arm64" ]; then \ HELM_ARCH="linux-arm64"; \ fi \ && curl -LO "https://get.helm.sh/helm-$helm_version-$HELM_ARCH.tar.gz" \ && mkdir -p "/usr/local/helm-$helm_version" \ && tar -xzf "helm-$helm_version-$HELM_ARCH.tar.gz" -C "/usr/local/helm-$helm_version" \ && ln -s "/usr/local/helm-$helm_version/$HELM_ARCH/helm" /usr/local/bin/helm \ && rm -f "helm-$helm_version-$HELM_ARCH.tar.gz" COPY ./etc/chart_schema.yaml /etc/ct/chart_schema.yaml COPY ./etc/lintconf.yaml /etc/ct/lintconf.yaml COPY ct /usr/local/bin/ct # Ensure that the binary is available on path and is executable RUN ct --help 0707010000000E000081A40000000000000000000000016624BE4500002C5E000000000000000000000000000000000000001D00000000chart-testing-3.11.0/LICENSE Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 0707010000000F000081A40000000000000000000000016624BE4500001AB3000000000000000000000000000000000000001F00000000chart-testing-3.11.0/README.md# Chart Testing [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Go Report Card](https://goreportcard.com/badge/github.com/helm/chart-testing)](https://goreportcard.com/report/github.com/helm/chart-testing) ![ci](https://github.com/helm/chart-testing/workflows/ci/badge.svg) `ct` is the the tool for testing Helm charts. It is meant to be used for linting and testing pull requests. It automatically detects charts changed against the target branch. ## Installation ### Prerequisites It is recommended to use the provided Docker image which can be [found on Quay](https://quay.io/helmpack/chart-testing/). It comes with all necessary tools installed. * [Helm](http://helm.sh) * [Git](https://git-scm.com) (2.17.0 or later) * [Yamllint](https://github.com/adrienverge/yamllint) * [Yamale](https://github.com/23andMe/Yamale) * [Kubectl](https://kubernetes.io/docs/reference/kubectl/overview/) ### Binary Distribution Download the release distribution for your OS from the Releases page: https://github.com/helm/chart-testing/releases Unpack the `ct` binary, add it to your PATH, and you are good to go! ### Docker Image A Docker image is available at `quay.io/helmpack/chart-testing` with list of available tags [here](https://quay.io/repository/helmpack/chart-testing?tab=tags). ### Homebrew ```console $ brew install chart-testing ``` ## Usage See documentation for individual commands: * [ct](doc/ct.md) * [ct install](doc/ct_install.md) * [ct lint](doc/ct_lint.md) * [ct lint-and-install](doc/ct_lint-and-install.md) * [ct list-changed](doc/ct_list-changed.md) * [ct version](doc/ct_version.md) For a more extensive how-to guide, please see: * [charts-repo-actions-demo](https://github.com/helm/charts-repo-actions-demo) ## Configuration `ct` is a command-line application. All command-line flags can also be set via environment variables or config file. Environment variables must be prefixed with `CT_`. Underscores must be used instead of hyphens. CLI flags, environment variables, and a config file can be mixed. The following order of precedence applies: 1. CLI flags 1. Environment variables 1. Config file Note that linting requires config file for [yamllint](https://github.com/adrienverge/yamllint) and [yamale](https://github.com/23andMe/Yamale). If not specified, these files are search in the current directory, the `.ct` directory in current directory, `$HOME/.ct`, and `/etc/ct`, in that order. Samples are provided in the [etc](etc) folder. ### Examples The following example show various way of configuring the same thing: #### CLI #### Remote repo With remote repo: ct install --remote upstream --chart-dirs stable,incubator --build-id pr-42 #### Local repo If you have a chart in current directory and ct installed on the host then you can run: ct install --chart-dirs . --charts . With docker it works with: docker run -it --network host --workdir=/data --volume ~/.kube/config:/root/.kube/config:ro --volume $(pwd):/data quay.io/helmpack/chart-testing:v3.7.1 ct install --chart-dirs . --charts . Notice that `workdir` param is important and must be the same as volume mounted. #### Environment Variables export CT_REMOTE=upstream export CT_CHART_DIRS=stable,incubator export CT_BUILD_ID ct install #### Config File `config.yaml`: ```yaml remote: upstream chart-dirs: - stable - incubator build-id: pr-42 ``` #### Config Usage ct install --config config.yaml `ct` supports any format [Viper](https://github.com/spf13/viper) can read, i. e. JSON, TOML, YAML, HCL, and Java properties files. Notice that if no config file is specified, then `ct.yaml` (or any of the supported formats) is loaded from the current directory, `$HOME/.ct`, or `/etc/ct`, in that order, if found. #### Using private chart repositories When adding chart-repos you can specify additional arguments for the `helm repo add` command using `helm-repo-extra-args` on a per-repo basis. You can also specify OCI registries which will be added using the `helm registry login` command, they also support the `helm-repo-extra-args` for authentication. This could for example be used to authenticate a private chart repository. `config.yaml`: ```yaml chart-repos: - incubator=https://incubator.io - basic-auth=https://private.com - ssl-repo=https://self-signed.ca - oci-registry=oci://nice-oci-registry.pt helm-repo-extra-args: - ssl-repo=--ca-file ./my-ca.crt ``` ct install --config config.yaml --helm-repo-extra-args "basic-auth=--username user --password secret" ## Building from Source `ct` is built using Go 1.13 or higher. `build.sh` is used to build and release the tool. It uses [Goreleaser](https://goreleaser.com/) under the covers. Note: on MacOS you will need `GNU Coreutils readlink`. You can install it with: ```console brew install coreutils ``` Then add `gnubin` to your `$PATH`, with: ```console echo 'export PATH="$(brew --prefix coreutils)/libexec/gnubin:$PATH"' >> ~/.bash_profile bash --login ``` To use the build script: ```console $ ./build.sh -h Usage: build.sh <options> Build ct using Goreleaser. -h, --help Display help -d, --debug Display verbose output and run Goreleaser with --debug -r, --release Create a release using Goreleaser. This includes the creation of a GitHub release and building and pushing the Docker image. If this flag is not specified, Goreleaser is run with --snapshot ``` ## Releasing ### Prepare Release Before a release is created, versions have to be updated in the examples. A pull request needs to be created for this, which should be merged right before the release is cut. Here's a previous one for reference: https://github.com/helm/chart-testing/pull/89 ### Create Release The release workflow is [dispatched from github actions](https://github.com/helm/chart-testing/actions) Versions must start with a lower-case `v`, e. g. `v3.7.1`. ## Supported versions The previous MAJOR version will be supported for three months after each new MAJOR release. Within this support window, pull requests for the previous MAJOR version should be made against the previous release branch. For example, if the current MAJOR version is `v2`, the pull request base branch should be `release-v1`. ## Upgrading When upgrading from `< v2.0.0` you will also need to change the usage in your scripts. This is because, while the [v2.0.0](https://github.com/helm/chart-testing/releases/tag/v2.0.0) release has parity with `v1`, it was refactored from a bash library to Go so there are minor syntax differences. Compare [v1 usage](https://github.com/helm/chart-testing/tree/release-v1#usage) with this (`v2`) version's README [usage](#usage) section above. 07070100000010000081ED0000000000000000000000016624BE45000007DC000000000000000000000000000000000000001E00000000chart-testing-3.11.0/build.sh#!/usr/bin/env bash # Copyright The Helm Authors. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit set -o nounset set -o pipefail SCRIPT_DIR=$(dirname -- "$(readlink -e "${BASH_SOURCE[0]}" || realpath "${BASH_SOURCE[0]}")") readonly SCRIPT_DIR show_help() { cat << EOF Usage: $(basename "$0") <options> Build ct using Goreleaser. -h, --help Display help -d, --debug Display verbose output and run Goreleaser with --debug -r, --release Create a release using Goreleaser. This includes the creation of a GitHub release and building and pushing the Docker image. If this flag is not specified, Goreleaser is run with --snapshot EOF } main() { local debug= local release= while :; do case "${1:-}" in -h | --help) show_help exit ;; -d | --debug) debug=true ;; -r | --release) release=true ;; *) break ;; esac shift done local goreleaser_args=(--clean) if [[ -n "$debug" ]]; then goreleaser_args+=(--debug) set -x fi if [[ -z "$release" ]]; then goreleaser_args+=(--snapshot --skip=sign) fi pushd "$SCRIPT_DIR" > /dev/null go test -race ./... goreleaser "${goreleaser_args[@]}" popd > /dev/null } main "$@" 07070100000011000081A40000000000000000000000016624BE4500000087000000000000000000000000000000000000002800000000chart-testing-3.11.0/code-of-conduct.md# Community Code of Conduct Helm follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). 07070100000012000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000001800000000chart-testing-3.11.0/ct07070100000013000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000001C00000000chart-testing-3.11.0/ct/cmd07070100000014000081A40000000000000000000000016624BE45000004A3000000000000000000000000000000000000002600000000chart-testing-3.11.0/ct/cmd/docGen.go// Copyright The Helm Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "fmt" "github.com/MakeNowJust/heredoc" "github.com/spf13/cobra" "github.com/spf13/cobra/doc" ) func newGenerateDocsCmd() *cobra.Command { return &cobra.Command{ Use: "doc-gen", Short: "Generate documentation", Long: heredoc.Doc(` Generate documentation for all commands to the 'docs' directory.`), Hidden: true, RunE: generateDocs, } } func generateDocs(_ *cobra.Command, _ []string) error { fmt.Println("Generating docs...") err := doc.GenMarkdownTree(NewRootCmd(), "doc") if err != nil { return err } fmt.Println("Done.") return nil } 07070100000015000081A40000000000000000000000016624BE4500000FE2000000000000000000000000000000000000002700000000chart-testing-3.11.0/ct/cmd/install.go// Copyright The Helm Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "fmt" "github.com/MakeNowJust/heredoc" "github.com/helm/chart-testing/v3/pkg/chart" "github.com/helm/chart-testing/v3/pkg/config" "github.com/spf13/cobra" flag "github.com/spf13/pflag" ) func newInstallCmd() *cobra.Command { cmd := &cobra.Command{ Use: "install", Short: "Install and test a chart", Long: heredoc.Doc(` Run 'helm install', 'helm test', and optionally 'helm upgrade' on * changed charts (default) * specific charts (--charts) * all charts (--all) in given chart directories. If upgrade (--upgrade) is true, then this command will validate that 'helm test' passes for the following upgrade paths: * previous chart revision => current chart version (if non-breaking SemVer change) * current chart version => current chart version Charts may have multiple custom values files matching the glob pattern '*-values.yaml' in a directory named 'ci' in the root of the chart's directory. The chart is installed and tested for each of these files. If no custom values file is present, the chart is installed and tested with defaults.`), RunE: install, } flags := cmd.Flags() addInstallFlags(flags) addCommonLintAndInstallFlags(flags) return cmd } func addInstallFlags(flags *flag.FlagSet) { flags.String("build-id", "", heredoc.Doc(` An optional, arbitrary identifier that is added to the name of the namespace a chart is installed into. In a CI environment, this could be the build number or the ID of a pull request. If not specified, the name of the chart is used`)) flags.Bool("upgrade", false, heredoc.Doc(` Whether to test an in-place upgrade of each chart from its previous revision if the current version should not introduce a breaking change according to the SemVer spec`)) flags.Bool("skip-missing-values", false, heredoc.Doc(` When --upgrade has been passed, this flag will skip testing CI values files from the previous chart revision if they have been deleted or renamed at the current chart revision`)) flags.String("namespace", "", heredoc.Doc(` Namespace to install the release(s) into. If not specified, each release will be installed in its own randomly generated namespace`)) flags.String("release-label", "app.kubernetes.io/instance", heredoc.Doc(` The label to be used as a selector when inspecting resources created by charts. This is only used if namespace is specified`)) flags.String("helm-extra-set-args", "", heredoc.Doc(` Additional arguments for Helm. Must be passed as a single quoted string (e.g. "--set=name=value"`)) flags.Bool("skip-clean-up", false, heredoc.Doc(` Skip resources clean-up. Used if need to continue other flows or keep it around.`)) } func install(cmd *cobra.Command, _ []string) error { fmt.Println("Installing charts...") printConfig, err := cmd.Flags().GetBool("print-config") if err != nil { return err } configuration, err := config.LoadConfiguration(cfgFile, cmd, printConfig) if err != nil { return fmt.Errorf("failed loading configuration: %w", err) } extraSetArgs, err := cmd.Flags().GetString("helm-extra-set-args") if err != nil { return err } testing, err := chart.NewTesting(*configuration, extraSetArgs) if err != nil { fmt.Println(err) } results, err := testing.InstallCharts() testing.PrintResults(results) if err != nil { return fmt.Errorf("failed installing charts: %w", err) } fmt.Println("All charts installed successfully") return nil } 07070100000016000081A40000000000000000000000016624BE4500000DD8000000000000000000000000000000000000002400000000chart-testing-3.11.0/ct/cmd/lint.go// Copyright The Helm Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "fmt" "github.com/MakeNowJust/heredoc" "github.com/helm/chart-testing/v3/pkg/chart" "github.com/helm/chart-testing/v3/pkg/config" "github.com/spf13/cobra" flag "github.com/spf13/pflag" ) func newLintCmd() *cobra.Command { cmd := &cobra.Command{ Use: "lint", Short: "Lint and validate a chart", Long: heredoc.Doc(` Run 'helm lint', version checking, YAML schema validation on 'Chart.yaml', YAML linting on 'Chart.yaml' and 'values.yaml', and maintainer validation on * changed charts (default) * specific charts (--charts) * all charts (--all) in given chart directories. Charts may have multiple custom values files matching the glob pattern '*-values.yaml' in a directory named 'ci' in the root of the chart's directory. The chart is linted for each of these files. If no custom values file is present, the chart is linted with defaults.`), RunE: lint, } flags := cmd.Flags() addLintFlags(flags) addCommonLintAndInstallFlags(flags) return cmd } func addLintFlags(flags *flag.FlagSet) { flags.String("lint-conf", "", heredoc.Doc(` The config file for YAML linting. If not specified, 'lintconf.yaml' is searched in the current directory, '$HOME/.ct', and '/etc/ct', in that order`)) flags.String("chart-yaml-schema", "", heredoc.Doc(` The schema for chart.yml validation. If not specified, 'chart_schema.yaml' is searched in the current directory, '$HOME/.ct', and '/etc/ct', in that order.`)) flags.Bool("validate-maintainers", true, heredoc.Doc(` Enable validation of maintainer account names in chart.yml. Works for GitHub, GitLab, and Bitbucket`)) flags.Bool("check-version-increment", true, "Activates a check for chart version increments") flags.Bool("validate-chart-schema", true, heredoc.Doc(` Enable schema validation of 'Chart.yaml' using Yamale`)) flags.Bool("validate-yaml", true, heredoc.Doc(` Enable linting of 'Chart.yaml' and values files`)) flags.StringSlice("additional-commands", []string{}, heredoc.Doc(` Additional commands to run per chart (default: []) Commands will be executed in the same order as provided in the list and will be rendered with go template before being executed. Example: "helm unittest --helm3 -f tests/*.yaml {{ .Path }}"`)) } func lint(cmd *cobra.Command, _ []string) error { fmt.Println("Linting charts...") printConfig, err := cmd.Flags().GetBool("print-config") if err != nil { return err } configuration, err := config.LoadConfiguration(cfgFile, cmd, printConfig) if err != nil { return fmt.Errorf("failed loading configuration: %w", err) } emptyExtraSetArgs := "" testing, err := chart.NewTesting(*configuration, emptyExtraSetArgs) if err != nil { return err } results, err := testing.LintCharts() testing.PrintResults(results) if err != nil { return fmt.Errorf("failed linting charts: %w", err) } fmt.Println("All charts linted successfully") return nil } 07070100000017000081A40000000000000000000000016624BE450000077E000000000000000000000000000000000000002E00000000chart-testing-3.11.0/ct/cmd/lintAndInstall.go// Copyright The Helm Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "fmt" "github.com/helm/chart-testing/v3/pkg/chart" "github.com/helm/chart-testing/v3/pkg/config" "github.com/spf13/cobra" ) func newLintAndInstallCmd() *cobra.Command { cmd := &cobra.Command{ Use: "lint-and-install", Aliases: []string{"li"}, Short: "Lint, install, and test a chart", Long: "Combines 'lint' and 'install' commands.", RunE: lintAndInstall, } flags := cmd.Flags() addLintFlags(flags) addInstallFlags(flags) addCommonLintAndInstallFlags(flags) return cmd } func lintAndInstall(cmd *cobra.Command, _ []string) error { fmt.Println("Linting and installing charts...") printConfig, err := cmd.Flags().GetBool("print-config") if err != nil { return err } configuration, err := config.LoadConfiguration(cfgFile, cmd, printConfig) if err != nil { return fmt.Errorf("failed loading configuration: %w", err) } extraSetArgs, err := cmd.Flags().GetString("helm-extra-set-args") if err != nil { return err } testing, err := chart.NewTesting(*configuration, extraSetArgs) if err != nil { return err } results, err := testing.LintAndInstallCharts() testing.PrintResults(results) if err != nil { return fmt.Errorf("failed linting and installing charts: %w", err) } fmt.Println("All charts linted and installed successfully") return nil } 07070100000018000081A40000000000000000000000016624BE45000006D0000000000000000000000000000000000000002B00000000chart-testing-3.11.0/ct/cmd/listChanged.go// Copyright The Helm Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "fmt" "github.com/MakeNowJust/heredoc" "github.com/helm/chart-testing/v3/pkg/chart" "github.com/helm/chart-testing/v3/pkg/config" "github.com/spf13/cobra" ) func newListChangedCmd() *cobra.Command { cmd := &cobra.Command{ Use: "list-changed", Aliases: []string{"ls-changed", "lsc"}, Short: "List changed charts", Long: heredoc.Doc(` "List changed charts based on configured charts directories, "remote, and target branch`), RunE: listChanged, } flags := cmd.Flags() addCommonFlags(flags) return cmd } func listChanged(cmd *cobra.Command, _ []string) error { printConfig, err := cmd.Flags().GetBool("print-config") if err != nil { return err } configuration, err := config.LoadConfiguration(cfgFile, cmd, printConfig) if err != nil { return fmt.Errorf("failed loading configuration: %w", err) } emptyExtraSetArgs := "" testing, err := chart.NewTesting(*configuration, emptyExtraSetArgs) if err != nil { return err } chartDirs, err := testing.ComputeChangedChartDirectories() if err != nil { return err } for _, dir := range chartDirs { fmt.Println(dir) } return nil } 07070100000019000081A40000000000000000000000016624BE45000010A8000000000000000000000000000000000000002400000000chart-testing-3.11.0/ct/cmd/root.go// Copyright The Helm Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "fmt" "os" "github.com/MakeNowJust/heredoc" "github.com/spf13/cobra" "github.com/spf13/pflag" ) var ( cfgFile string ) func NewRootCmd() *cobra.Command { cmd := &cobra.Command{ Use: "ct", Short: "The Helm chart testing tool", Long: heredoc.Doc(` Lint and test * changed charts * specific charts * all charts in given chart directories.`), SilenceUsage: true, } cmd.AddCommand(newLintCmd()) cmd.AddCommand(newInstallCmd()) cmd.AddCommand(newLintAndInstallCmd()) cmd.AddCommand(newListChangedCmd()) cmd.AddCommand(newVersionCmd()) cmd.AddCommand(newGenerateDocsCmd()) cmd.DisableAutoGenTag = true return cmd } // Execute runs the application func Execute() { if err := NewRootCmd().Execute(); err != nil { fmt.Println(err) os.Exit(1) } } func addCommonFlags(flags *pflag.FlagSet) { flags.StringVar(&cfgFile, "config", "", "Config file") flags.String("remote", "origin", "The name of the Git remote used to identify changed charts") flags.String("target-branch", "main", "The name of the target branch used to identify changed charts") flags.String("since", "HEAD", "The Git reference used to identify changed charts") flags.StringSlice("chart-dirs", []string{"charts"}, heredoc.Doc(` Directories containing Helm charts. May be specified multiple times or separate values with commas`)) flags.StringSlice("excluded-charts", []string{}, heredoc.Doc(` Charts that should be skipped. May be specified multiple times or separate values with commas`)) flags.Bool("print-config", false, heredoc.Doc(` Prints the configuration to stderr (caution: setting this may expose sensitive data when helm-repo-extra-args contains passwords)`)) flags.Bool("exclude-deprecated", false, "Skip charts that are marked as deprecated") flags.Bool("github-groups", false, heredoc.Doc(` Change the delimiters for github to create collapsible groups for command output`)) } func addCommonLintAndInstallFlags(flags *pflag.FlagSet) { addCommonFlags(flags) flags.Bool("all", false, heredoc.Doc(` Process all charts except those explicitly excluded. Disables changed charts detection and version increment checking`)) flags.StringSlice("charts", []string{}, heredoc.Doc(` Specific charts to test. Disables changed charts detection and version increment checking. May be specified multiple times or separate values with commas`)) flags.StringSlice("chart-repos", []string{}, heredoc.Doc(` Additional chart repositories for dependency resolutions. Repositories should be formatted as 'name=url' (ex: local=http://127.0.0.1:8879/charts). May be specified multiple times or separate values with commas`)) flags.String("helm-extra-args", "", heredoc.Doc(` Additional arguments for Helm. Must be passed as a single quoted string (e.g. '--timeout 500s')`)) flags.String("helm-lint-extra-args", "", heredoc.Doc(` Additional arguments for Helm lint subcommand. Must be passed as a single quoted string (e.g. '--quiet')`)) flags.StringSlice("helm-repo-extra-args", []string{}, heredoc.Doc(` Additional arguments for the 'helm repo add' command to be specified on a per-repo basis with an equals sign as delimiter (e.g. 'myrepo=--username test --password secret'). May be specified multiple times or separate values with commas`)) flags.StringSlice("helm-dependency-extra-args", []string{}, heredoc.Doc(` Additional arguments for 'helm dependency build' (e.g. ["--skip-refresh"]`)) flags.Bool("debug", false, heredoc.Doc(` Print CLI calls of external tools to stdout (caution: setting this may expose sensitive data when helm-repo-extra-args contains passwords)`)) } 0707010000001A000081A40000000000000000000000016624BE4500000503000000000000000000000000000000000000002700000000chart-testing-3.11.0/ct/cmd/version.go// Copyright The Helm Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "fmt" "github.com/spf13/cobra" ) var ( // GitCommit is updated with the Git tag by the Goreleaser build GitCommit = "unknown" // BuildDate is updated with the current ISO timestamp by the Goreleaser build BuildDate = "unknown" // Version is updated with the latest tag by the Goreleaser build Version = "unreleased" ) func newVersionCmd() *cobra.Command { return &cobra.Command{ Use: "version", Short: "Print version information", Run: version, } } func version(_ *cobra.Command, _ []string) { fmt.Println("Version:\t", Version) fmt.Println("Git commit:\t", GitCommit) fmt.Println("Date:\t\t", BuildDate) fmt.Println("License:\t Apache 2.0") } 0707010000001B000081A40000000000000000000000016624BE45000002B1000000000000000000000000000000000000002000000000chart-testing-3.11.0/ct/main.go// Copyright The Helm Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "github.com/helm/chart-testing/v3/ct/cmd" ) func main() { cmd.Execute() } 0707010000001C000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000001900000000chart-testing-3.11.0/doc0707010000001D000081A40000000000000000000000016624BE450000020B000000000000000000000000000000000000001F00000000chart-testing-3.11.0/doc/ct.md## ct The Helm chart testing tool ### Synopsis Lint and test * changed charts * specific charts * all charts in given chart directories. ### Options ``` -h, --help help for ct ``` ### SEE ALSO * [ct install](ct_install.md) - Install and test a chart * [ct lint](ct_lint.md) - Lint and validate a chart * [ct lint-and-install](ct_lint-and-install.md) - Lint, install, and test a chart * [ct list-changed](ct_list-changed.md) - List changed charts * [ct version](ct_version.md) - Print version information 0707010000001E000081A40000000000000000000000016624BE450000173D000000000000000000000000000000000000002700000000chart-testing-3.11.0/doc/ct_install.md## ct install Install and test a chart ### Synopsis Run 'helm install', 'helm test', and optionally 'helm upgrade' on * changed charts (default) * specific charts (--charts) * all charts (--all) in given chart directories. If upgrade (--upgrade) is true, then this command will validate that 'helm test' passes for the following upgrade paths: * previous chart revision => current chart version (if non-breaking SemVer change) * current chart version => current chart version Charts may have multiple custom values files matching the glob pattern '*-values.yaml' in a directory named 'ci' in the root of the chart's directory. The chart is installed and tested for each of these files. If no custom values file is present, the chart is installed and tested with defaults. ``` ct install [flags] ``` ### Options ``` --all Process all charts except those explicitly excluded. Disables changed charts detection and version increment checking --build-id string An optional, arbitrary identifier that is added to the name of the namespace a chart is installed into. In a CI environment, this could be the build number or the ID of a pull request. If not specified, the name of the chart is used --chart-dirs strings Directories containing Helm charts. May be specified multiple times or separate values with commas (default [charts]) --chart-repos strings Additional chart repositories for dependency resolutions. Repositories should be formatted as 'name=url' (ex: local=http://127.0.0.1:8879/charts). May be specified multiple times or separate values with commas --charts strings Specific charts to test. Disables changed charts detection and version increment checking. May be specified multiple times or separate values with commas --config string Config file --debug Print CLI calls of external tools to stdout (caution: setting this may expose sensitive data when helm-repo-extra-args contains passwords) --exclude-deprecated Skip charts that are marked as deprecated --excluded-charts strings Charts that should be skipped. May be specified multiple times or separate values with commas --github-groups Change the delimiters for github to create collapsible groups for command output --helm-dependency-extra-args strings Additional arguments for 'helm dependency build' (e.g. ["--skip-refresh"] --helm-extra-args string Additional arguments for Helm. Must be passed as a single quoted string (e.g. '--timeout 500s') --helm-extra-set-args string Additional arguments for Helm. Must be passed as a single quoted string (e.g. "--set=name=value" --helm-lint-extra-args string Additional arguments for Helm lint subcommand. Must be passed as a single quoted string (e.g. '--quiet') --helm-repo-extra-args strings Additional arguments for the 'helm repo add' command to be specified on a per-repo basis with an equals sign as delimiter (e.g. 'myrepo=--username test --password secret'). May be specified multiple times or separate values with commas -h, --help help for install --namespace string Namespace to install the release(s) into. If not specified, each release will be installed in its own randomly generated namespace --print-config Prints the configuration to stderr (caution: setting this may expose sensitive data when helm-repo-extra-args contains passwords) --release-label string The label to be used as a selector when inspecting resources created by charts. This is only used if namespace is specified (default "app.kubernetes.io/instance") --remote string The name of the Git remote used to identify changed charts (default "origin") --since string The Git reference used to identify changed charts (default "HEAD") --skip-clean-up Skip resources clean-up. Used if need to continue other flows or keep it around. --skip-missing-values When --upgrade has been passed, this flag will skip testing CI values files from the previous chart revision if they have been deleted or renamed at the current chart revision --target-branch string The name of the target branch used to identify changed charts (default "main") --upgrade Whether to test an in-place upgrade of each chart from its previous revision if the current version should not introduce a breaking change according to the SemVer spec ``` ### SEE ALSO * [ct](ct.md) - The Helm chart testing tool 0707010000001F000081A40000000000000000000000016624BE4500001AAC000000000000000000000000000000000000003000000000chart-testing-3.11.0/doc/ct_lint-and-install.md## ct lint-and-install Lint, install, and test a chart ### Synopsis Combines 'lint' and 'install' commands. ``` ct lint-and-install [flags] ``` ### Options ``` --additional-commands strings Additional commands to run per chart (default: []) Commands will be executed in the same order as provided in the list and will be rendered with go template before being executed. Example: "helm unittest --helm3 -f tests/*.yaml {{ .Path }}" --all Process all charts except those explicitly excluded. Disables changed charts detection and version increment checking --build-id string An optional, arbitrary identifier that is added to the name of the namespace a chart is installed into. In a CI environment, this could be the build number or the ID of a pull request. If not specified, the name of the chart is used --chart-dirs strings Directories containing Helm charts. May be specified multiple times or separate values with commas (default [charts]) --chart-repos strings Additional chart repositories for dependency resolutions. Repositories should be formatted as 'name=url' (ex: local=http://127.0.0.1:8879/charts). May be specified multiple times or separate values with commas --chart-yaml-schema string The schema for chart.yml validation. If not specified, 'chart_schema.yaml' is searched in the current directory, '$HOME/.ct', and '/etc/ct', in that order. --charts strings Specific charts to test. Disables changed charts detection and version increment checking. May be specified multiple times or separate values with commas --check-version-increment Activates a check for chart version increments (default true) --config string Config file --debug Print CLI calls of external tools to stdout (caution: setting this may expose sensitive data when helm-repo-extra-args contains passwords) --exclude-deprecated Skip charts that are marked as deprecated --excluded-charts strings Charts that should be skipped. May be specified multiple times or separate values with commas --github-groups Change the delimiters for github to create collapsible groups for command output --helm-dependency-extra-args strings Additional arguments for 'helm dependency build' (e.g. ["--skip-refresh"] --helm-extra-args string Additional arguments for Helm. Must be passed as a single quoted string (e.g. '--timeout 500s') --helm-extra-set-args string Additional arguments for Helm. Must be passed as a single quoted string (e.g. "--set=name=value" --helm-lint-extra-args string Additional arguments for Helm lint subcommand. Must be passed as a single quoted string (e.g. '--quiet') --helm-repo-extra-args strings Additional arguments for the 'helm repo add' command to be specified on a per-repo basis with an equals sign as delimiter (e.g. 'myrepo=--username test --password secret'). May be specified multiple times or separate values with commas -h, --help help for lint-and-install --lint-conf string The config file for YAML linting. If not specified, 'lintconf.yaml' is searched in the current directory, '$HOME/.ct', and '/etc/ct', in that order --namespace string Namespace to install the release(s) into. If not specified, each release will be installed in its own randomly generated namespace --print-config Prints the configuration to stderr (caution: setting this may expose sensitive data when helm-repo-extra-args contains passwords) --release-label string The label to be used as a selector when inspecting resources created by charts. This is only used if namespace is specified (default "app.kubernetes.io/instance") --remote string The name of the Git remote used to identify changed charts (default "origin") --since string The Git reference used to identify changed charts (default "HEAD") --skip-clean-up Skip resources clean-up. Used if need to continue other flows or keep it around. --skip-missing-values When --upgrade has been passed, this flag will skip testing CI values files from the previous chart revision if they have been deleted or renamed at the current chart revision --target-branch string The name of the target branch used to identify changed charts (default "main") --upgrade Whether to test an in-place upgrade of each chart from its previous revision if the current version should not introduce a breaking change according to the SemVer spec --validate-chart-schema Enable schema validation of 'Chart.yaml' using Yamale (default true) --validate-maintainers Enable validation of maintainer account names in chart.yml. Works for GitHub, GitLab, and Bitbucket (default true) --validate-yaml Enable linting of 'Chart.yaml' and values files (default true) ``` ### SEE ALSO * [ct](ct.md) - The Helm chart testing tool 07070100000020000081A40000000000000000000000016624BE45000015AE000000000000000000000000000000000000002400000000chart-testing-3.11.0/doc/ct_lint.md## ct lint Lint and validate a chart ### Synopsis Run 'helm lint', version checking, YAML schema validation on 'Chart.yaml', YAML linting on 'Chart.yaml' and 'values.yaml', and maintainer validation on * changed charts (default) * specific charts (--charts) * all charts (--all) in given chart directories. Charts may have multiple custom values files matching the glob pattern '*-values.yaml' in a directory named 'ci' in the root of the chart's directory. The chart is linted for each of these files. If no custom values file is present, the chart is linted with defaults. ``` ct lint [flags] ``` ### Options ``` --additional-commands strings Additional commands to run per chart (default: []) Commands will be executed in the same order as provided in the list and will be rendered with go template before being executed. Example: "helm unittest --helm3 -f tests/*.yaml {{ .Path }}" --all Process all charts except those explicitly excluded. Disables changed charts detection and version increment checking --chart-dirs strings Directories containing Helm charts. May be specified multiple times or separate values with commas (default [charts]) --chart-repos strings Additional chart repositories for dependency resolutions. Repositories should be formatted as 'name=url' (ex: local=http://127.0.0.1:8879/charts). May be specified multiple times or separate values with commas --chart-yaml-schema string The schema for chart.yml validation. If not specified, 'chart_schema.yaml' is searched in the current directory, '$HOME/.ct', and '/etc/ct', in that order. --charts strings Specific charts to test. Disables changed charts detection and version increment checking. May be specified multiple times or separate values with commas --check-version-increment Activates a check for chart version increments (default true) --config string Config file --debug Print CLI calls of external tools to stdout (caution: setting this may expose sensitive data when helm-repo-extra-args contains passwords) --exclude-deprecated Skip charts that are marked as deprecated --excluded-charts strings Charts that should be skipped. May be specified multiple times or separate values with commas --github-groups Change the delimiters for github to create collapsible groups for command output --helm-dependency-extra-args strings Additional arguments for 'helm dependency build' (e.g. ["--skip-refresh"] --helm-extra-args string Additional arguments for Helm. Must be passed as a single quoted string (e.g. '--timeout 500s') --helm-lint-extra-args string Additional arguments for Helm lint subcommand. Must be passed as a single quoted string (e.g. '--quiet') --helm-repo-extra-args strings Additional arguments for the 'helm repo add' command to be specified on a per-repo basis with an equals sign as delimiter (e.g. 'myrepo=--username test --password secret'). May be specified multiple times or separate values with commas -h, --help help for lint --lint-conf string The config file for YAML linting. If not specified, 'lintconf.yaml' is searched in the current directory, '$HOME/.ct', and '/etc/ct', in that order --print-config Prints the configuration to stderr (caution: setting this may expose sensitive data when helm-repo-extra-args contains passwords) --remote string The name of the Git remote used to identify changed charts (default "origin") --since string The Git reference used to identify changed charts (default "HEAD") --target-branch string The name of the target branch used to identify changed charts (default "main") --validate-chart-schema Enable schema validation of 'Chart.yaml' using Yamale (default true) --validate-maintainers Enable validation of maintainer account names in chart.yml. Works for GitHub, GitLab, and Bitbucket (default true) --validate-yaml Enable linting of 'Chart.yaml' and values files (default true) ``` ### SEE ALSO * [ct](ct.md) - The Helm chart testing tool 07070100000021000081A40000000000000000000000016624BE45000005B2000000000000000000000000000000000000002C00000000chart-testing-3.11.0/doc/ct_list-changed.md## ct list-changed List changed charts ### Synopsis "List changed charts based on configured charts directories, "remote, and target branch ``` ct list-changed [flags] ``` ### Options ``` --chart-dirs strings Directories containing Helm charts. May be specified multiple times or separate values with commas (default [charts]) --config string Config file --exclude-deprecated Skip charts that are marked as deprecated --excluded-charts strings Charts that should be skipped. May be specified multiple times or separate values with commas --github-groups Change the delimiters for github to create collapsible groups for command output -h, --help help for list-changed --print-config Prints the configuration to stderr (caution: setting this may expose sensitive data when helm-repo-extra-args contains passwords) --remote string The name of the Git remote used to identify changed charts (default "origin") --since string The Git reference used to identify changed charts (default "HEAD") --target-branch string The name of the target branch used to identify changed charts (default "main") ``` ### SEE ALSO * [ct](ct.md) - The Helm chart testing tool 07070100000022000081A40000000000000000000000016624BE45000000B8000000000000000000000000000000000000002700000000chart-testing-3.11.0/doc/ct_version.md## ct version Print version information ``` ct version [flags] ``` ### Options ``` -h, --help help for version ``` ### SEE ALSO * [ct](ct.md) - The Helm chart testing tool 07070100000023000081ED0000000000000000000000016624BE4500000283000000000000000000000000000000000000002100000000chart-testing-3.11.0/e2e-kind.sh#!/usr/bin/env bash set -o errexit set -o nounset set -o pipefail CLUSTER_NAME=chart-testing readonly CLUSTER_NAME K8S_VERSION=v1.22.9 readonly K8S_VERSION create_kind_cluster() { kind create cluster --name "$CLUSTER_NAME" --image "kindest/node:$K8S_VERSION" --wait 60s kubectl cluster-info || kubectl cluster-info dump echo kubectl get nodes echo echo 'Cluster ready!' echo } test_e2e() { go test -cover -race -tags=integration ./... echo } cleanup() { kind delete cluster --name "$CLUSTER_NAME" echo 'Done!' } main() { trap cleanup EXIT create_kind_cluster test_e2e } main 07070100000024000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000001900000000chart-testing-3.11.0/etc07070100000025000081A40000000000000000000000016624BE45000003F9000000000000000000000000000000000000002B00000000chart-testing-3.11.0/etc/chart_schema.yamlname: str() home: str(required=False) version: str() apiVersion: str() appVersion: any(str(), num(), required=False) description: str(required=False) keywords: list(str(), required=False) sources: list(str(), required=False) maintainers: list(include('maintainer'), required=False) dependencies: list(include('dependency'), required=False) icon: str(required=False) engine: str(required=False) condition: str(required=False) tags: str(required=False) deprecated: bool(required=False) kubeVersion: str(required=False) annotations: map(str(), str(), required=False) type: str(required=False) --- maintainer: name: str() email: str(required=False) url: str(required=False) --- dependency: name: str() version: str() repository: str(required=False) condition: str(required=False) tags: list(str(), required=False) enabled: bool(required=False) import-values: any(list(str()), list(include('import-value')), required=False) alias: str(required=False) --- import-value: child: str() parent: str() 07070100000026000081A40000000000000000000000016624BE4500000409000000000000000000000000000000000000002700000000chart-testing-3.11.0/etc/lintconf.yaml--- rules: braces: min-spaces-inside: 0 max-spaces-inside: 0 min-spaces-inside-empty: -1 max-spaces-inside-empty: -1 brackets: min-spaces-inside: 0 max-spaces-inside: 0 min-spaces-inside-empty: -1 max-spaces-inside-empty: -1 colons: max-spaces-before: 0 max-spaces-after: 1 commas: max-spaces-before: 0 min-spaces-after: 1 max-spaces-after: 1 comments: require-starting-space: true min-spaces-from-content: 2 document-end: disable document-start: disable # No --- to start a file empty-lines: max: 2 max-start: 0 max-end: 0 hyphens: max-spaces-after: 1 indentation: spaces: consistent indent-sequences: whatever # - list indentation will handle both indentation and without check-multi-line-strings: false key-duplicates: enable line-length: disable # Lines can be any length new-line-at-end-of-file: enable new-lines: type: unix trailing-spaces: enable truthy: level: warning 07070100000027000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000001E00000000chart-testing-3.11.0/examples07070100000028000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000002D00000000chart-testing-3.11.0/examples/docker-for-mac07070100000029000081ED0000000000000000000000016624BE4500000A2D000000000000000000000000000000000000003800000000chart-testing-3.11.0/examples/docker-for-mac/my_test.sh#!/usr/bin/env bash # Copyright The Helm Authors. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit set -o nounset set -o pipefail readonly IMAGE_TAG=v3.7.1 readonly IMAGE_REPOSITORY="quay.io/helmpack/chart-testing" main() { local testcontainer_id testcontainer_id=$(create_testcontainer) # shellcheck disable=SC2064 trap "docker container rm --force $testcontainer_id > /dev/null" EXIT configure_kubectl "$testcontainer_id" run_test } lookup_apiserver_container_id() { docker container list --filter name=k8s_kube-apiserver --format '{{ .ID }}' } get_apiserver_arg() { local container_id="$1" local arg="$2" docker container inspect "$container_id" | jq -r ".[].Args[] | capture(\"$arg=(?<arg>.*)\") | .arg" } create_testcontainer() { docker container run --interactive --tty --detach \ --volume "$(pwd):/workdir" --workdir /workdir \ "$IMAGE_REPOSITORY:$IMAGE_TAG" cat } configure_kubectl() { local testcontainer_id="$1" local apiserver_id apiserver_id=$(lookup_apiserver_container_id) if [[ -z "$apiserver_id" ]]; then echo "ERROR: API-Server container not found. Make sure 'Show system containers' is enabled in Docker4Mac 'Preferences'!" >&2 exit 1 fi local ip ip=$(get_apiserver_arg "$apiserver_id" --advertise-address) local port port=$(get_apiserver_arg "$apiserver_id" --secure-port) docker cp "$HOME/.kube" "$testcontainer_id:/root/.kube" docker exec "$testcontainer_id" kubectl config set-cluster docker-desktop "--server=https://$ip:$port" docker exec "$testcontainer_id" kubectl config set-cluster docker-desktop --insecure-skip-tls-verify=true docker exec "$testcontainer_id" kubectl config use-context docker-desktop } run_test() { git remote add k8s https://github.com/helm/charts.git &> /dev/null || true git fetch k8s docker exec "$testcontainer_id" ct lint --chart-dirs stable,incubator --remote k8s docker exec "$testcontainer_id" ct install --chart-dirs stable,incubator --remote k8s } main 0707010000002A000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000002E00000000chart-testing-3.11.0/examples/gcp-cloud-build0707010000002B000081A40000000000000000000000016624BE450000017B000000000000000000000000000000000000003800000000chart-testing-3.11.0/examples/gcp-cloud-build/README.md# Chart testing example with Google Cloud Build This example shows how to lint and test charts using [Google Cloud Build](https://cloud.google.com/cloud-build/) Since Google Cloud Build will ignore copying over `.git` by default, you will need to initialize `git` and add a `remote`. This example assumes that there is a pre-existing GKE cluster with `helm` already installed. 0707010000002C000081A40000000000000000000000016624BE450000028B000000000000000000000000000000000000003E00000000chart-testing-3.11.0/examples/gcp-cloud-build/cloudbuild.yamlsteps: - name: 'gcr.io/cloud-builders/git' id: 'git-init' args: ['init'] waitFor: ['-'] - name: 'gcr.io/cloud-builders/git' id: 'git-add-remote' args: ['remote', 'add', 'origin', 'git@github.com:github-account/charts-repo.git'] waitFor: ['git-init'] - name: 'gcr.io/cloud-builders/kubectl' id: 'cluster-info' args: ['cluster-info'] waitFor: ['git-add-remote'] - name: quay.io/helmpack/chart-testing id: 'lint-and-install-charts' entrypoint: 'ct' args: ['lint-and-install'] waitFor: ['cluster-info'] options: env: - CLOUDSDK_COMPUTE_ZONE=cluster-location - CLOUDSDK_CONTAINER_CLUSTER=cluster-name timeout: 3600s 0707010000002D000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000002200000000chart-testing-3.11.0/examples/gke0707010000002E000081A40000000000000000000000016624BE4500000515000000000000000000000000000000000000002D00000000chart-testing-3.11.0/examples/gke/Dockerfile# Copyright The Helm Authors. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. FROM quay.io/helmpack/chart-testing:v3.7.1 ENV PATH /google-cloud-sdk/bin:$PATH ARG CLOUD_SDK_VERSION=221.0.0 RUN curl -LO "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-$CLOUD_SDK_VERSION-linux-x86_64.tar.gz" && \ tar xzf "google-cloud-sdk-$CLOUD_SDK_VERSION-linux-x86_64.tar.gz" && \ rm "google-cloud-sdk-$CLOUD_SDK_VERSION-linux-x86_64.tar.gz" && \ ln -s /lib /lib64 && \ rm -rf /google-cloud-sdk/.install/.backup && \ gcloud version RUN gcloud config set core/disable_usage_reporting true && \ gcloud config set component_manager/disable_update_check true && \ gcloud config set metrics/environment github_docker_image WORKDIR /workdir 0707010000002F000081ED0000000000000000000000016624BE450000061D000000000000000000000000000000000000002D00000000chart-testing-3.11.0/examples/gke/my_test.sh#!/usr/bin/env bash # Copyright The Helm Authors. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit set -o nounset set -o pipefail readonly IMAGE_REPOSITORY="myrepo/chart-testing" readonly IMAGE_TAG="v1.0.0" readonly REPO_ROOT="${REPO_ROOT:-$(git rev-parse --show-toplevel)}" main() { local config_container_id config_container_id=$(docker run -ti -d \ -v "$GOOGLE_APPLICATION_CREDENTIALS:/service-account.json" \ -v "$REPO_ROOT:/workdir" \ -e "BUILD_ID=$PULL_NUMBER" \ "$IMAGE_REPOSITORY:$IMAGE_TAG" cat) # shellcheck disable=SC2064 trap "docker rm -f $config_container_id" EXIT docker exec "$config_container_id" gcloud auth activate-service-account --key-file /service-account.json docker exec "$config_container_id" gcloud container clusters get-credentials my-cluster --project my-project --zone us-west1-a docker exec "$config_container_id" kubectl cluster-info docker exec "$config_container_id" ct lint-and-install --chart-dirs stable,incubator } main 07070100000030000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000002300000000chart-testing-3.11.0/examples/kind07070100000031000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000002D00000000chart-testing-3.11.0/examples/kind/.circleci07070100000032000081A40000000000000000000000016624BE4500000246000000000000000000000000000000000000003800000000chart-testing-3.11.0/examples/kind/.circleci/config.ymlversion: 2.1 jobs: lint-scripts: docker: - image: koalaman/shellcheck-alpine steps: - checkout - run: command: shellcheck -x test/e2e-kind.sh lint-charts: docker: - image: quay.io/helmpack/chart-testing:v3.7.1 steps: - checkout - run: command: ct lint --config test/ct.yaml install-charts: machine: true steps: - checkout - run: command: test/e2e-kind.sh workflows: version: 2 untagged-build: jobs: - lint-scripts - lint-charts - install-charts 07070100000033000081A40000000000000000000000016624BE450000024C000000000000000000000000000000000000002D00000000chart-testing-3.11.0/examples/kind/README.md# Chart testing example with CircleCi and kind - `K`ubernetes `in` `D`ocker `kind` is a tool for running local Kubernetes clusters using Docker container "nodes". This example shows how to lint and test charts using CircleCi and [kind](https://github.com/kubernetes-sigs/kind). It creates a cluster with a single control-plane node and one worker node. The cluster configuration can be adjusted in [kind-config.yaml](test/kind-config.yaml). You can check for available configuration options in `kind` [docs](https://kind.sigs.k8s.io/docs/user/quick-start#configuring-your-kind-cluster) 07070100000034000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000002800000000chart-testing-3.11.0/examples/kind/test07070100000035000081A40000000000000000000000016624BE4500000020000000000000000000000000000000000000003000000000chart-testing-3.11.0/examples/kind/test/ct.yamlhelm-extra-args: --timeout 800s 07070100000036000081ED0000000000000000000000016624BE45000005B5000000000000000000000000000000000000003400000000chart-testing-3.11.0/examples/kind/test/e2e-kind.sh#!/usr/bin/env bash set -o errexit set -o nounset set -o pipefail readonly CT_VERSION=v3.7.1 readonly KIND_VERSION=v0.12.0 readonly CLUSTER_NAME=chart-testing readonly K8S_VERSION=v1.22.7 run_ct_container() { echo 'Running ct container...' docker run --rm --interactive --detach --network host --name ct \ --volume "$(pwd)/test/ct.yaml:/etc/ct/ct.yaml" \ --volume "$(pwd):/workdir" \ --workdir /workdir \ "quay.io/helmpack/chart-testing:$CT_VERSION" \ cat echo } cleanup() { echo 'Removing ct container...' docker kill ct > /dev/null 2>&1 echo 'Done!' } docker_exec() { docker exec --interactive ct "$@" } create_kind_cluster() { echo 'Installing kind...' curl -sSLo kind "https://github.com/kubernetes-sigs/kind/releases/download/$KIND_VERSION/kind-linux-amd64" chmod +x kind sudo mv kind /usr/local/bin/kind kind create cluster --name "$CLUSTER_NAME" --config test/kind-config.yaml --image "kindest/node:$K8S_VERSION" --wait 60s echo 'Copying kubeconfig to container...' docker_exec mkdir /root/.kube docker cp /root/.kube/kind-config ct:/root/.kube/config docker_exec kubectl cluster-info echo docker_exec kubectl get nodes echo echo 'Cluster ready!' echo } install_charts() { docker_exec ct install echo } main() { run_ct_container trap cleanup EXIT create_kind_cluster install_charts } main 07070100000037000081A40000000000000000000000016624BE4500000061000000000000000000000000000000000000003900000000chart-testing-3.11.0/examples/kind/test/kind-config.yamlkind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane - role: worker 07070100000038000081A40000000000000000000000016624BE4500000A51000000000000000000000000000000000000004400000000chart-testing-3.11.0/examples/kind/test/local-path-provisioner.yamlapiVersion: v1 kind: Namespace metadata: name: local-path-storage --- apiVersion: v1 kind: ServiceAccount metadata: name: local-path-provisioner-service-account namespace: local-path-storage --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: name: local-path-provisioner-role namespace: local-path-storage rules: - apiGroups: [""] resources: ["nodes", "persistentvolumeclaims"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["endpoints", "persistentvolumes", "pods"] verbs: ["*"] - apiGroups: [""] resources: ["events"] verbs: ["create", "patch"] - apiGroups: ["storage.k8s.io"] resources: ["storageclasses"] verbs: ["get", "list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: local-path-provisioner-bind namespace: local-path-storage roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: local-path-provisioner-role subjects: - kind: ServiceAccount name: local-path-provisioner-service-account namespace: local-path-storage --- apiVersion: apps/v1beta2 kind: Deployment metadata: name: local-path-provisioner namespace: local-path-storage spec: replicas: 1 selector: matchLabels: app: local-path-provisioner template: metadata: labels: app: local-path-provisioner spec: serviceAccountName: local-path-provisioner-service-account containers: - name: local-path-provisioner image: rancher/local-path-provisioner:v0.0.11 imagePullPolicy: Always command: - local-path-provisioner - --debug - start - --config - /etc/config/config.json volumeMounts: - name: config-volume mountPath: /etc/config/ env: - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace volumes: - name: config-volume configMap: name: local-path-config --- apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: local-path annotations: storageclass.kubernetes.io/is-default-class: "true" provisioner: rancher.io/local-path volumeBindingMode: WaitForFirstConsumer reclaimPolicy: Delete --- kind: ConfigMap apiVersion: v1 metadata: name: local-path-config namespace: local-path-storage data: config.json: |- { "nodePathMap":[ { "node":"DEFAULT_PATH_FOR_NON_LISTED_NODES", "paths":["/opt/local-path-provisioner"] } ] } 07070100000039000081A40000000000000000000000016624BE450000073B000000000000000000000000000000000000001C00000000chart-testing-3.11.0/go.modmodule github.com/helm/chart-testing/v3 go 1.22 require ( github.com/MakeNowJust/heredoc v1.0.0 github.com/Masterminds/semver v1.5.0 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-retryablehttp v0.7.5 github.com/mattn/go-shellwords v1.0.12 github.com/mitchellh/go-homedir v1.1.0 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.9.0 gopkg.in/yaml.v2 v2.4.0 ) require ( github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) 0707010000003A000081A40000000000000000000000016624BE45000026AC000000000000000000000000000000000000001C00000000chart-testing-3.11.0/go.sumgithub.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 0707010000003B000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000001900000000chart-testing-3.11.0/pkg0707010000003C000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000001F00000000chart-testing-3.11.0/pkg/chart0707010000003D000081A40000000000000000000000016624BE4500007774000000000000000000000000000000000000002800000000chart-testing-3.11.0/pkg/chart/chart.go// Copyright The Helm Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package chart import ( "errors" "fmt" "os" "path/filepath" "strings" "github.com/Masterminds/semver" "github.com/helm/chart-testing/v3/pkg/config" "github.com/helm/chart-testing/v3/pkg/exec" "github.com/helm/chart-testing/v3/pkg/tool" "github.com/helm/chart-testing/v3/pkg/util" ) const maxNameLength = 63 // Git is the Interface that wraps Git operations. // // FileExistsOnBranch checks whether file exists on the specified remote/branch. // // Show returns the contents of file on the specified remote/branch. // // AddWorktree checks out the contents of the repository at a commit ref into the specified path. // // RemoveWorktree removes the working tree at the specified path. // // MergeBase returns the SHA1 of the merge base of commit1 and commit2. // // ListChangedFilesInDirs diffs commit against HEAD and returns changed files for the specified dirs. // // GetURLForRemote returns the repo URL for the specified remote. // // ValidateRepository checks that the current working directory is a valid git repository, // and returns nil if valid. // // BranchExists checks whether a given branch exists in the git repository. type Git interface { FileExistsOnBranch(file string, remote string, branch string) bool Show(file string, remote string, branch string) (string, error) AddWorktree(path string, ref string) error RemoveWorktree(path string) error MergeBase(commit1 string, commit2 string) (string, error) ListChangedFilesInDirs(commit string, dirs ...string) ([]string, error) GetURLForRemote(remote string) (string, error) ValidateRepository() error BranchExists(branch string) bool } // Helm is the interface that wraps Helm operations // // # AddRepo adds a chart repository to the local Helm configuration // // # BuildDependencies builds the chart's dependencies // // # BuildDependenciesWithArgs allows passing additional arguments to BuildDependencies // // LintWithValues runs `helm lint` for the given chart using the specified values file. // Pass a zero value for valuesFile in order to run lint without specifying a values file. // // InstallWithValues runs `helm install` for the given chart using the specified values file. // Pass a zero value for valuesFile in order to run install without specifying a values file. // // Upgrade runs `helm upgrade` against an existing release, and re-uses the previously computed values. // // Test runs `helm test` against an existing release. Set the cleanup argument to true in order // to clean up test pods created by helm after the test command completes. // // DeleteRelease purges the specified Helm release. type Helm interface { AddRepo(name string, url string, extraArgs []string) error BuildDependencies(chart string) error BuildDependenciesWithArgs(chart string, extraArgs []string) error LintWithValues(chart string, valuesFile string) error InstallWithValues(chart string, valuesFile string, namespace string, release string) error Upgrade(chart string, namespace string, release string) error Test(namespace string, release string) error DeleteRelease(namespace string, release string) Version() (string, error) } // Kubectl is the interface that wraps kubectl operations // // # DeleteNamespace deletes a namespace // // # WaitForDeployments waits for a deployment to become ready // // # GetPodsforDeployment gets all pods for a deployment // // # GetPods gets pods for the given args // // # GetEvents prints all events for namespace // // # DescribePod prints the pod's description // // # Logs prints the logs of container // // # GetInitContainers gets all init containers of pod // // GetContainers gets all containers of pod type Kubectl interface { CreateNamespace(namespace string) error DeleteNamespace(namespace string) WaitForDeployments(namespace string, selector string) error GetPodsforDeployment(namespace string, deployment string) ([]string, error) GetPods(args ...string) ([]string, error) GetEvents(namespace string) error DescribePod(namespace string, pod string) error Logs(namespace string, pod string, container string) error GetInitContainers(namespace string, pod string) ([]string, error) GetContainers(namespace string, pod string) ([]string, error) } // Linter is the interface that wrap linting operations // // # YamlLint runs `yamllint` on the specified file with the specified configuration // // Yamale runs `yamale` on the specified file with the specified schema file type Linter interface { YamlLint(yamlFile string, configFile string) error Yamale(yamlFile string, schemaFile string) error } // CmdExecutor is the interface // // RunCommand renders cmdTemplate as go template using data and executes the resulting command type CmdExecutor interface { RunCommand(cmdTemplate string, data interface{}) error } // DirectoryLister is the interface // // ListChildDirs lists direct child directories of parentDir given they pass the test function type DirectoryLister interface { ListChildDirs(parentDir string, test func(string) bool) ([]string, error) } // Utils is the interface that wraps chart-related methods // // LookupChartDir looks up the chart's root directory based on some chart file that has changed type Utils interface { LookupChartDir(chartDirs []string, dir string) (string, error) } // AccountValidator is the interface that wraps Git account validation // // Validate checks if account is valid on repoDomain type AccountValidator interface { Validate(repoDomain string, account string) error } // Chart represents a Helm chart, and can be initialized with the NewChart method. type Chart struct { path string yaml *util.ChartYaml ciValuesPaths []string } // Yaml returns the Chart metadata func (c *Chart) Yaml() *util.ChartYaml { return c.yaml } // Path returns the chart's directory path func (c *Chart) Path() string { return c.path } func (c *Chart) String() string { return fmt.Sprintf(`%s => (version: "%s", path: "%s")`, c.yaml.Name, c.yaml.Version, c.Path()) } // ValuesFilePathsForCI returns all file paths in the 'ci' subfolder of the chart directory matching the pattern '*-values.yaml' func (c *Chart) ValuesFilePathsForCI() []string { return c.ciValuesPaths } // HasCIValuesFile checks whether a given CI values file is present. func (c *Chart) HasCIValuesFile(path string) bool { fileName := filepath.Base(path) for _, file := range c.ValuesFilePathsForCI() { if fileName == filepath.Base(file) { return true } } return false } // CreateInstallParams generates a randomized release name and namespace based on the chart path // and optional buildID. If a buildID is specified, it will be part of the generated namespace. func (c *Chart) CreateInstallParams(buildID string) (release string, namespace string) { release = filepath.Base(c.Path()) if release == "." || release == "/" { yaml := c.Yaml() release = yaml.Name } namespace = release if buildID != "" { namespace = fmt.Sprintf("%s-%s", namespace, buildID) } randomSuffix := util.RandomString(10) release = util.SanitizeName(fmt.Sprintf("%s-%s", release, randomSuffix), maxNameLength) namespace = util.SanitizeName(fmt.Sprintf("%s-%s", namespace, randomSuffix), maxNameLength) return } // NewChart parses the path to a chart directory and allocates a new Chart object. If chartPath is // not a valid chart directory an error is returned. func NewChart(chartPath string) (*Chart, error) { yaml, err := util.ReadChartYaml(chartPath) if err != nil { return nil, err } matches, _ := filepath.Glob(filepath.Join(chartPath, "ci", "*-values.yaml")) return &Chart{chartPath, yaml, matches}, nil } type Testing struct { config config.Configuration helm Helm kubectl Kubectl git Git linter Linter cmdExecutor CmdExecutor accountValidator AccountValidator directoryLister DirectoryLister utils Utils previousRevisionWorktree string } // TestResults holds results and overall status type TestResults struct { OverallSuccess bool TestResults []TestResult } // TestResult holds test results for a specific chart type TestResult struct { Chart *Chart Error error } // NewTesting creates a new Testing struct with the given config. func NewTesting(config config.Configuration, extraSetArgs string) (Testing, error) { procExec := exec.NewProcessExecutor(config.Debug) helmExtraArgs := strings.Fields(config.HelmExtraArgs) helmLintExtraArgs := strings.Fields(config.HelmLintExtraArgs) testing := Testing{ config: config, helm: tool.NewHelm(procExec, helmExtraArgs, helmLintExtraArgs, strings.Fields(extraSetArgs)), git: tool.NewGit(procExec), kubectl: tool.NewKubectl(procExec, config.KubectlTimeout), linter: tool.NewLinter(procExec), cmdExecutor: tool.NewCmdTemplateExecutor(procExec), accountValidator: tool.AccountValidator{}, directoryLister: util.DirectoryLister{}, utils: util.Utils{}, } versionString, err := testing.helm.Version() if err != nil { return testing, err } version, err := semver.NewVersion(versionString) if err != nil { return testing, err } if version.Major() < 3 { return testing, fmt.Errorf("minimum required Helm version is v3.0.0; found: %s", version) } return testing, nil } // computePreviousRevisionPath converts any file or directory path to the same path in the // previous revision's working tree. func (t *Testing) computePreviousRevisionPath(fileOrDirPath string) string { return filepath.Join(t.previousRevisionWorktree, fileOrDirPath) } func (t *Testing) processCharts(action func(chart *Chart) TestResult) ([]TestResult, error) { var results []TestResult // nolint: prealloc chartDirs, err := t.FindChartDirsToBeProcessed() if err != nil { return nil, fmt.Errorf("failed identifying charts to process: %w", err) } else if len(chartDirs) == 0 { return results, nil } var charts []*Chart for _, dir := range chartDirs { chart, err := NewChart(dir) if err != nil { return nil, err } if t.config.ExcludeDeprecated && chart.yaml.Deprecated { fmt.Printf("Chart %q is deprecated and will be ignored because '--exclude-deprecated' is set\n", chart.String()) } else { charts = append(charts, chart) } } if !t.config.GithubGroups { fmt.Println() util.PrintDelimiterLineToWriter(os.Stdout, "-") fmt.Println(" Charts to be processed:") util.PrintDelimiterLineToWriter(os.Stdout, "-") } else { util.GithubGroupsBegin(os.Stdout, "Charts to be processed") } for _, chart := range charts { fmt.Printf(" %s\n", chart) } if !t.config.GithubGroups { util.PrintDelimiterLineToWriter(os.Stdout, "-") fmt.Println() } else { util.GithubGroupsEnd(os.Stdout) } repoArgs := map[string][]string{} for _, repo := range t.config.HelmRepoExtraArgs { repoSlice := strings.SplitN(repo, "=", 2) name := repoSlice[0] repoExtraArgs := strings.Fields(repoSlice[1]) repoArgs[name] = repoExtraArgs } for _, repo := range t.config.ChartRepos { repoSlice := strings.SplitN(repo, "=", 2) name := repoSlice[0] url := repoSlice[1] repoExtraArgs := repoArgs[name] if err := t.helm.AddRepo(name, url, repoExtraArgs); err != nil { return nil, fmt.Errorf("failed adding repo: %s=%s: %w", name, url, err) } } testResults := TestResults{ OverallSuccess: true, TestResults: results, } // Checkout previous chart revisions and build their dependencies if t.config.Upgrade { mergeBase, err := t.computeMergeBase() if err != nil { return results, fmt.Errorf("failed identifying merge base: %w", err) } // Add worktree for the target revision worktreePath, err := os.MkdirTemp("./", "ct-previous-revision") if err != nil { return results, fmt.Errorf("could not create previous revision directory: %w", err) } t.previousRevisionWorktree = worktreePath err = t.git.AddWorktree(worktreePath, mergeBase) if err != nil { return results, fmt.Errorf("could not create worktree for previous revision: %w", err) } defer t.git.RemoveWorktree(worktreePath) // nolint: errcheck for _, chart := range charts { if err := t.helm.BuildDependenciesWithArgs(t.computePreviousRevisionPath(chart.Path()), t.config.HelmDependencyExtraArgs); err != nil { // Only print error (don't exit) if building dependencies for previous revision fails. fmt.Printf("failed building dependencies for previous revision of chart %q: %v\n", chart, err.Error()) } } } for _, chart := range charts { if err := t.helm.BuildDependenciesWithArgs(chart.Path(), t.config.HelmDependencyExtraArgs); err != nil { return nil, fmt.Errorf("failed building dependencies for chart %q: %w", chart, err) } result := action(chart) if result.Error != nil { testResults.OverallSuccess = false } results = append(results, result) } if testResults.OverallSuccess { return results, nil } return results, fmt.Errorf("failed processing charts") } // LintCharts lints charts (changed, all, specific) depending on the configuration. func (t *Testing) LintCharts() ([]TestResult, error) { return t.processCharts(t.LintChart) } // InstallCharts install charts (changed, all, specific) depending on the configuration. func (t *Testing) InstallCharts() ([]TestResult, error) { return t.processCharts(t.InstallChart) } // LintAndInstallCharts first lints and then installs charts (changed, all, specific) depending on the configuration. func (t *Testing) LintAndInstallCharts() ([]TestResult, error) { return t.processCharts(t.LintAndInstallChart) } // PrintResults writes test results to stdout. func (t *Testing) PrintResults(results []TestResult) { if !t.config.GithubGroups { fmt.Println() util.PrintDelimiterLineToWriter(os.Stdout, "-") } else { util.GithubGroupsBegin(os.Stdout, "Test Results") } if results != nil { for _, result := range results { err := result.Error if err != nil { fmt.Printf(" %s %s > %s\n", "✖︎", result.Chart, err) } else { fmt.Printf(" %s %s\n", "✔︎", result.Chart) } } } else { fmt.Println("No chart changes detected.") } if !t.config.GithubGroups { util.PrintDelimiterLineToWriter(os.Stdout, "-") } else { util.GithubGroupsEnd(os.Stdout) } } // LintChart lints the specified chart. func (t *Testing) LintChart(chart *Chart) TestResult { fmt.Printf("Linting chart %q\n", chart) result := TestResult{Chart: chart} if t.config.CheckVersionIncrement { if err := t.CheckVersionIncrement(chart); err != nil { result.Error = err return result } } chartYaml := filepath.Join(chart.Path(), "Chart.yaml") valuesYaml := filepath.Join(chart.Path(), "values.yaml") valuesFiles := chart.ValuesFilePathsForCI() if t.config.ValidateChartSchema { if err := t.linter.Yamale(chartYaml, t.config.ChartYamlSchema); err != nil { result.Error = err return result } } if t.config.ValidateYaml { yamlFiles := append([]string{chartYaml, valuesYaml}, valuesFiles...) for _, yamlFile := range yamlFiles { if err := t.linter.YamlLint(yamlFile, t.config.LintConf); err != nil { result.Error = err return result } } } if t.config.ValidateMaintainers { if err := t.ValidateMaintainers(chart); err != nil { result.Error = err return result } } for _, cmd := range t.config.AdditionalCommands { if err := t.cmdExecutor.RunCommand(cmd, chart); err != nil { result.Error = err return result } } // Lint with defaults if no values files are specified. if len(valuesFiles) == 0 { valuesFiles = append(valuesFiles, "") } for _, valuesFile := range valuesFiles { if valuesFile != "" { fmt.Printf("\nLinting chart with values file %q...\n\n", valuesFile) } if err := t.helm.LintWithValues(chart.Path(), valuesFile); err != nil { result.Error = err break } } return result } // InstallChart installs the specified chart into a new namespace, waits for resources to become ready, and eventually // uninstalls it and deletes the namespace again. func (t *Testing) InstallChart(chart *Chart) TestResult { var result TestResult if t.config.Upgrade { // Test upgrade from previous version result = t.UpgradeChart(chart) if result.Error != nil { return result } // Test upgrade of current version (related: https://github.com/helm/chart-testing/issues/19) if err := t.doUpgrade(chart, chart, true); err != nil { result.Error = err return result } } result = TestResult{Chart: chart} if err := t.doInstall(chart); err != nil { result.Error = err } return result } // UpgradeChart tests in-place upgrades of the specified chart relative to its previous revisions. If the // initial install or helm test of a previous revision of the chart fails, that release is ignored and no // error will be returned. If the latest revision of the chart introduces a potentially breaking change // according to the SemVer specification, upgrade testing will be skipped. func (t *Testing) UpgradeChart(chart *Chart) TestResult { result := TestResult{Chart: chart} breakingChangeAllowed, err := t.checkBreakingChangeAllowed(chart) if breakingChangeAllowed { if err != nil { fmt.Printf("Skipping upgrade test of %q because: %v\n", chart, err.Error()) } return result } else if err != nil { fmt.Printf("Error comparing chart versions for %q\n", chart) result.Error = err return result } if oldChart, err := NewChart(t.computePreviousRevisionPath(chart.Path())); err == nil { result.Error = t.doUpgrade(oldChart, chart, false) } return result } func (t *Testing) doInstall(chart *Chart) error { fmt.Printf("Installing chart %q...\n", chart) valuesFiles := chart.ValuesFilePathsForCI() // Test with defaults if no values files are specified. if len(valuesFiles) == 0 { valuesFiles = append(valuesFiles, "") } for _, valuesFile := range valuesFiles { if valuesFile != "" { fmt.Printf("\nInstalling chart with values file %q...\n\n", valuesFile) } // Use anonymous function. Otherwise deferred calls would pile up // and be executed in reverse order after the loop. fun := func() error { namespace, release, releaseSelector, cleanup := t.generateInstallConfig(chart) if !t.config.SkipCleanUp { defer cleanup() } if t.config.Namespace == "" { if err := t.kubectl.CreateNamespace(namespace); err != nil { return err } } if err := t.helm.InstallWithValues(chart.Path(), valuesFile, namespace, release); err != nil { return err } return t.testRelease(namespace, release, releaseSelector) } if err := fun(); err != nil { return err } } return nil } func (t *Testing) doUpgrade(oldChart, newChart *Chart, oldChartMustPass bool) error { fmt.Printf("Testing upgrades of chart %q relative to previous revision %q...\n", newChart, oldChart) valuesFiles := oldChart.ValuesFilePathsForCI() if len(valuesFiles) == 0 { valuesFiles = append(valuesFiles, "") } for _, valuesFile := range valuesFiles { if valuesFile != "" { if t.config.SkipMissingValues && !newChart.HasCIValuesFile(valuesFile) { fmt.Printf("Upgrade testing for values file %q skipped because a corresponding values file was not found in %s/ci\n", valuesFile, newChart.Path()) continue } fmt.Printf("\nInstalling chart %q with values file %q...\n\n", oldChart, valuesFile) } // Use anonymous function. Otherwise deferred calls would pile up // and be executed in reverse order after the loop. fun := func() error { namespace, release, releaseSelector, cleanup := t.generateInstallConfig(oldChart) if !t.config.SkipCleanUp { defer cleanup() } if t.config.Namespace == "" { if err := t.kubectl.CreateNamespace(namespace); err != nil { return err } } // Install previous version of chart. If installation fails, ignore this release. if err := t.helm.InstallWithValues(oldChart.Path(), valuesFile, namespace, release); err != nil { if oldChartMustPass { return err } fmt.Printf("Upgrade testing for release %q skipped because of previous revision installation error: %v\n", release, err.Error()) return nil } if err := t.testRelease(namespace, release, releaseSelector); err != nil { if oldChartMustPass { return err } fmt.Printf("Upgrade testing for release %q skipped because of previous revision testing error: %v\n", release, err.Error()) return nil } if err := t.helm.Upgrade(newChart.Path(), namespace, release); err != nil { return err } return t.testRelease(namespace, release, releaseSelector) } if err := fun(); err != nil { return err } } return nil } func (t *Testing) testRelease(namespace, release, releaseSelector string) error { if err := t.kubectl.WaitForDeployments(namespace, releaseSelector); err != nil { return err } return t.helm.Test(namespace, release) } func (t *Testing) generateInstallConfig(chart *Chart) (namespace, release, releaseSelector string, cleanup func()) { if t.config.Namespace != "" { namespace = t.config.Namespace release, _ = chart.CreateInstallParams(t.config.BuildID) releaseSelector = fmt.Sprintf("%s=%s", t.config.ReleaseLabel, release) cleanup = func() { t.PrintEventsPodDetailsAndLogs(namespace, releaseSelector) t.helm.DeleteRelease(namespace, release) } } else { release, namespace = chart.CreateInstallParams(t.config.BuildID) cleanup = func() { t.PrintEventsPodDetailsAndLogs(namespace, releaseSelector) t.helm.DeleteRelease(namespace, release) t.kubectl.DeleteNamespace(namespace) } } return } // LintAndInstallChart first lints and then installs the specified chart. func (t *Testing) LintAndInstallChart(chart *Chart) TestResult { result := t.LintChart(chart) if result.Error != nil { return result } return t.InstallChart(chart) } // FindChartDirsToBeProcessed identifies charts to be processed depending on the configuration // (changed charts, all charts, or specific charts). func (t *Testing) FindChartDirsToBeProcessed() ([]string, error) { cfg := t.config if cfg.ProcessAllCharts { return t.ReadAllChartDirectories() } else if len(cfg.Charts) > 0 { return t.config.Charts, nil } return t.ComputeChangedChartDirectories() } func (t *Testing) computeMergeBase() (string, error) { err := t.git.ValidateRepository() if err != nil { return "", errors.New("must be in a git repository") } branch := fmt.Sprintf("%s/%s", t.config.Remote, t.config.TargetBranch) if !t.git.BranchExists(branch) { return "", fmt.Errorf("targetBranch '%s' does not exist", branch) } return t.git.MergeBase(branch, t.config.Since) } // ComputeChangedChartDirectories takes the merge base of HEAD and the configured remote and target branch and computes a // slice of changed charts from that in the configured chart directories excluding those configured to be excluded. func (t *Testing) ComputeChangedChartDirectories() ([]string, error) { cfg := t.config mergeBase, err := t.computeMergeBase() if err != nil { return nil, err } allChangedChartFiles, err := t.git.ListChangedFilesInDirs(mergeBase, cfg.ChartDirs...) if err != nil { return nil, fmt.Errorf("failed creating diff: %w", err) } var changedChartDirs []string for _, file := range allChangedChartFiles { pathElements := strings.SplitN(filepath.ToSlash(file), "/", 3) if len(pathElements) < 2 || util.StringSliceContains(cfg.ExcludedCharts, pathElements[1]) { continue } dir := filepath.Dir(file) // Make sure directory is really a chart directory chartDir, err := t.utils.LookupChartDir(cfg.ChartDirs, dir) chartDirElement := strings.Split(chartDir, "/") if err == nil { if len(chartDirElement) > 1 { chartDirName := chartDirElement[len(chartDirElement)-1] if util.StringSliceContains(cfg.ExcludedCharts, chartDirName) { continue } } // Only add it if not already in the list if !util.StringSliceContains(changedChartDirs, chartDir) { changedChartDirs = append(changedChartDirs, chartDir) } } else { fmt.Fprintf(os.Stderr, "Directory %q is not a valid chart directory. Skipping...\n", dir) } } return changedChartDirs, nil } // ReadAllChartDirectories returns a slice of all charts in the configured chart directories except those // configured to be excluded. func (t *Testing) ReadAllChartDirectories() ([]string, error) { cfg := t.config var chartDirs []string for _, chartParentDir := range cfg.ChartDirs { dirs, err := t.directoryLister.ListChildDirs(chartParentDir, func(dir string) bool { _, err := t.utils.LookupChartDir(cfg.ChartDirs, dir) return err == nil && !util.StringSliceContains(cfg.ExcludedCharts, filepath.Base(dir)) }) if err != nil { return nil, fmt.Errorf("failed reading chart directories: %w", err) } chartDirs = append(chartDirs, dirs...) } return chartDirs, nil } // CheckVersionIncrement checks that the new chart version is greater than the old one using semantic version comparison. func (t *Testing) CheckVersionIncrement(chart *Chart) error { fmt.Printf("Checking chart %q for a version bump...\n", chart) oldVersion, err := t.GetOldChartVersion(chart.Path()) if err != nil { return err } if oldVersion == "" { // new chart, skip version check return nil } fmt.Println("Old chart version:", oldVersion) chartYaml := chart.Yaml() newVersion := chartYaml.Version fmt.Println("New chart version:", newVersion) result, err := util.CompareVersions(oldVersion, newVersion) if err != nil { return err } if result >= 0 { return errors.New("chart version not ok. Needs a version bump! ") } fmt.Println("Chart version ok.") return nil } func (t *Testing) checkBreakingChangeAllowed(chart *Chart) (allowed bool, err error) { oldVersion, err := t.GetOldChartVersion(chart.Path()) if err != nil { return false, err } if oldVersion == "" { // new chart, skip upgrade check return true, fmt.Errorf("chart has no previous revision") } newVersion := chart.Yaml().Version return util.BreakingChangeAllowed(oldVersion, newVersion) } // GetOldChartVersion gets the version of the old Chart.yaml file from the target branch. func (t *Testing) GetOldChartVersion(chartPath string) (string, error) { cfg := t.config chartYamlFile := filepath.Join(chartPath, "Chart.yaml") if !t.git.FileExistsOnBranch(chartYamlFile, cfg.Remote, cfg.TargetBranch) { fmt.Printf("Unable to find chart on %s. New chart detected.\n", cfg.TargetBranch) return "", nil } chartYamlContents, err := t.git.Show(chartYamlFile, cfg.Remote, cfg.TargetBranch) if err != nil { return "", fmt.Errorf("failed reading old Chart.yaml: %w", err) } chartYaml, err := util.UnmarshalChartYaml([]byte(chartYamlContents)) if err != nil { return "", fmt.Errorf("failed reading old chart version: %w", err) } return chartYaml.Version, nil } // ValidateMaintainers validates maintainers in the Chart.yaml file. Maintainer names must be valid accounts // (GitHub, Bitbucket, GitLab) names. Deprecated charts must not have maintainers. func (t *Testing) ValidateMaintainers(chart *Chart) error { fmt.Println("Validating maintainers...") chartYaml := chart.Yaml() if chartYaml.Deprecated { if len(chartYaml.Maintainers) > 0 { return errors.New("deprecated chart must not have maintainers") } return nil } if len(chartYaml.Maintainers) == 0 { return errors.New("chart doesn't have maintainers") } repoURL, err := t.git.GetURLForRemote(t.config.Remote) if err != nil { return err } for _, maintainer := range chartYaml.Maintainers { if err := t.accountValidator.Validate(repoURL, maintainer.Name); err != nil { return err } } return nil } func (t *Testing) PrintEventsPodDetailsAndLogs(namespace string, selector string) { util.PrintDelimiterLineToWriter(os.Stdout, "=") t.printDetails(namespace, "Events of namespace", ".", func(_ string) error { return t.kubectl.GetEvents(namespace) }, namespace) pods, err := t.kubectl.GetPods( "--no-headers", "--namespace", namespace, "--selector", selector, "--output", "jsonpath={.items[*].metadata.name}", ) if err != nil { fmt.Println("Error printing logs:", err) return } for _, pod := range pods { t.printDetails(pod, "Description of pod", "~", func(_ string) error { return t.kubectl.DescribePod(namespace, pod) }, pod) initContainers, err := t.kubectl.GetInitContainers(namespace, pod) if err != nil { fmt.Println("Error printing logs:", err) return } if t.config.PrintLogs { t.printDetails(pod, "Logs of init container", "-", func(item string) error { return t.kubectl.Logs(namespace, pod, item) }, initContainers...) containers, err := t.kubectl.GetContainers(namespace, pod) if err != nil { fmt.Printf("failed printing logs: %v\n", err.Error()) return } t.printDetails(pod, "Logs of container", "-", func(item string) error { return t.kubectl.Logs(namespace, pod, item) }, containers...) } } util.PrintDelimiterLineToWriter(os.Stdout, "=") } func (t *Testing) printDetails(resource string, text string, delimiterChar string, printFunc func(item string) error, items ...string) { for _, item := range items { item = strings.Trim(item, "'") if !t.config.GithubGroups { util.PrintDelimiterLineToWriter(os.Stdout, delimiterChar) fmt.Printf("==> %s %s\n", text, resource) util.PrintDelimiterLineToWriter(os.Stdout, delimiterChar) } else { util.GithubGroupsBegin(os.Stdout, fmt.Sprintf("%s %s", text, resource)) } if err := printFunc(item); err != nil { fmt.Println("Error printing details:", err) return } if !t.config.GithubGroups { util.PrintDelimiterLineToWriter(os.Stdout, delimiterChar) fmt.Printf("<== %s %s\n", text, resource) util.PrintDelimiterLineToWriter(os.Stdout, delimiterChar) } else { util.GithubGroupsEnd(os.Stdout) } } } 0707010000003E000081A40000000000000000000000016624BE4500003542000000000000000000000000000000000000002D00000000chart-testing-3.11.0/pkg/chart/chart_test.go// Copyright The Helm Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package chart import ( "fmt" "strings" "testing" "github.com/helm/chart-testing/v3/pkg/config" "github.com/helm/chart-testing/v3/pkg/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) type fakeGit struct{} func (g fakeGit) FileExistsOnBranch(file string, remote string, branch string) bool { return true } func (g fakeGit) Show(file string, remote string, branch string) (string, error) { return "", nil } func (g fakeGit) MergeBase(commit1 string, commit2 string) (string, error) { return "HEAD", nil } func (g fakeGit) ListChangedFilesInDirs(commit string, dirs ...string) ([]string, error) { return []string{ "test_charts/foo/Chart.yaml", "test_charts/bar/Chart.yaml", "test_charts/bar/bar_sub/templates/bar_sub.yaml", "test_charts/excluded/Chart.yaml", "test_chart_at_root/templates/foo.yaml", "test_chart_at_multi_level/foo/bar/Chart.yaml", "test_chart_at_multi_level/foo/baz/Chart.yaml", "test_chart_at_multi_level/foo/excluded/Chart.yaml", "some_non_chart_dir/some_non_chart_file", "some_non_chart_file", }, nil } func (g fakeGit) AddWorktree(path string, ref string) error { return nil } func (g fakeGit) RemoveWorktree(path string) error { return nil } func (g fakeGit) GetURLForRemote(remote string) (string, error) { return "git@github.com/helm/chart-testing", nil } func (g fakeGit) ValidateRepository() error { return nil } func (g fakeGit) BranchExists(branch string) bool { return true } type fakeAccountValidator struct{} func (v fakeAccountValidator) Validate(repoDomain string, account string) error { if strings.HasPrefix(account, "valid") { return nil } return fmt.Errorf("failed validating account: %s", account) } type fakeLinter struct { mock.Mock } func (l *fakeLinter) YamlLint(yamlFile, configFile string) error { l.Called(yamlFile, configFile) return nil } func (l *fakeLinter) Yamale(yamlFile, schemaFile string) error { l.Called(yamlFile, schemaFile) return nil } type fakeHelm struct { mock.Mock } func (h *fakeHelm) AddRepo(name, url string, extraArgs []string) error { return nil } func (h *fakeHelm) BuildDependencies(chart string) error { return nil } func (h *fakeHelm) BuildDependenciesWithArgs(chart string, extraArgs []string) error { h.Called(chart, extraArgs) return nil } func (h *fakeHelm) LintWithValues(chart string, valuesFile string) error { return nil } func (h *fakeHelm) InstallWithValues(chart string, valuesFile string, namespace string, release string) error { return nil } func (h *fakeHelm) Upgrade(chart string, namespace string, release string) error { return nil } func (h *fakeHelm) Test(namespace string, release string) error { return nil } func (h *fakeHelm) DeleteRelease(namespace string, release string) {} func (h *fakeHelm) Version() (string, error) { return "v3.0.0", nil } type fakeCmdExecutor struct { mock.Mock } func (c *fakeCmdExecutor) RunCommand(cmdTemplate string, data interface{}) error { c.Called(cmdTemplate, data) return nil } var ct Testing func init() { cfg := config.Configuration{ ExcludedCharts: []string{"excluded"}, ChartDirs: []string{"test_charts", "."}, } ct = newTestingMock(cfg) } func newTestingMock(cfg config.Configuration) Testing { fakeMockLinter := new(fakeLinter) return Testing{ config: cfg, directoryLister: util.DirectoryLister{}, git: fakeGit{}, utils: util.Utils{}, accountValidator: fakeAccountValidator{}, linter: fakeMockLinter, helm: new(fakeHelm), } } func TestComputeChangedChartDirectories(t *testing.T) { actual, err := ct.ComputeChangedChartDirectories() expected := []string{"test_charts/foo", "test_charts/bar", "test_chart_at_root"} for _, chart := range actual { assert.Contains(t, expected, chart) } assert.Len(t, actual, 3) assert.Nil(t, err) } func TestComputeChangedChartDirectoriesWithMultiLevelChart(t *testing.T) { cfg := config.Configuration{ ExcludedCharts: []string{"excluded"}, ChartDirs: []string{"test_chart_at_multi_level/foo"}, } ct := newTestingMock(cfg) actual, err := ct.ComputeChangedChartDirectories() expected := []string{"test_chart_at_multi_level/foo/bar", "test_chart_at_multi_level/foo/baz"} for _, chart := range actual { assert.Contains(t, expected, chart) } assert.Len(t, actual, 2) assert.Nil(t, err) } func TestReadAllChartDirectories(t *testing.T) { actual, err := ct.ReadAllChartDirectories() expected := []string{ "test_charts/foo", "test_charts/bar", "test_charts/must-pass-upgrade-install", "test_charts/mutating-deployment-selector", "test_charts/simple-deployment", "test_charts/simple-deployment-different-selector", "test_charts/mutating-sfs-volumeclaim", "test_chart_at_root", } for _, chart := range actual { assert.Contains(t, expected, chart) } assert.Len(t, actual, 8) assert.Nil(t, err) } func TestValidateMaintainers(t *testing.T) { var testDataSlice = []struct { name string chartDir string expected bool }{ {"valid", "testdata/valid_maintainers", true}, {"invalid", "testdata/invalid_maintainers", false}, {"no-maintainers", "testdata/no_maintainers", false}, {"empty-maintainers", "testdata/empty_maintainers", false}, {"valid-deprecated", "testdata/valid_maintainers_deprecated", false}, {"no-maintainers-deprecated", "testdata/no_maintainers_deprecated", true}, } for _, testData := range testDataSlice { t.Run(testData.name, func(t *testing.T) { chart, err := NewChart(testData.chartDir) assert.Nil(t, err) validationErr := ct.ValidateMaintainers(chart) assert.Equal(t, testData.expected, validationErr == nil) }) } } func TestLintChartMaintainerValidation(t *testing.T) { type testData struct { name string chartDir string expected bool } runTests := func(validate bool) { ct.config.ValidateMaintainers = validate var suffix string if validate { suffix = "with-validation" } else { suffix = "without-validation" } testCases := []testData{ {fmt.Sprintf("maintainers-%s", suffix), "testdata/valid_maintainers", true}, {fmt.Sprintf("no-maintainers-%s", suffix), "testdata/no_maintainers", !validate}, } for _, testData := range testCases { t.Run(testData.name, func(t *testing.T) { chart, err := NewChart(testData.chartDir) assert.Nil(t, err) result := ct.LintChart(chart) assert.Equal(t, testData.expected, result.Error == nil) }) } } runTests(true) runTests(false) } func TestLintChartSchemaValidation(t *testing.T) { type testData struct { name string chartDir string expected bool } runTests := func(validate bool, callsYamlLint int, callsYamale int) { fakeMockLinter := new(fakeLinter) fakeMockLinter.On("Yamale", mock.Anything, mock.Anything).Return(true) fakeMockLinter.On("YamlLint", mock.Anything, mock.Anything).Return(true) ct.linter = fakeMockLinter ct.config.ValidateChartSchema = validate ct.config.ValidateMaintainers = false ct.config.ValidateYaml = false var suffix string if validate { suffix = "with-validation" } else { suffix = "without-validation" } testCases := []testData{ {fmt.Sprintf("schema-%s", suffix), "testdata/test_lints", true}, } for _, testData := range testCases { t.Run(testData.name, func(t *testing.T) { chart, err := NewChart(testData.chartDir) assert.Nil(t, err) result := ct.LintChart(chart) assert.Equal(t, testData.expected, result.Error == nil) fakeMockLinter.AssertNumberOfCalls(t, "Yamale", callsYamale) fakeMockLinter.AssertNumberOfCalls(t, "YamlLint", callsYamlLint) }) } } runTests(true, 0, 1) runTests(false, 0, 0) } func TestLintYamlValidation(t *testing.T) { type testData struct { name string chartDir string expected bool } runTests := func(validate bool, callsYamlLint int, callsYamale int) { fakeMockLinter := new(fakeLinter) fakeMockLinter.On("Yamale", mock.Anything, mock.Anything).Return(true) fakeMockLinter.On("YamlLint", mock.Anything, mock.Anything).Return(true) ct.linter = fakeMockLinter ct.config.ValidateYaml = validate ct.config.ValidateChartSchema = false ct.config.ValidateMaintainers = false var suffix string if validate { suffix = "with-validation" } else { suffix = "without-validation" } testCases := []testData{ {fmt.Sprintf("lint-%s", suffix), "testdata/test_lints", true}, } for _, testData := range testCases { t.Run(testData.name, func(t *testing.T) { chart, err := NewChart(testData.chartDir) assert.Nil(t, err) result := ct.LintChart(chart) assert.Equal(t, testData.expected, result.Error == nil) fakeMockLinter.AssertNumberOfCalls(t, "Yamale", callsYamale) fakeMockLinter.AssertNumberOfCalls(t, "YamlLint", callsYamlLint) }) } } runTests(true, 2, 0) runTests(false, 0, 0) } func TestLintDependencyExtraArgs(t *testing.T) { chart := "testdata/test_lints" args := []string{"--skip-refresh"} fakeMockHelm := new(fakeHelm) ct.helm = fakeMockHelm ct.config.HelmDependencyExtraArgs = args ct.config.Charts = []string{chart} t.Run("lint-helm-dependency-extra-args", func(t *testing.T) { call := fakeMockHelm.On("BuildDependenciesWithArgs", chart, args).Return(nil) call.Repeatability = 1 results, err := ct.LintCharts() assert.Nil(t, err) for _, result := range results { assert.Nil(t, result.Error) } // -1 is set after Repeatability runs out assert.Equal(t, -1, call.Repeatability) }) } func TestGenerateInstallConfig(t *testing.T) { type testData struct { name string cfg config.Configuration chart *Chart } testCases := []testData{ { "custom namespace", config.Configuration{ Namespace: "default", ReleaseLabel: "app.kubernetes.io/instance", }, &Chart{ yaml: &util.ChartYaml{ Name: "bar", }, }, }, { "random namespace", config.Configuration{ ReleaseLabel: "app.kubernetes.io/instance", }, &Chart{ yaml: &util.ChartYaml{ Name: "bar", }, }, }, { "long chart name", config.Configuration{ ReleaseLabel: "app.kubernetes.io/instance", }, &Chart{ yaml: &util.ChartYaml{ Name: "test_charts/barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar", }, }, }, } for _, testData := range testCases { t.Run(testData.name, func(t *testing.T) { ct := newTestingMock(testData.cfg) namespace, release, releaseSelector, _ := ct.generateInstallConfig(testData.chart) assert.NotEqual(t, "", namespace) assert.NotEqual(t, "", release) assert.True(t, len(release) < 64, "release should be less than 64 chars") assert.True(t, len(namespace) < 64, "namespace should be less than 64 chars") if testData.cfg.Namespace != "" { assert.Equal(t, testData.cfg.Namespace, namespace) assert.Equal(t, fmt.Sprintf("%s=%s", testData.cfg.ReleaseLabel, release), releaseSelector) } else { assert.Equal(t, "", releaseSelector) assert.Contains(t, namespace, release) } }) } } func TestChart_HasCIValuesFile(t *testing.T) { type testData struct { name string chart *Chart file string expected bool } testCases := []testData{ { name: "has file", chart: &Chart{ ciValuesPaths: []string{"foo-values.yaml"}, }, file: "foo-values.yaml", expected: true, }, { name: "different paths", chart: &Chart{ ciValuesPaths: []string{"ci/foo-values.yaml"}, }, file: "foo/bar/foo-values.yaml", expected: true, }, { name: "does not have file", chart: &Chart{ ciValuesPaths: []string{"foo-values.yaml"}, }, file: "bar-values.yaml", expected: false, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { actual := tc.chart.HasCIValuesFile(tc.file) assert.Equal(t, tc.expected, actual) }) } } func TestChart_AdditionalCommandsAreRun(t *testing.T) { type testData struct { name string cfg config.Configuration callsRunCommand int } testCases := []testData{ { name: "no additional commands", cfg: config.Configuration{}, callsRunCommand: 0, }, { name: "one command", cfg: config.Configuration{ AdditionalCommands: []string{"helm unittest --helm3 -f tests/*.yaml {{ .Path }}"}, }, callsRunCommand: 1, }, { name: "multiple commands", cfg: config.Configuration{ AdditionalCommands: []string{"echo", "helm unittest --helm3 -f tests/*.yaml {{ .Path }}"}, }, callsRunCommand: 2, }, } for _, testData := range testCases { t.Run(testData.name, func(t *testing.T) { fakeCmdExecutor := new(fakeCmdExecutor) fakeCmdExecutor.On("RunCommand", mock.Anything, mock.Anything).Return(nil) ct := newTestingMock(testData.cfg) ct.cmdExecutor = fakeCmdExecutor ct.LintChart(&Chart{}) fakeCmdExecutor.AssertNumberOfCalls(t, "RunCommand", testData.callsRunCommand) }) } } 0707010000003F000081A40000000000000000000000016624BE45000011D6000000000000000000000000000000000000003300000000chart-testing-3.11.0/pkg/chart/integration_test.go// Copyright The Helm Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build integration // +build integration package chart import ( "fmt" "strings" "testing" "time" "github.com/helm/chart-testing/v3/pkg/config" "github.com/helm/chart-testing/v3/pkg/exec" "github.com/helm/chart-testing/v3/pkg/tool" "github.com/helm/chart-testing/v3/pkg/util" "github.com/stretchr/testify/assert" ) func newTestingHelmIntegration(cfg config.Configuration, extraSetArgs string) Testing { fakeMockLinter := new(fakeLinter) procExec := exec.NewProcessExecutor(true) extraArgs := strings.Fields(cfg.HelmExtraArgs) extraLintArgs := strings.Fields(cfg.HelmLintExtraArgs) return Testing{ config: cfg, directoryLister: util.DirectoryLister{}, git: fakeGit{}, utils: util.Utils{}, accountValidator: fakeAccountValidator{}, linter: fakeMockLinter, helm: tool.NewHelm(procExec, extraArgs, extraLintArgs, strings.Fields(extraSetArgs)), kubectl: tool.NewKubectl(procExec, 30*time.Second), } } func TestInstallChart(t *testing.T) { type testCase struct { name string cfg config.Configuration chartDir string output TestResult extraSet string } cases := []testCase{ { "install only in custom namespace", config.Configuration{ Debug: true, Namespace: "foobar", ReleaseLabel: "app.kubernetes.io/instance", }, "test_charts/must-pass-upgrade-install", TestResult{mustNewChart("test_charts/must-pass-upgrade-install"), nil}, "", }, { "install only in random namespace", config.Configuration{ Debug: true, }, "test_charts/must-pass-upgrade-install", TestResult{mustNewChart("test_charts/must-pass-upgrade-install"), nil}, "", }, { "install with override set", config.Configuration{ Debug: true, }, "test_charts/must-pass-upgrade-install", TestResult{mustNewChart("test_charts/must-pass-upgrade-install"), nil}, "--set=image.tag=latest", }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { ct := newTestingHelmIntegration(tc.cfg, tc.extraSet) namespace := tc.cfg.Namespace if namespace != "" { ct.kubectl.CreateNamespace(namespace) defer ct.kubectl.DeleteNamespace(namespace) } result := ct.InstallChart(mustNewChart(tc.chartDir)) if result.Error != tc.output.Error { if result.Error != nil && tc.output.Error != nil { assert.Equal(t, tc.output.Error.Error(), result.Error.Error()) } else { assert.Equal(t, tc.output.Error, result.Error) } } }) } } func TestUpgradeChart(t *testing.T) { type testCase struct { name string old string new string err error } cfg := config.Configuration{ Debug: true, Upgrade: true, } ct := newTestingHelmIntegration(cfg, "") processError := fmt.Errorf("failed waiting for process: exit status 1") cases := []testCase{ { "upgrade nginx", "test_charts/must-pass-upgrade-install", "test_charts/must-pass-upgrade-install", nil, }, { "change immutable deployment.spec.selector field", "test_charts/mutating-deployment-selector", "test_charts/mutating-deployment-selector", processError, }, { "change immutable statefulset.spec.volumeClaimTemplates field", "test_charts/mutating-sfs-volumeclaim", "test_charts/mutating-sfs-volumeclaim", processError, }, { "change immutable deployment.spec.selector.matchLabels field", "test_charts/simple-deployment", "test_charts/simple-deployment-different-selector", processError, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { err := ct.doUpgrade(mustNewChart(tc.old), mustNewChart(tc.new), true) if err != tc.err { if err != nil && tc.err != nil { assert.Equal(t, tc.err.Error(), err.Error()) } else { assert.Equal(t, tc.err, err) } } }) } } func mustNewChart(chartPath string) *Chart { c, err := NewChart(chartPath) if err != nil { panic(err) } return c } 07070100000040000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000003200000000chart-testing-3.11.0/pkg/chart/some_non_chart_dir07070100000041000081A40000000000000000000000016624BE4500000000000000000000000000000000000000000000004600000000chart-testing-3.11.0/pkg/chart/some_non_chart_dir/some_non_chart_file07070100000042000081A40000000000000000000000016624BE4500000000000000000000000000000000000000000000003300000000chart-testing-3.11.0/pkg/chart/some_non_chart_file07070100000043000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000003900000000chart-testing-3.11.0/pkg/chart/test_chart_at_multi_level07070100000044000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000003D00000000chart-testing-3.11.0/pkg/chart/test_chart_at_multi_level/foo07070100000045000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000004100000000chart-testing-3.11.0/pkg/chart/test_chart_at_multi_level/foo/bar07070100000046000081A40000000000000000000000016624BE4500000000000000000000000000000000000000000000004C00000000chart-testing-3.11.0/pkg/chart/test_chart_at_multi_level/foo/bar/Chart.yaml07070100000047000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000004100000000chart-testing-3.11.0/pkg/chart/test_chart_at_multi_level/foo/baz07070100000048000081A40000000000000000000000016624BE4500000000000000000000000000000000000000000000004C00000000chart-testing-3.11.0/pkg/chart/test_chart_at_multi_level/foo/baz/Chart.yaml07070100000049000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000004600000000chart-testing-3.11.0/pkg/chart/test_chart_at_multi_level/foo/excluded0707010000004A000081A40000000000000000000000016624BE4500000000000000000000000000000000000000000000005100000000chart-testing-3.11.0/pkg/chart/test_chart_at_multi_level/foo/excluded/Chart.yaml0707010000004B000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000003200000000chart-testing-3.11.0/pkg/chart/test_chart_at_root0707010000004C000081A40000000000000000000000016624BE4500000000000000000000000000000000000000000000003D00000000chart-testing-3.11.0/pkg/chart/test_chart_at_root/Chart.yaml0707010000004D000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000003C00000000chart-testing-3.11.0/pkg/chart/test_chart_at_root/templates0707010000004E000081A40000000000000000000000016624BE4500000000000000000000000000000000000000000000004500000000chart-testing-3.11.0/pkg/chart/test_chart_at_root/templates/foo.yaml0707010000004F000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000002B00000000chart-testing-3.11.0/pkg/chart/test_charts07070100000050000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000002F00000000chart-testing-3.11.0/pkg/chart/test_charts/bar07070100000051000081A40000000000000000000000016624BE4500000000000000000000000000000000000000000000003A00000000chart-testing-3.11.0/pkg/chart/test_charts/bar/Chart.yaml07070100000052000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000003700000000chart-testing-3.11.0/pkg/chart/test_charts/bar/bar_sub07070100000053000081A40000000000000000000000016624BE4500000000000000000000000000000000000000000000004200000000chart-testing-3.11.0/pkg/chart/test_charts/bar/bar_sub/Chart.yaml07070100000054000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000004100000000chart-testing-3.11.0/pkg/chart/test_charts/bar/bar_sub/templates07070100000055000081A40000000000000000000000016624BE4500000000000000000000000000000000000000000000004E00000000chart-testing-3.11.0/pkg/chart/test_charts/bar/bar_sub/templates/bar_sub.yaml07070100000056000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000003400000000chart-testing-3.11.0/pkg/chart/test_charts/excluded07070100000057000081A40000000000000000000000016624BE4500000000000000000000000000000000000000000000003F00000000chart-testing-3.11.0/pkg/chart/test_charts/excluded/Chart.yaml07070100000058000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000002F00000000chart-testing-3.11.0/pkg/chart/test_charts/foo07070100000059000081A40000000000000000000000016624BE4500000000000000000000000000000000000000000000003A00000000chart-testing-3.11.0/pkg/chart/test_charts/foo/Chart.yaml0707010000005A000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000004500000000chart-testing-3.11.0/pkg/chart/test_charts/must-pass-upgrade-install0707010000005B000081A40000000000000000000000016624BE4500000065000000000000000000000000000000000000005000000000chart-testing-3.11.0/pkg/chart/test_charts/must-pass-upgrade-install/Chart.yamlapiVersion: v1 appVersion: "1.0" description: A Helm chart for Kubernetes name: nginx version: 0.2.0 0707010000005C000081A40000000000000000000000016624BE45000000B5000000000000000000000000000000000000004F00000000chart-testing-3.11.0/pkg/chart/test_charts/must-pass-upgrade-install/README.mdChart generated with `helm create nginx`, and is intended to pass upgrades and installs. A fake "previous revision" can be found at `./ct_prev_revision/must-pass-upgrade-install`. 0707010000005D000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000004800000000chart-testing-3.11.0/pkg/chart/test_charts/must-pass-upgrade-install/ci0707010000005E000081A40000000000000000000000016624BE4500000010000000000000000000000000000000000000005900000000chart-testing-3.11.0/pkg/chart/test_charts/must-pass-upgrade-install/ci/test-values.yamlreplicaCount: 2 0707010000005F000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000004F00000000chart-testing-3.11.0/pkg/chart/test_charts/must-pass-upgrade-install/templates07070100000060000081A40000000000000000000000016624BE450000040F000000000000000000000000000000000000005C00000000chart-testing-3.11.0/pkg/chart/test_charts/must-pass-upgrade-install/templates/_helpers.tpl{{/* vim: set filetype=mustache: */}} {{/* Expand the name of the chart. */}} {{- define "nginx.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- end -}} {{/* Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). If release name contains chart name it will be used as a full name. */}} {{- define "nginx.fullname" -}} {{- if .Values.fullnameOverride -}} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} {{- else -}} {{- $name := default .Chart.Name .Values.nameOverride -}} {{- if contains $name .Release.Name -}} {{- .Release.Name | trunc 63 | trimSuffix "-" -}} {{- else -}} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} {{- end -}} {{- end -}} {{- end -}} {{/* Create chart name and version as used by the chart label. */}} {{- define "nginx.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} {{- end -}} 07070100000061000081A40000000000000000000000016624BE4500000600000000000000000000000000000000000000005F00000000chart-testing-3.11.0/pkg/chart/test_charts/must-pass-upgrade-install/templates/deployment.yamlapiVersion: apps/v1 kind: Deployment metadata: name: {{ include "nginx.fullname" . }} labels: app.kubernetes.io/name: {{ include "nginx.name" . }} helm.sh/chart: {{ include "nginx.chart" . }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: app.kubernetes.io/name: {{ include "nginx.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} template: metadata: labels: app.kubernetes.io/name: {{ include "nginx.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: 80 protocol: TCP livenessProbe: httpGet: path: / port: http readinessProbe: httpGet: path: / port: http resources: {{- toYaml .Values.resources | nindent 12 }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }} 07070100000062000081A40000000000000000000000016624BE450000023F000000000000000000000000000000000000005C00000000chart-testing-3.11.0/pkg/chart/test_charts/must-pass-upgrade-install/templates/service.yamlapiVersion: v1 kind: Service metadata: name: {{ include "nginx.fullname" . }} labels: app.kubernetes.io/name: {{ include "nginx.name" . }} helm.sh/chart: {{ include "nginx.chart" . }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} targetPort: http protocol: TCP name: http selector: app.kubernetes.io/name: {{ include "nginx.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} 07070100000063000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000005500000000chart-testing-3.11.0/pkg/chart/test_charts/must-pass-upgrade-install/templates/tests07070100000064000081A40000000000000000000000016624BE4500000225000000000000000000000000000000000000006A00000000chart-testing-3.11.0/pkg/chart/test_charts/must-pass-upgrade-install/templates/tests/test-connection.yamlapiVersion: v1 kind: Pod metadata: name: "{{ include "nginx.fullname" . }}-test-connection" labels: app.kubernetes.io/name: {{ include "nginx.name" . }} helm.sh/chart: {{ include "nginx.chart" . }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} annotations: "helm.sh/hook": test-success spec: containers: - name: wget image: busybox command: ['wget'] args: ['{{ include "nginx.fullname" . }}:{{ .Values.service.port }}'] restartPolicy: Never 07070100000065000081A40000000000000000000000016624BE450000031F000000000000000000000000000000000000005100000000chart-testing-3.11.0/pkg/chart/test_charts/must-pass-upgrade-install/values.yaml# Default values for nginx. # This is a YAML-formatted file. # Declare variables to be passed into your templates. replicaCount: 1 image: repository: nginx tag: stable pullPolicy: IfNotPresent nameOverride: "" fullnameOverride: "" service: type: ClusterIP port: 80 resources: {} # We usually recommend not to specify default resources and to leave this as a conscious # choice for the user. This also increases chances charts run on environments with little # resources, such as Minikube. If you do want to specify resources, uncomment the following # lines, adjust them as necessary, and remove the curly braces after 'resources:'. # limits: # cpu: 100m # memory: 128Mi # requests: # cpu: 100m # memory: 128Mi nodeSelector: {} tolerations: [] affinity: {} 07070100000066000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000004800000000chart-testing-3.11.0/pkg/chart/test_charts/mutating-deployment-selector07070100000067000081A40000000000000000000000016624BE4500000065000000000000000000000000000000000000005300000000chart-testing-3.11.0/pkg/chart/test_charts/mutating-deployment-selector/Chart.yamlapiVersion: v1 appVersion: "1.0" description: A Helm chart for Kubernetes name: nginx version: 0.1.0 07070100000068000081A40000000000000000000000016624BE4500000036000000000000000000000000000000000000005200000000chart-testing-3.11.0/pkg/chart/test_charts/mutating-deployment-selector/README.mdReproduces https://github.com/helm/charts/issues/7726 07070100000069000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000005200000000chart-testing-3.11.0/pkg/chart/test_charts/mutating-deployment-selector/templates0707010000006A000081A40000000000000000000000016624BE450000040F000000000000000000000000000000000000005F00000000chart-testing-3.11.0/pkg/chart/test_charts/mutating-deployment-selector/templates/_helpers.tpl{{/* vim: set filetype=mustache: */}} {{/* Expand the name of the chart. */}} {{- define "nginx.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- end -}} {{/* Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). If release name contains chart name it will be used as a full name. */}} {{- define "nginx.fullname" -}} {{- if .Values.fullnameOverride -}} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} {{- else -}} {{- $name := default .Chart.Name .Values.nameOverride -}} {{- if contains $name .Release.Name -}} {{- .Release.Name | trunc 63 | trimSuffix "-" -}} {{- else -}} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} {{- end -}} {{- end -}} {{- end -}} {{/* Create chart name and version as used by the chart label. */}} {{- define "nginx.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} {{- end -}} 0707010000006B000081A40000000000000000000000016624BE4500000798000000000000000000000000000000000000006200000000chart-testing-3.11.0/pkg/chart/test_charts/mutating-deployment-selector/templates/deployment.yamlapiVersion: apps/v1 kind: Deployment metadata: name: {{ include "nginx.fullname" . }} labels: app.kubernetes.io/name: {{ include "nginx.name" . }} helm.sh/chart: {{ include "nginx.chart" . }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} # Revision should change with each helm upgrade, so if we use it here # and don't specify spec.selector, we run into https://github.com/helm/charts/issues/7726 revision: {{ .Release.Revision | quote }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: app.kubernetes.io/name: {{ include "nginx.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} revision: {{ .Release.Revision | quote }} template: metadata: labels: app.kubernetes.io/name: {{ include "nginx.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} # This should change with each helm upgrade, so we shouldn't be using it as a selector! revision: {{ .Release.Revision | quote }} spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: 80 protocol: TCP livenessProbe: httpGet: path: / port: http readinessProbe: httpGet: path: / port: http resources: {{- toYaml .Values.resources | nindent 12 }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }} 0707010000006C000081A40000000000000000000000016624BE450000023F000000000000000000000000000000000000005F00000000chart-testing-3.11.0/pkg/chart/test_charts/mutating-deployment-selector/templates/service.yamlapiVersion: v1 kind: Service metadata: name: {{ include "nginx.fullname" . }} labels: app.kubernetes.io/name: {{ include "nginx.name" . }} helm.sh/chart: {{ include "nginx.chart" . }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} targetPort: http protocol: TCP name: http selector: app.kubernetes.io/name: {{ include "nginx.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} 0707010000006D000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000005800000000chart-testing-3.11.0/pkg/chart/test_charts/mutating-deployment-selector/templates/tests0707010000006E000081A40000000000000000000000016624BE4500000225000000000000000000000000000000000000006D00000000chart-testing-3.11.0/pkg/chart/test_charts/mutating-deployment-selector/templates/tests/test-connection.yamlapiVersion: v1 kind: Pod metadata: name: "{{ include "nginx.fullname" . }}-test-connection" labels: app.kubernetes.io/name: {{ include "nginx.name" . }} helm.sh/chart: {{ include "nginx.chart" . }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} annotations: "helm.sh/hook": test-success spec: containers: - name: wget image: busybox command: ['wget'] args: ['{{ include "nginx.fullname" . }}:{{ .Values.service.port }}'] restartPolicy: Never 0707010000006F000081A40000000000000000000000016624BE450000031F000000000000000000000000000000000000005400000000chart-testing-3.11.0/pkg/chart/test_charts/mutating-deployment-selector/values.yaml# Default values for nginx. # This is a YAML-formatted file. # Declare variables to be passed into your templates. replicaCount: 1 image: repository: nginx tag: stable pullPolicy: IfNotPresent nameOverride: "" fullnameOverride: "" service: type: ClusterIP port: 80 resources: {} # We usually recommend not to specify default resources and to leave this as a conscious # choice for the user. This also increases chances charts run on environments with little # resources, such as Minikube. If you do want to specify resources, uncomment the following # lines, adjust them as necessary, and remove the curly braces after 'resources:'. # limits: # cpu: 100m # memory: 128Mi # requests: # cpu: 100m # memory: 128Mi nodeSelector: {} tolerations: [] affinity: {} 07070100000070000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000004400000000chart-testing-3.11.0/pkg/chart/test_charts/mutating-sfs-volumeclaim07070100000071000081A40000000000000000000000016624BE4500000065000000000000000000000000000000000000004F00000000chart-testing-3.11.0/pkg/chart/test_charts/mutating-sfs-volumeclaim/Chart.yamlapiVersion: v1 appVersion: "1.0" description: A Helm chart for Kubernetes name: nginx version: 0.1.0 07070100000072000081A40000000000000000000000016624BE4500000036000000000000000000000000000000000000004E00000000chart-testing-3.11.0/pkg/chart/test_charts/mutating-sfs-volumeclaim/README.mdReproduces https://github.com/helm/charts/issues/7803 07070100000073000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000004E00000000chart-testing-3.11.0/pkg/chart/test_charts/mutating-sfs-volumeclaim/templates07070100000074000081A40000000000000000000000016624BE450000040F000000000000000000000000000000000000005B00000000chart-testing-3.11.0/pkg/chart/test_charts/mutating-sfs-volumeclaim/templates/_helpers.tpl{{/* vim: set filetype=mustache: */}} {{/* Expand the name of the chart. */}} {{- define "nginx.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- end -}} {{/* Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). If release name contains chart name it will be used as a full name. */}} {{- define "nginx.fullname" -}} {{- if .Values.fullnameOverride -}} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} {{- else -}} {{- $name := default .Chart.Name .Values.nameOverride -}} {{- if contains $name .Release.Name -}} {{- .Release.Name | trunc 63 | trimSuffix "-" -}} {{- else -}} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} {{- end -}} {{- end -}} {{- end -}} {{/* Create chart name and version as used by the chart label. */}} {{- define "nginx.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} {{- end -}} 07070100000075000081A40000000000000000000000016624BE4500000251000000000000000000000000000000000000005B00000000chart-testing-3.11.0/pkg/chart/test_charts/mutating-sfs-volumeclaim/templates/service.yamlapiVersion: v1 kind: Service metadata: name: {{ include "nginx.fullname" . }} labels: app.kubernetes.io/name: {{ include "nginx.name" . }} helm.sh/chart: {{ include "nginx.chart" . }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} spec: type: {{ .Values.service.type }} clusterIP: None ports: - port: {{ .Values.service.port }} targetPort: http protocol: TCP name: http selector: app.kubernetes.io/name: {{ include "nginx.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} 07070100000076000081A40000000000000000000000016624BE45000008AB000000000000000000000000000000000000005F00000000chart-testing-3.11.0/pkg/chart/test_charts/mutating-sfs-volumeclaim/templates/statefulset.yamlapiVersion: apps/v1 kind: StatefulSet metadata: name: {{ include "nginx.fullname" . }} labels: app.kubernetes.io/name: {{ include "nginx.name" . }} helm.sh/chart: {{ include "nginx.chart" . }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} spec: replicas: {{ .Values.replicaCount }} serviceName: {{ include "nginx.fullname" . }} selector: matchLabels: app.kubernetes.io/name: {{ include "nginx.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} volumeClaimTemplates: - metadata: name: data labels: app.kubernetes.io/name: {{ include "nginx.name" . }} helm.sh/chart: {{ include "nginx.chart" . }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} # Revision should change with each helm upgrade, so if we use it here # and don't specify spec.selector, we run into https://github.com/helm/charts/issues/7726 revision: {{ .Release.Revision | quote }} spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 50Mi template: metadata: labels: app.kubernetes.io/name: {{ include "nginx.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: 80 protocol: TCP livenessProbe: httpGet: path: / port: http readinessProbe: httpGet: path: / port: http resources: {{- toYaml .Values.resources | nindent 12 }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }} 07070100000077000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000005400000000chart-testing-3.11.0/pkg/chart/test_charts/mutating-sfs-volumeclaim/templates/tests07070100000078000081A40000000000000000000000016624BE4500000225000000000000000000000000000000000000006900000000chart-testing-3.11.0/pkg/chart/test_charts/mutating-sfs-volumeclaim/templates/tests/test-connection.yamlapiVersion: v1 kind: Pod metadata: name: "{{ include "nginx.fullname" . }}-test-connection" labels: app.kubernetes.io/name: {{ include "nginx.name" . }} helm.sh/chart: {{ include "nginx.chart" . }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} annotations: "helm.sh/hook": test-success spec: containers: - name: wget image: busybox command: ['wget'] args: ['{{ include "nginx.fullname" . }}:{{ .Values.service.port }}'] restartPolicy: Never 07070100000079000081A40000000000000000000000016624BE450000031F000000000000000000000000000000000000005000000000chart-testing-3.11.0/pkg/chart/test_charts/mutating-sfs-volumeclaim/values.yaml# Default values for nginx. # This is a YAML-formatted file. # Declare variables to be passed into your templates. replicaCount: 1 image: repository: nginx tag: stable pullPolicy: IfNotPresent nameOverride: "" fullnameOverride: "" service: type: ClusterIP port: 80 resources: {} # We usually recommend not to specify default resources and to leave this as a conscious # choice for the user. This also increases chances charts run on environments with little # resources, such as Minikube. If you do want to specify resources, uncomment the following # lines, adjust them as necessary, and remove the curly braces after 'resources:'. # limits: # cpu: 100m # memory: 128Mi # requests: # cpu: 100m # memory: 128Mi nodeSelector: {} tolerations: [] affinity: {} 0707010000007A000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000003D00000000chart-testing-3.11.0/pkg/chart/test_charts/simple-deployment0707010000007B000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000005000000000chart-testing-3.11.0/pkg/chart/test_charts/simple-deployment-different-selector0707010000007C000081A40000000000000000000000016624BE4500000065000000000000000000000000000000000000005B00000000chart-testing-3.11.0/pkg/chart/test_charts/simple-deployment-different-selector/Chart.yamlapiVersion: v1 appVersion: "1.0" description: A Helm chart for Kubernetes name: nginx version: 0.1.0 0707010000007D000081A40000000000000000000000016624BE45000000CB000000000000000000000000000000000000005A00000000chart-testing-3.11.0/pkg/chart/test_charts/simple-deployment-different-selector/README.mdSimple chart with a Deployment having a different selector. The integration test will install first simple-deployment and then try to upgrade to simple-deployment-different-selector failing as expected 0707010000007E000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000005A00000000chart-testing-3.11.0/pkg/chart/test_charts/simple-deployment-different-selector/templates0707010000007F000081A40000000000000000000000016624BE450000040F000000000000000000000000000000000000006700000000chart-testing-3.11.0/pkg/chart/test_charts/simple-deployment-different-selector/templates/_helpers.tpl{{/* vim: set filetype=mustache: */}} {{/* Expand the name of the chart. */}} {{- define "nginx.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- end -}} {{/* Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). If release name contains chart name it will be used as a full name. */}} {{- define "nginx.fullname" -}} {{- if .Values.fullnameOverride -}} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} {{- else -}} {{- $name := default .Chart.Name .Values.nameOverride -}} {{- if contains $name .Release.Name -}} {{- .Release.Name | trunc 63 | trimSuffix "-" -}} {{- else -}} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} {{- end -}} {{- end -}} {{- end -}} {{/* Create chart name and version as used by the chart label. */}} {{- define "nginx.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} {{- end -}} 07070100000080000081A40000000000000000000000016624BE45000003C8000000000000000000000000000000000000006A00000000chart-testing-3.11.0/pkg/chart/test_charts/simple-deployment-different-selector/templates/deployment.yamlapiVersion: apps/v1 kind: Deployment metadata: name: {{ include "nginx.fullname" . }} labels: app.kubernetes.io/name: {{ include "nginx.name" . }} helm.sh/chart: {{ include "nginx.chart" . }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} spec: replicas: 1 selector: matchLabels: app.kubernetes.io/name: {{ include "nginx.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} extra: label template: metadata: labels: app.kubernetes.io/name: {{ include "nginx.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} extra: label spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: 80 protocol: TCP 07070100000081000081A40000000000000000000000016624BE45000000DF000000000000000000000000000000000000005C00000000chart-testing-3.11.0/pkg/chart/test_charts/simple-deployment-different-selector/values.yaml# Default values for nginx. # This is a YAML-formatted file. # Declare variables to be passed into your templates. image: repository: nginx tag: stable pullPolicy: IfNotPresent nameOverride: "" fullnameOverride: "" 07070100000082000081A40000000000000000000000016624BE4500000065000000000000000000000000000000000000004800000000chart-testing-3.11.0/pkg/chart/test_charts/simple-deployment/Chart.yamlapiVersion: v1 appVersion: "1.0" description: A Helm chart for Kubernetes name: nginx version: 0.1.0 07070100000083000081A40000000000000000000000016624BE45000000AF000000000000000000000000000000000000004700000000chart-testing-3.11.0/pkg/chart/test_charts/simple-deployment/README.mdSimple chart with a Deployment. The integration test will install first simple-deployment and then try to upgrade to simple-deployment-different-selector failing as expected 07070100000084000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000004700000000chart-testing-3.11.0/pkg/chart/test_charts/simple-deployment/templates07070100000085000081A40000000000000000000000016624BE450000040F000000000000000000000000000000000000005400000000chart-testing-3.11.0/pkg/chart/test_charts/simple-deployment/templates/_helpers.tpl{{/* vim: set filetype=mustache: */}} {{/* Expand the name of the chart. */}} {{- define "nginx.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- end -}} {{/* Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). If release name contains chart name it will be used as a full name. */}} {{- define "nginx.fullname" -}} {{- if .Values.fullnameOverride -}} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} {{- else -}} {{- $name := default .Chart.Name .Values.nameOverride -}} {{- if contains $name .Release.Name -}} {{- .Release.Name | trunc 63 | trimSuffix "-" -}} {{- else -}} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} {{- end -}} {{- end -}} {{- end -}} {{/* Create chart name and version as used by the chart label. */}} {{- define "nginx.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} {{- end -}} 07070100000086000081A40000000000000000000000016624BE45000003A0000000000000000000000000000000000000005700000000chart-testing-3.11.0/pkg/chart/test_charts/simple-deployment/templates/deployment.yamlapiVersion: apps/v1 kind: Deployment metadata: name: {{ include "nginx.fullname" . }} labels: app.kubernetes.io/name: {{ include "nginx.name" . }} helm.sh/chart: {{ include "nginx.chart" . }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} spec: replicas: 1 selector: matchLabels: app.kubernetes.io/name: {{ include "nginx.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} template: metadata: labels: app.kubernetes.io/name: {{ include "nginx.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: 80 protocol: TCP 07070100000087000081A40000000000000000000000016624BE45000000DF000000000000000000000000000000000000004900000000chart-testing-3.11.0/pkg/chart/test_charts/simple-deployment/values.yaml# Default values for nginx. # This is a YAML-formatted file. # Declare variables to be passed into your templates. image: repository: nginx tag: stable pullPolicy: IfNotPresent nameOverride: "" fullnameOverride: "" 07070100000088000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000002800000000chart-testing-3.11.0/pkg/chart/testdata07070100000089000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000003A00000000chart-testing-3.11.0/pkg/chart/testdata/empty_maintainers0707010000008A000081A40000000000000000000000016624BE4500000010000000000000000000000000000000000000004500000000chart-testing-3.11.0/pkg/chart/testdata/empty_maintainers/Chart.yamlmaintainers: [] 0707010000008B000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000003C00000000chart-testing-3.11.0/pkg/chart/testdata/invalid_maintainers0707010000008C000081A40000000000000000000000016624BE450000006B000000000000000000000000000000000000004700000000chart-testing-3.11.0/pkg/chart/testdata/invalid_maintainers/Chart.yamlmaintainers: - name: invalid email: invalid@example.com - name: valid email: valid@example.com 0707010000008D000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000003700000000chart-testing-3.11.0/pkg/chart/testdata/no_maintainers0707010000008E000081A40000000000000000000000016624BE4500000000000000000000000000000000000000000000004200000000chart-testing-3.11.0/pkg/chart/testdata/no_maintainers/Chart.yaml0707010000008F000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000004200000000chart-testing-3.11.0/pkg/chart/testdata/no_maintainers_deprecated07070100000090000081A40000000000000000000000016624BE4500000011000000000000000000000000000000000000004D00000000chart-testing-3.11.0/pkg/chart/testdata/no_maintainers_deprecated/Chart.yamldeprecated: true 07070100000091000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000003300000000chart-testing-3.11.0/pkg/chart/testdata/test_lints07070100000092000081A40000000000000000000000016624BE45000000CE000000000000000000000000000000000000003E00000000chart-testing-3.11.0/pkg/chart/testdata/test_lints/Chart.yamlapiVersion: v1 appVersion: xoxo description: A Helm chart for testing name: invalid version: 1.2.3 home: https://github.com/helm/chart-testing maintainers: - name: invalid - email: invalid@example.com 07070100000093000081A40000000000000000000000016624BE4500000018000000000000000000000000000000000000003F00000000chart-testing-3.11.0/pkg/chart/testdata/test_lints/values.yamltest: abc list: - abc 07070100000094000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000003A00000000chart-testing-3.11.0/pkg/chart/testdata/valid_maintainers07070100000095000081A40000000000000000000000016624BE450000006F000000000000000000000000000000000000004500000000chart-testing-3.11.0/pkg/chart/testdata/valid_maintainers/Chart.yamlmaintainers: - name: valid email: valid@example.com - name: valid-too email: valid-too@example.com 07070100000096000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000004500000000chart-testing-3.11.0/pkg/chart/testdata/valid_maintainers_deprecated07070100000097000081A40000000000000000000000016624BE4500000080000000000000000000000000000000000000005000000000chart-testing-3.11.0/pkg/chart/testdata/valid_maintainers_deprecated/Chart.yamldeprecated: true maintainers: - name: valid email: valid@example.com - name: valid-too email: valid-too@example.com 07070100000098000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000002000000000chart-testing-3.11.0/pkg/config07070100000099000081A40000000000000000000000016624BE4500001C2F000000000000000000000000000000000000002A00000000chart-testing-3.11.0/pkg/config/config.go// Copyright The Helm Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package config import ( "errors" "fmt" "os" "path/filepath" "reflect" "strings" "time" "github.com/mitchellh/go-homedir" "github.com/helm/chart-testing/v3/pkg/util" "github.com/spf13/cobra" flag "github.com/spf13/pflag" "github.com/spf13/viper" ) var ( homeDir, _ = homedir.Dir() configSearchLocations = []string{ ".", ".ct", filepath.Join(homeDir, ".ct"), "/usr/local/etc/ct", "/etc/ct", } ) type Configuration struct { Remote string `mapstructure:"remote"` TargetBranch string `mapstructure:"target-branch"` Since string `mapstructure:"since"` BuildID string `mapstructure:"build-id"` LintConf string `mapstructure:"lint-conf"` ChartYamlSchema string `mapstructure:"chart-yaml-schema"` ValidateMaintainers bool `mapstructure:"validate-maintainers"` ValidateChartSchema bool `mapstructure:"validate-chart-schema"` ValidateYaml bool `mapstructure:"validate-yaml"` AdditionalCommands []string `mapstructure:"additional-commands"` CheckVersionIncrement bool `mapstructure:"check-version-increment"` ProcessAllCharts bool `mapstructure:"all"` Charts []string `mapstructure:"charts"` ChartRepos []string `mapstructure:"chart-repos"` ChartDirs []string `mapstructure:"chart-dirs"` ExcludedCharts []string `mapstructure:"excluded-charts"` HelmExtraArgs string `mapstructure:"helm-extra-args"` HelmLintExtraArgs string `mapstructure:"helm-lint-extra-args"` HelmRepoExtraArgs []string `mapstructure:"helm-repo-extra-args"` HelmDependencyExtraArgs []string `mapstructure:"helm-dependency-extra-args"` Debug bool `mapstructure:"debug"` Upgrade bool `mapstructure:"upgrade"` SkipMissingValues bool `mapstructure:"skip-missing-values"` SkipCleanUp bool `mapstructure:"skip-clean-up"` Namespace string `mapstructure:"namespace"` ReleaseLabel string `mapstructure:"release-label"` ExcludeDeprecated bool `mapstructure:"exclude-deprecated"` KubectlTimeout time.Duration `mapstructure:"kubectl-timeout"` PrintLogs bool `mapstructure:"print-logs"` GithubGroups bool `mapstructure:"github-groups"` } func LoadConfiguration(cfgFile string, cmd *cobra.Command, printConfig bool) (*Configuration, error) { v := viper.New() v.SetDefault("kubectl-timeout", 30*time.Second) v.SetDefault("print-logs", bool(true)) cmd.Flags().VisitAll(func(flag *flag.Flag) { flagName := flag.Name if flagName != "config" && flagName != "help" { if err := v.BindPFlag(flagName, flag); err != nil { // can't really happen panic(fmt.Sprintf("failed binding flag %q: %v\n", flagName, err.Error())) } } }) v.AutomaticEnv() v.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) v.SetEnvPrefix("CT") if cfgFile != "" { v.SetConfigFile(cfgFile) } else { v.SetConfigName("ct") if cfgFile, ok := os.LookupEnv("CT_CONFIG_DIR"); ok { v.AddConfigPath(cfgFile) } else { for _, searchLocation := range configSearchLocations { v.AddConfigPath(searchLocation) } } } if err := v.ReadInConfig(); err != nil { if cfgFile != "" { // Only error out for specified config file. Ignore for default locations. return nil, fmt.Errorf("failed loading config file: %w", err) } } else { if printConfig { fmt.Fprintln(os.Stderr, "Using config file:", v.ConfigFileUsed()) } } isLint := strings.Contains(cmd.Use, "lint") isInstall := strings.Contains(cmd.Use, "install") cfg := &Configuration{} if err := v.Unmarshal(cfg); err != nil { return nil, fmt.Errorf("failed unmarshaling configuration: %w", err) } if cfg.ProcessAllCharts && len(cfg.Charts) > 0 { return nil, errors.New("specifying both, '--all' and '--charts', is not allowed") } if cfg.Namespace != "" && cfg.ReleaseLabel == "" { return nil, errors.New("specifying '--namespace' without '--release-label' is not allowed") } // Disable upgrade (this does some expensive dependency building on previous revisions) // when neither "install" nor "lint-and-install" have not been specified. cfg.Upgrade = isInstall && cfg.Upgrade if (cfg.TargetBranch == "" || cfg.Remote == "") && cfg.Upgrade { return nil, errors.New("specifying '--upgrade=true' without '--target-branch' or '--remote', is not allowed") } chartYamlSchemaPath := cfg.ChartYamlSchema if chartYamlSchemaPath == "" { var err error cfgFile, err = findConfigFile("chart_schema.yaml") if err != nil && isLint && cfg.ValidateChartSchema { return nil, errors.New("'chart_schema.yaml' neither specified nor found in default locations") } cfg.ChartYamlSchema = cfgFile } lintConfPath := cfg.LintConf if lintConfPath == "" { var err error cfgFile, err = findConfigFile("lintconf.yaml") if err != nil && isLint && cfg.ValidateYaml { return nil, errors.New("'lintconf.yaml' neither specified nor found in default locations") } cfg.LintConf = cfgFile } if len(cfg.Charts) > 0 || cfg.ProcessAllCharts { fmt.Fprintln(os.Stderr, "Version increment checking disabled.") cfg.CheckVersionIncrement = false } if printConfig { printCfg(cfg) } return cfg, nil } func printCfg(cfg *Configuration) { if !cfg.GithubGroups { util.PrintDelimiterLineToWriter(os.Stderr, "-") fmt.Fprintln(os.Stderr, " Configuration") util.PrintDelimiterLineToWriter(os.Stderr, "-") } else { util.GithubGroupsBegin(os.Stderr, "Configuration") } e := reflect.ValueOf(cfg).Elem() typeOfCfg := e.Type() for i := 0; i < e.NumField(); i++ { var pattern string switch e.Field(i).Kind() { case reflect.Bool: pattern = "%s: %t\n" default: pattern = "%s: %s\n" } fmt.Fprintf(os.Stderr, pattern, typeOfCfg.Field(i).Name, e.Field(i).Interface()) } if !cfg.GithubGroups { util.PrintDelimiterLineToWriter(os.Stderr, "-") } else { util.GithubGroupsEnd(os.Stderr) } } func findConfigFile(fileName string) (string, error) { if dir, ok := os.LookupEnv("CT_CONFIG_DIR"); ok { return filepath.Join(dir, fileName), nil } for _, location := range configSearchLocations { filePath := filepath.Join(location, fileName) if util.FileExists(filePath) { return filePath, nil } } return "", fmt.Errorf("config file not found: %s", fileName) } 0707010000009A000081A40000000000000000000000016624BE4500000DD4000000000000000000000000000000000000002F00000000chart-testing-3.11.0/pkg/config/config_test.go// Copyright The Helm Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package config import ( "os" "path/filepath" "testing" "time" "github.com/spf13/cobra" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestUnmarshalYaml(t *testing.T) { loadAndAssertConfigFromFile(t, "test_config.yaml") } func TestUnmarshalJson(t *testing.T) { loadAndAssertConfigFromFile(t, "test_config.json") } func loadAndAssertConfigFromFile(t *testing.T, configFile string) { cfg, _ := LoadConfiguration(configFile, &cobra.Command{ Use: "install", }, true) require.Equal(t, "origin", cfg.Remote) require.Equal(t, "main", cfg.TargetBranch) require.Equal(t, "pr-42", cfg.BuildID) require.Equal(t, "my-lint-conf.yaml", cfg.LintConf) require.Equal(t, "my-chart-yaml-schema.yaml", cfg.ChartYamlSchema) require.Equal(t, true, cfg.ValidateMaintainers) require.Equal(t, true, cfg.ValidateChartSchema) require.Equal(t, true, cfg.ValidateYaml) require.Equal(t, true, cfg.CheckVersionIncrement) require.Equal(t, false, cfg.ProcessAllCharts) require.Equal(t, []string{"incubator=https://incubator"}, cfg.ChartRepos) require.Equal(t, []string{"incubator=--username test"}, cfg.HelmRepoExtraArgs) require.Equal(t, []string{"stable", "incubator"}, cfg.ChartDirs) require.Equal(t, []string{"common"}, cfg.ExcludedCharts) require.Equal(t, "--timeout 300s", cfg.HelmExtraArgs) require.Equal(t, "--quiet", cfg.HelmLintExtraArgs) require.Equal(t, true, cfg.Upgrade) require.Equal(t, true, cfg.SkipMissingValues) require.Equal(t, "default", cfg.Namespace) require.Equal(t, "release", cfg.ReleaseLabel) require.Equal(t, true, cfg.ExcludeDeprecated) require.Equal(t, 120*time.Second, cfg.KubectlTimeout) require.Equal(t, true, cfg.SkipCleanUp) } func Test_findConfigFile(t *testing.T) { tests := []struct { name string envVar string defaultDir string want string wantErr bool }{ { name: "without env var", defaultDir: filepath.Join("testdata", "default"), want: filepath.Join("testdata", "default", "test.yaml"), }, { name: "with env var", envVar: filepath.Join("testdata", "env"), want: filepath.Join("testdata", "env", "test.yaml"), }, { name: "with env var and default location", envVar: filepath.Join("testdata", "env"), defaultDir: filepath.Join("testdata", "default"), want: filepath.Join("testdata", "env", "test.yaml"), }, { name: "not found", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.envVar != "" { err := os.Setenv("CT_CONFIG_DIR", tt.envVar) require.NoError(t, err) t.Cleanup(func() { err := os.Unsetenv("CT_CONFIG_DIR") require.NoError(t, err) }) } configSearchLocations = []string{tt.defaultDir} got, err := findConfigFile("test.yaml") if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.Equal(t, tt.want, got) } }) } } 0707010000009B000081A40000000000000000000000016624BE45000003A1000000000000000000000000000000000000003100000000chart-testing-3.11.0/pkg/config/test_config.json{ "remote": "origin", "target-branch": "main", "since": "HEAD~1", "build-id": "pr-42", "lint-conf": "my-lint-conf.yaml", "chart-yaml-schema": "my-chart-yaml-schema.yaml", "github-instance": "https://github.com", "validate-maintainers": true, "validate-chart-schema": true, "validate-yaml": true, "check-version-increment": true, "all": false, "chart-repos": [ "incubator=https://incubator" ], "helm-repo-extra-args": [ "incubator=--username test" ], "chart-dirs": [ "stable", "incubator" ], "excluded-charts": [ "common" ], "helm-extra-args": "--timeout 300s", "helm-lint-extra-args": "--quiet", "upgrade": true, "skip-missing-values": true, "namespace": "default", "release-label": "release", "exclude-deprecated": true, "kubectl-timeout": "120s", "skip-clean-up": true } 0707010000009C000081A40000000000000000000000016624BE4500000298000000000000000000000000000000000000003100000000chart-testing-3.11.0/pkg/config/test_config.yamlremote: origin target-branch: main since: HEAD~1 build-id: pr-42 lint-conf: my-lint-conf.yaml chart-yaml-schema: my-chart-yaml-schema.yaml github-instance: https://github.com validate-maintainers: true validate-chart-schema: true validate-yaml: true check-version-increment: true all: false chart-repos: - incubator=https://incubator helm-repo-extra-args: - incubator=--username test chart-dirs: - stable - incubator excluded-charts: - common helm-extra-args: --timeout 300s helm-lint-extra-args: --quiet upgrade: true skip-missing-values: true namespace: default release-label: release exclude-deprecated: true kubectl-timeout: 120s skip-clean-up: true 0707010000009D000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000002900000000chart-testing-3.11.0/pkg/config/testdata0707010000009E000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000003100000000chart-testing-3.11.0/pkg/config/testdata/default0707010000009F000081A40000000000000000000000016624BE4500000000000000000000000000000000000000000000003B00000000chart-testing-3.11.0/pkg/config/testdata/default/test.yaml070701000000A0000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000002D00000000chart-testing-3.11.0/pkg/config/testdata/env070701000000A1000081A40000000000000000000000016624BE4500000000000000000000000000000000000000000000003700000000chart-testing-3.11.0/pkg/config/testdata/env/test.yaml070701000000A2000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000001E00000000chart-testing-3.11.0/pkg/exec070701000000A3000081A40000000000000000000000016624BE4500000FD8000000000000000000000000000000000000002600000000chart-testing-3.11.0/pkg/exec/exec.go// Copyright The Helm Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package exec import ( "bufio" "fmt" "io" "os" "os/exec" "strings" "github.com/helm/chart-testing/v3/pkg/util" ) type ProcessExecutor struct { debug bool } func NewProcessExecutor(debug bool) ProcessExecutor { return ProcessExecutor{ debug: debug, } } func (p ProcessExecutor) RunProcessAndCaptureOutput(executable string, execArgs ...interface{}) (string, error) { return p.RunProcessInDirAndCaptureOutput("", executable, execArgs) } func (p ProcessExecutor) RunProcessAndCaptureStdout(executable string, execArgs ...interface{}) (string, error) { return p.RunProcessInDirAndCaptureStdout("", executable, execArgs) } func (p ProcessExecutor) RunProcessInDirAndCaptureOutput(workingDirectory string, executable string, execArgs ...interface{}) (string, error) { cmd, err := p.CreateProcess(executable, execArgs...) if err != nil { return "", err } cmd.Dir = workingDirectory bytes, err := cmd.CombinedOutput() if err != nil { return "", fmt.Errorf("failed running process: %w", err) } return strings.TrimSpace(string(bytes)), nil } func (p ProcessExecutor) RunProcessInDirAndCaptureStdout(workingDirectory string, executable string, execArgs ...interface{}) (string, error) { cmd, err := p.CreateProcess(executable, execArgs...) if err != nil { return "", err } cmd.Dir = workingDirectory bytes, err := cmd.Output() if err != nil { return "", fmt.Errorf("failed running process: %w", err) } return strings.TrimSpace(string(bytes)), nil } func (p ProcessExecutor) RunProcess(executable string, execArgs ...interface{}) error { cmd, err := p.CreateProcess(executable, execArgs...) if err != nil { return err } outReader, err := cmd.StdoutPipe() if err != nil { return fmt.Errorf("failed getting StdoutPipe for command: %w", err) } errReader, err := cmd.StderrPipe() if err != nil { return fmt.Errorf("failed getting StderrPipe for command: %w", err) } scanner := bufio.NewScanner(io.MultiReader(outReader, errReader)) go func() { defer outReader.Close() defer errReader.Close() for scanner.Scan() { fmt.Println(scanner.Text()) } }() err = cmd.Start() if err != nil { return fmt.Errorf("failed running process: %w", err) } err = cmd.Wait() if err != nil { return fmt.Errorf("failed waiting for process: %w", err) } return nil } func (p ProcessExecutor) CreateProcess(executable string, execArgs ...interface{}) (*exec.Cmd, error) { args, err := util.Flatten(execArgs) if p.debug { fmt.Println(">>>", executable, strings.Join(args, " ")) } if err != nil { return nil, fmt.Errorf("invalid arguments supplied: %w", err) } cmd := exec.Command(executable, args...) return cmd, nil } type fn func(port int) error func (p ProcessExecutor) RunWithProxy(withProxy fn) error { randomPort, err := util.GetRandomPort() if err != nil { return fmt.Errorf("could not find a free port for running 'kubectl proxy': %w", err) } fmt.Printf("Running 'kubectl proxy' on port %d\n", randomPort) cmdProxy, err := p.CreateProcess("kubectl", "proxy", fmt.Sprintf("--port=%d", randomPort)) if err != nil { return fmt.Errorf("failed creating the 'kubectl proxy' process: %w", err) } err = cmdProxy.Start() if err != nil { return fmt.Errorf("failed starting the 'kubectl proxy' process: %w", err) } err = withProxy(randomPort) _ = cmdProxy.Process.Signal(os.Kill) if err != nil { return fmt.Errorf("failed running command with proxy: %w", err) } return nil } 070701000000A4000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000001E00000000chart-testing-3.11.0/pkg/tool070701000000A5000081A40000000000000000000000016624BE4500000620000000000000000000000000000000000000002900000000chart-testing-3.11.0/pkg/tool/account.go// Copyright The Helm Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tool import ( "fmt" "net/http" "regexp" ) type AccountValidator struct{} var repoDomainPattern = regexp.MustCompile("(?:https://(?:[^@:]+:[^@:]+@)?|git@)([^/:]+)") func (v AccountValidator) Validate(repoURL string, account string) error { domain, err := parseOutGitRepoDomain(repoURL) if err != nil { return err } url := fmt.Sprintf("https://%s/%s", domain, account) response, err := http.Head(url) // nolint: gosec if err != nil { return fmt.Errorf("failed validating maintainers: %w", err) } if response.StatusCode != 200 { return fmt.Errorf("failed validating maintainer %q: %s", account, response.Status) } return nil } func parseOutGitRepoDomain(repoURL string) (string, error) { // This works for GitHub, Bitbucket, and Gitlab submatch := repoDomainPattern.FindStringSubmatch(repoURL) if submatch == nil || len(submatch) < 2 { return "", fmt.Errorf("could not parse git repository domain for %q", repoURL) } return submatch[1], nil } 070701000000A6000081A40000000000000000000000016624BE4500000526000000000000000000000000000000000000002E00000000chart-testing-3.11.0/pkg/tool/account_test.gopackage tool import ( "fmt" "testing" "github.com/stretchr/testify/assert" ) func TestParseOutGitDomain(t *testing.T) { var testDataSlice = []struct { name string repoURL string expected string err error }{ {"GitHub SSH", "git@github.com:foo/bar", "github.com", nil}, {"GitHub HTTPS", "https://github.com/foo/bar", "github.com", nil}, {"GitHub HTTPS with username/password", "https://foo:token@github.com/foo/bar", "github.com", nil}, {"Gitlab SSH", "git@gitlab.com:foo/bar", "gitlab.com", nil}, {"Gitlab HTTPS", "https://gitlab.com/foo/bar", "gitlab.com", nil}, {"Gitlab HTTPS with username/password", "https://gitlab-ci-token:password@gitlab.com/foo/bar", "gitlab.com", nil}, {"Bitbucket SSH", "git@bitbucket.com:foo/bar", "bitbucket.com", nil}, {"Bitbucket HTTPS", "https://bitbucket.com/foo/bar", "bitbucket.com", nil}, {"Bitbucket HTTPS with username/password", "https://user:pass@bitbucket.com/foo/bar", "bitbucket.com", nil}, {"Invalid", "foo/bar", "", fmt.Errorf("could not parse git repository domain for \"foo/bar\"")}, } for _, testData := range testDataSlice { t.Run(testData.name, func(t *testing.T) { actual, err := parseOutGitRepoDomain(testData.repoURL) assert.Equal(t, err, testData.err) assert.Equal(t, testData.expected, actual) }) } } 070701000000A7000081A40000000000000000000000016624BE4500000319000000000000000000000000000000000000002D00000000chart-testing-3.11.0/pkg/tool/cmdexecutor.gopackage tool import ( "strings" "text/template" "github.com/mattn/go-shellwords" ) type ProcessExecutor interface { RunProcess(executable string, execArgs ...interface{}) error } type CmdTemplateExecutor struct { exec ProcessExecutor } func NewCmdTemplateExecutor(exec ProcessExecutor) CmdTemplateExecutor { return CmdTemplateExecutor{ exec: exec, } } func (t CmdTemplateExecutor) RunCommand(cmdTemplate string, data interface{}) error { var template = template.Must(template.New("command").Parse(cmdTemplate)) var b strings.Builder if err := template.Execute(&b, data); err != nil { return err } rendered := b.String() words, err := shellwords.Parse(rendered) if err != nil { return err } name, args := words[0], words[1:] return t.exec.RunProcess(name, args) } 070701000000A8000081A40000000000000000000000016624BE45000007AC000000000000000000000000000000000000003200000000chart-testing-3.11.0/pkg/tool/cmdexecutor_test.gopackage tool import ( "testing" "github.com/stretchr/testify/mock" ) type fakeProcessExecutor struct { mock.Mock } func (c *fakeProcessExecutor) RunProcess(executable string, execArgs ...interface{}) error { c.Called(executable, execArgs[0]) return nil } func TestCmdTemplateExecutor_RunCommand(t *testing.T) { type args struct { cmdTemplate string data interface{} } tests := []struct { name string args args wantErr bool validate func(t *testing.T, executor *fakeProcessExecutor) }{ { name: "command without arguments", args: args{ cmdTemplate: "echo", data: nil, }, validate: func(t *testing.T, executor *fakeProcessExecutor) { executor.AssertCalled(t, "RunProcess", "echo", []string{}) }, wantErr: false, }, { name: "command with args", args: args{ cmdTemplate: "echo hello world", }, validate: func(t *testing.T, executor *fakeProcessExecutor) { executor.AssertCalled(t, "RunProcess", "echo", []string{"hello", "world"}) }, wantErr: false, }, { name: "interpolate args", args: args{ cmdTemplate: "helm unittest --helm3 -f tests/*.yaml {{ .Path }}", data: map[string]string{"Path": "charts/my-chart"}, }, validate: func(t *testing.T, executor *fakeProcessExecutor) { executor.AssertCalled(t, "RunProcess", "helm", []string{"unittest", "--helm3", "-f", "tests/*.yaml", "charts/my-chart"}) }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { processExecutor := new(fakeProcessExecutor) processExecutor.On("RunProcess", mock.Anything, mock.Anything).Return(nil) templateExecutor := CmdTemplateExecutor{ exec: processExecutor, } if err := templateExecutor.RunCommand(tt.args.cmdTemplate, tt.args.data); (err != nil) != tt.wantErr { t.Errorf("RunCommand() error = %v, wantErr %v", err, tt.wantErr) } tt.validate(t, processExecutor) }) } } 070701000000A9000081A40000000000000000000000016624BE45000009D7000000000000000000000000000000000000002500000000chart-testing-3.11.0/pkg/tool/git.go// Copyright The Helm Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tool import ( "fmt" "strings" "github.com/helm/chart-testing/v3/pkg/exec" ) type Git struct { exec exec.ProcessExecutor } func NewGit(exec exec.ProcessExecutor) Git { return Git{ exec: exec, } } func (g Git) FileExistsOnBranch(file string, remote string, branch string) bool { fileSpec := fmt.Sprintf("%s/%s:%s", remote, branch, file) _, err := g.exec.RunProcessAndCaptureOutput("git", "cat-file", "-e", fileSpec) return err == nil } func (g Git) AddWorktree(path string, ref string) error { return g.exec.RunProcess("git", "worktree", "add", path, ref) } func (g Git) RemoveWorktree(path string) error { return g.exec.RunProcess("git", "worktree", "remove", path) } func (g Git) Show(file string, remote string, branch string) (string, error) { fileSpec := fmt.Sprintf("%s/%s:%s", remote, branch, file) return g.exec.RunProcessAndCaptureOutput("git", "show", fileSpec) } func (g Git) MergeBase(commit1 string, commit2 string) (string, error) { return g.exec.RunProcessAndCaptureOutput("git", "merge-base", commit1, commit2) } func (g Git) ListChangedFilesInDirs(commit string, dirs ...string) ([]string, error) { changedChartFilesString, err := g.exec.RunProcessAndCaptureOutput("git", "diff", "--find-renames", "--name-only", commit, "--", dirs) if err != nil { return nil, fmt.Errorf("failed creating diff: %w", err) } if changedChartFilesString == "" { return nil, nil } return strings.Split(changedChartFilesString, "\n"), nil } func (g Git) GetURLForRemote(remote string) (string, error) { return g.exec.RunProcessAndCaptureOutput("git", "ls-remote", "--get-url", remote) } func (g Git) ValidateRepository() error { _, err := g.exec.RunProcessAndCaptureOutput("git", "rev-parse", "--is-inside-work-tree") return err } func (g Git) BranchExists(branch string) bool { _, err := g.exec.RunProcessAndCaptureOutput("git", "rev-parse", "--verify", branch) return err == nil } 070701000000AA000081A40000000000000000000000016624BE4500000BE1000000000000000000000000000000000000002600000000chart-testing-3.11.0/pkg/tool/helm.go// Copyright The Helm Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tool import ( "fmt" "strings" "github.com/helm/chart-testing/v3/pkg/exec" ) type Helm struct { exec exec.ProcessExecutor extraArgs []string lintExtraArgs []string extraSetArgs []string } func NewHelm(exec exec.ProcessExecutor, extraArgs, lintExtraArgs, extraSetArgs []string) Helm { return Helm{ exec: exec, extraArgs: extraArgs, lintExtraArgs: lintExtraArgs, extraSetArgs: extraSetArgs, } } func (h Helm) AddRepo(name string, url string, extraArgs []string) error { const ociPrefix string = "oci://" if strings.HasPrefix(url, ociPrefix) { registryDomain := url[len(ociPrefix):] return h.exec.RunProcess("helm", "registry", "login", registryDomain, extraArgs) } return h.exec.RunProcess("helm", "repo", "add", name, url, extraArgs) } func (h Helm) BuildDependencies(chart string) error { return h.BuildDependenciesWithArgs(chart, []string{}) } func (h Helm) BuildDependenciesWithArgs(chart string, extraArgs []string) error { return h.exec.RunProcess("helm", "dependency", "build", chart, extraArgs) } func (h Helm) LintWithValues(chart string, valuesFile string) error { var values []string if valuesFile != "" { values = []string{"--values", valuesFile} } return h.exec.RunProcess("helm", "lint", chart, values, h.lintExtraArgs) } func (h Helm) InstallWithValues(chart string, valuesFile string, namespace string, release string) error { var values []string if valuesFile != "" { values = []string{"--values", valuesFile} } return h.exec.RunProcess("helm", "install", release, chart, "--namespace", namespace, "--wait", values, h.extraArgs, h.extraSetArgs) } func (h Helm) Upgrade(chart string, namespace string, release string) error { return h.exec.RunProcess("helm", "upgrade", release, chart, "--namespace", namespace, "--reuse-values", "--wait", h.extraArgs, h.extraSetArgs) } func (h Helm) Test(namespace string, release string) error { return h.exec.RunProcess("helm", "test", release, "--namespace", namespace, h.extraArgs) } func (h Helm) DeleteRelease(namespace string, release string) { fmt.Printf("Deleting release %q...\n", release) if err := h.exec.RunProcess("helm", "uninstall", release, "--namespace", namespace, h.extraArgs); err != nil { fmt.Println("Error deleting Helm release:", err) } } func (h Helm) Version() (string, error) { return h.exec.RunProcessAndCaptureStdout("helm", "version", "--template", "{{ .Version }}") } 070701000000AB000081A40000000000000000000000016624BE450000200A000000000000000000000000000000000000002900000000chart-testing-3.11.0/pkg/tool/kubectl.gopackage tool import ( "bytes" "encoding/json" "errors" "fmt" "net/http" "strings" "time" "github.com/hashicorp/go-retryablehttp" "github.com/helm/chart-testing/v3/pkg/exec" ) type Kubectl struct { exec exec.ProcessExecutor timeout time.Duration } func NewKubectl(exec exec.ProcessExecutor, timeout time.Duration) Kubectl { return Kubectl{ exec: exec, timeout: timeout, } } // CreateNamespace creates a new namespace with the given name. func (k Kubectl) CreateNamespace(namespace string) error { fmt.Printf("Creating namespace %q...\n", namespace) return k.exec.RunProcess("kubectl", fmt.Sprintf("--request-timeout=%s", k.timeout), "create", "namespace", namespace) } // DeleteNamespace deletes the specified namespace. If the namespace does not terminate within 120s, pods running in the // namespace and, eventually, the namespace itself are force-deleted. func (k Kubectl) DeleteNamespace(namespace string) { fmt.Printf("Deleting namespace %q...\n", namespace) timeoutSec := "180s" err := k.exec.RunProcess("kubectl", fmt.Sprintf("--request-timeout=%s", k.timeout), "delete", "namespace", namespace, "--timeout", timeoutSec) if err != nil { fmt.Printf("Namespace %q did not terminate after %s.\n", namespace, timeoutSec) } if k.getNamespace(namespace) { fmt.Printf("Namespace %q did not terminate after %s.\n", namespace, timeoutSec) fmt.Println("Force-deleting everything...") err = k.exec.RunProcess("kubectl", fmt.Sprintf("--request-timeout=%s", k.timeout), "delete", "all", "--namespace", namespace, "--all", "--force", "--grace-period=0") if err != nil { fmt.Printf("Error deleting everything in the namespace %v: %v", namespace, err) } // Give it some more time to be deleted by K8s time.Sleep(5 * time.Second) if k.getNamespace(namespace) { if err := k.forceNamespaceDeletion(namespace); err != nil { fmt.Println("Error force deleting namespace:", err) } } } } func (k Kubectl) forceNamespaceDeletion(namespace string) error { // Getting the namespace json to remove the finalizer cmdOutput, err := k.exec.RunProcessAndCaptureStdout("kubectl", fmt.Sprintf("--request-timeout=%s", k.timeout), "get", "namespace", namespace, "--output=json") if err != nil { fmt.Println("Error getting namespace json:", err) return err } namespaceUpdate := map[string]interface{}{} err = json.Unmarshal([]byte(cmdOutput), &namespaceUpdate) if err != nil { fmt.Println("Error in unmarshalling the payload:", err) return err } namespaceUpdate["spec"] = nil namespaceUpdateBytes, err := json.Marshal(&namespaceUpdate) if err != nil { fmt.Println("Error in marshalling the payload:", err) return err } // Remove finalizer from the namespace fun := func(port int) error { fmt.Printf("Removing finalizers from namespace %q...\n", namespace) k8sURL := fmt.Sprintf("http://127.0.0.1:%d/api/v1/namespaces/%s/finalize", port, namespace) req, err := retryablehttp.NewRequest("PUT", k8sURL, bytes.NewReader(namespaceUpdateBytes)) if err != nil { fmt.Println("Error creating the request to update the namespace:", err) return err } req.Header.Set("Content-Type", "application/json") errMsg := "Error removing finalizer from namespace" client := retryablehttp.NewClient() client.Logger = nil if resp, err := client.Do(req); err != nil { return fmt.Errorf("%s:%w", errMsg, err) } else if resp.StatusCode != http.StatusOK { return errors.New(errMsg) } return nil } err = k.exec.RunWithProxy(fun) if err != nil { return fmt.Errorf("cannot force-delete namespace %q: %w", namespace, err) } // Give it some more time to be deleted by K8s time.Sleep(5 * time.Second) // Check again _, err = k.exec.RunProcessAndCaptureOutput("kubectl", fmt.Sprintf("--request-timeout=%s", k.timeout), "get", "namespace", namespace) if err != nil { fmt.Printf("Namespace %q terminated.\n", namespace) return nil } fmt.Printf("Force-deleting namespace %q...\n", namespace) err = k.exec.RunProcess("kubectl", fmt.Sprintf("--request-timeout=%s", k.timeout), "delete", "namespace", namespace, "--force", "--grace-period=0", "--ignore-not-found=true") if err != nil { fmt.Println("Error deleting namespace:", err) return err } return nil } func (k Kubectl) WaitForDeployments(namespace string, selector string) error { output, err := k.exec.RunProcessAndCaptureStdout("kubectl", fmt.Sprintf("--request-timeout=%s", k.timeout), "get", "deployments", "--namespace", namespace, "--selector", selector, "--output", "jsonpath={.items[*].metadata.name}") if err != nil { return err } deployments := strings.Fields(output) for _, deployment := range deployments { deployment = strings.Trim(deployment, "'") err = k.exec.RunProcess("kubectl", fmt.Sprintf("--request-timeout=%s", k.timeout), "rollout", "status", "deployment", deployment, "--namespace", namespace) if err != nil { return err } // 'kubectl rollout status' does not return a non-zero exit code when rollouts fail. // We, thus, need to double-check here. // // Just after rollout, pods from the previous deployment revision may still be in a // terminating state. unavailable, err := k.exec.RunProcessAndCaptureStdout("kubectl", fmt.Sprintf("--request-timeout=%s", k.timeout), "get", "deployment", deployment, "--namespace", namespace, "--output", `jsonpath={.status.unavailableReplicas}`) if err != nil { return err } if unavailable != "" && unavailable != "0" { return fmt.Errorf("%s replicas unavailable", unavailable) } } return nil } func (k Kubectl) GetPodsforDeployment(namespace string, deployment string) ([]string, error) { jsonString, _ := k.exec.RunProcessAndCaptureStdout("kubectl", fmt.Sprintf("--request-timeout=%s", k.timeout), "get", "deployment", deployment, "--namespace", namespace, "--output=json") var deploymentMap map[string]interface{} err := json.Unmarshal([]byte(jsonString), &deploymentMap) if err != nil { return nil, err } spec := deploymentMap["spec"].(map[string]interface{}) selector := spec["selector"].(map[string]interface{}) matchLabels := selector["matchLabels"].(map[string]interface{}) var ls string for name, value := range matchLabels { if ls != "" { ls += "," } ls += fmt.Sprintf("%s=%s", name, value) } return k.GetPods("--selector", ls, "--namespace", namespace, "--output", "jsonpath={.items[*].metadata.name}") } func (k Kubectl) GetPods(args ...string) ([]string, error) { kubectlArgs := []string{"get", "pods"} kubectlArgs = append(kubectlArgs, args...) pods, err := k.exec.RunProcessAndCaptureStdout("kubectl", fmt.Sprintf("--request-timeout=%s", k.timeout), kubectlArgs) if err != nil { return nil, err } return strings.Fields(pods), nil } func (k Kubectl) GetEvents(namespace string) error { return k.exec.RunProcess("kubectl", fmt.Sprintf("--request-timeout=%s", k.timeout), "get", "events", "--output", "wide", "--namespace", namespace, "--sort-by", "lastTimestamp") } func (k Kubectl) DescribePod(namespace string, pod string) error { return k.exec.RunProcess("kubectl", fmt.Sprintf("--request-timeout=%s", k.timeout), "describe", "pod", pod, "--namespace", namespace) } func (k Kubectl) Logs(namespace string, pod string, container string) error { return k.exec.RunProcess("kubectl", fmt.Sprintf("--request-timeout=%s", k.timeout), "logs", pod, "--namespace", namespace, "--container", container) } func (k Kubectl) GetInitContainers(namespace string, pod string) ([]string, error) { return k.GetPods(pod, "--no-headers", "--namespace", namespace, "--output", "jsonpath={.spec.initContainers[*].name}") } func (k Kubectl) GetContainers(namespace string, pod string) ([]string, error) { return k.GetPods(pod, "--no-headers", "--namespace", namespace, "--output", "jsonpath={.spec.containers[*].name}") } func (k Kubectl) getNamespace(namespace string) bool { _, err := k.exec.RunProcessAndCaptureOutput("kubectl", fmt.Sprintf("--request-timeout=%s", k.timeout), "get", "namespace", namespace) if err != nil { fmt.Printf("Namespace %q terminated.\n", namespace) return false } return true } 070701000000AC000081A40000000000000000000000016624BE4500000439000000000000000000000000000000000000002800000000chart-testing-3.11.0/pkg/tool/linter.go// Copyright The Helm Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tool import "github.com/helm/chart-testing/v3/pkg/exec" type Linter struct { exec exec.ProcessExecutor } func NewLinter(exec exec.ProcessExecutor) Linter { return Linter{ exec: exec, } } func (l Linter) YamlLint(yamlFile string, configFile string) error { return l.exec.RunProcess("yamllint", "--config-file", configFile, yamlFile) } func (l Linter) Yamale(yamlFile string, schemaFile string) error { return l.exec.RunProcess("yamale", "--schema", schemaFile, yamlFile) } 070701000000AD000041ED0000000000000000000000026624BE4500000000000000000000000000000000000000000000001E00000000chart-testing-3.11.0/pkg/util070701000000AE000081A40000000000000000000000016624BE45000019D6000000000000000000000000000000000000002600000000chart-testing-3.11.0/pkg/util/util.go// Copyright The Helm Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package util import ( "errors" "fmt" "io" "io/fs" "math/rand" "net" "os" "path/filepath" "regexp" "strings" "time" "github.com/Masterminds/semver" "github.com/hashicorp/go-multierror" "gopkg.in/yaml.v2" ) const chars = "1234567890abcdefghijklmnopqrstuvwxyz" type Maintainer struct { Name string `yaml:"name"` Email string `yaml:"email"` } type ChartYaml struct { Name string `yaml:"name"` Version string `yaml:"version"` Deprecated bool `yaml:"deprecated"` Maintainers []Maintainer } func Flatten(items []interface{}) ([]string, error) { return doFlatten([]string{}, items) } func init() { rand.New(rand.NewSource(time.Now().UnixNano())) // nolint: gosec } func doFlatten(result []string, items interface{}) ([]string, error) { var err error switch v := items.(type) { case string: result = append(result, v) case []string: result = append(result, v...) case []interface{}: for _, item := range v { result, err = doFlatten(result, item) if err != nil { return nil, err } } default: return nil, fmt.Errorf("flatten does not support %T", v) } return result, err } func StringSliceContains(slice []string, s string) bool { for _, element := range slice { if s == element { return true } } return false } func FileExists(file string) bool { if _, err := os.Stat(file); err != nil { return false } return true } // RandomString string creates a random string of numbers and lower-case ascii characters with the specified length. func RandomString(length int) string { n := len(chars) bytes := make([]byte, length) for i := range bytes { bytes[i] = chars[rand.Intn(n)] // nolint: gosec } return string(bytes) } type DirectoryLister struct{} // ListChildDirs lists subdirectories of parentDir matching the test function. func (l DirectoryLister) ListChildDirs(parentDir string, test func(dir string) bool) ([]string, error) { entries, err := os.ReadDir(parentDir) if err != nil { return nil, err } fileInfos := make([]fs.FileInfo, 0, len(entries)) for _, entry := range entries { info, err := entry.Info() if err != nil { return nil, err } fileInfos = append(fileInfos, info) } var dirs []string for _, dir := range fileInfos { dirName := dir.Name() parentSlashChildDir := filepath.Join(parentDir, dirName) if test(parentSlashChildDir) { dirs = append(dirs, parentSlashChildDir) } } return dirs, nil } type Utils struct{} func (u Utils) LookupChartDir(chartDirs []string, dir string) (string, error) { for _, chartDir := range chartDirs { currentDir := dir for { chartYaml := filepath.Join(currentDir, "Chart.yaml") parent := filepath.Dir(filepath.Dir(chartYaml)) chartDir = strings.TrimRight(chartDir, "/") // remove any trailing slash from the dir // check directory has a Chart.yaml and that it is in a // direct subdirectory of a configured charts directory if FileExists(chartYaml) && (parent == chartDir) { return currentDir, nil } currentDir = filepath.Dir(currentDir) relativeDir, _ := filepath.Rel(chartDir, currentDir) joined := filepath.Join(chartDir, relativeDir) if (joined == chartDir) || strings.HasPrefix(relativeDir, "..") { break } } } return "", errors.New("no chart directory") } // ReadChartYaml attempts to parse Chart.yaml within the specified directory // and return a newly allocated ChartYaml object. If no Chart.yaml is present // or there is an error unmarshaling the file contents, an error will be returned. func ReadChartYaml(dir string) (*ChartYaml, error) { yamlBytes, err := os.ReadFile(filepath.Join(dir, "Chart.yaml")) if err != nil { return nil, fmt.Errorf("could not read 'Chart.yaml': %w", err) } return UnmarshalChartYaml(yamlBytes) } // UnmarshalChartYaml parses the yaml encoded data and returns a newly // allocated ChartYaml object. func UnmarshalChartYaml(yamlBytes []byte) (*ChartYaml, error) { chartYaml := &ChartYaml{} if err := yaml.Unmarshal(yamlBytes, chartYaml); err != nil { return nil, fmt.Errorf("could not unmarshal 'Chart.yaml': %w", err) } return chartYaml, nil } func CompareVersions(left string, right string) (int, error) { leftVersion, err := semver.NewVersion(left) if err != nil { return 0, fmt.Errorf("failed parsing semantic version: %w", err) } rightVersion, err := semver.NewVersion(right) if err != nil { return 0, fmt.Errorf("failed parsing semantic version: %w", err) } return leftVersion.Compare(rightVersion), nil } func BreakingChangeAllowed(left string, right string) (bool, error) { leftVersion, err := semver.NewVersion(left) if err != nil { return false, fmt.Errorf("failed parsing semantic version: %w", err) } rightVersion, err := semver.NewVersion(right) if err != nil { return false, fmt.Errorf("failed parsing semantic version: %w", err) } constraintOp := "^" if leftVersion.Major() == 0 { constraintOp = "~" } c, err := semver.NewConstraint(fmt.Sprintf("%s %s", constraintOp, leftVersion.String())) if err != nil { return false, fmt.Errorf("failed parsing semantic version constraint: %w", err) } minor, reasons := c.Validate(rightVersion) if len(reasons) > 0 { err = multierror.Append(err, reasons...) } return !minor, err } func PrintDelimiterLineToWriter(w io.Writer, delimiterChar string) { fmt.Fprintln(w, strings.Repeat(delimiterChar, 120)) } func GithubGroupsBegin(w io.Writer, title string) { fmt.Fprintf(w, "::group::%s\n", title) } func GithubGroupsEnd(w io.Writer) { fmt.Fprintln(w, "::endgroup::") } func SanitizeName(s string, maxLength int) string { reg := regexp.MustCompile("^[^a-zA-Z0-9]+") excess := len(s) - maxLength result := s if excess > 0 { result = s[excess:] } return reg.ReplaceAllString(result, "") } func GetRandomPort() (int, error) { listener, err := net.Listen("tcp", ":0") // nolint: gosec defer listener.Close() // nolint: staticcheck if err != nil { return 0, err } return listener.Addr().(*net.TCPAddr).Port, nil } 070701000000AF000081A40000000000000000000000016624BE4500000EBA000000000000000000000000000000000000002B00000000chart-testing-3.11.0/pkg/util/util_test.go// Copyright The Helm Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package util import ( "fmt" "strconv" "testing" "github.com/stretchr/testify/assert" ) func TestFlatten(t *testing.T) { var testDataSlice = []struct { input []interface{} expected []string }{ {[]interface{}{"foo", "bar", []string{"bla", "blubb"}}, []string{"foo", "bar", "bla", "blubb"}}, {[]interface{}{"foo", "bar", "bla", "blubb"}, []string{"foo", "bar", "bla", "blubb"}}, {[]interface{}{"foo", "bar", []interface{}{"bla", []string{"blubb"}}}, []string{"foo", "bar", "bla", "blubb"}}, {[]interface{}{"foo", 42, []interface{}{"bla", []string{"blubb"}}}, nil}, } for index, testData := range testDataSlice { t.Run(strconv.Itoa(index), func(t *testing.T) { actual, err := Flatten(testData.input) assert.Equal(t, testData.expected, actual) if testData.expected != nil { assert.Nil(t, err) } else { assert.NotNil(t, err) } }) } } func TestCompareVersions(t *testing.T) { var testDataSlice = []struct { oldVersion string newVersion string expected int }{ {"1.2.3", "1.2.4+2", -1}, {"1+foo", "1+bar", 0}, {"1.4-beta", "1.3", 1}, {"1.3-beta", "1.3", -1}, {"1", "2", -1}, {"3", "3", 0}, {"3-alpha", "3-beta", -1}, } for index, testData := range testDataSlice { t.Run(strconv.Itoa(index), func(t *testing.T) { actual, _ := CompareVersions(testData.oldVersion, testData.newVersion) assert.Equal(t, testData.expected, actual) }) } } func TestSanitizeName(t *testing.T) { var testDataSlice = []struct { input string maxLength int expected string }{ {"way-shorter-than-max-length", 63, "way-shorter-than-max-length"}, {"max-length", len("max-length"), "max-length"}, {"way-longer-than-max-length", 10, "max-length"}, {"one-shorter-than-max-length", len("one-shorter-than-max-length") + 1, "one-shorter-than-max-length"}, {"oone-longer-than-max-length", len("oone-longer-than-max-length") - 1, "one-longer-than-max-length"}, {"foo-would-start-with-hyphen-after-trimming", len("foo-would-start-with-hyphen-after-trimming") - 3, "would-start-with-hyphen-after-trimming"}, } for index, testData := range testDataSlice { t.Run(strconv.Itoa(index), func(t *testing.T) { actual := SanitizeName(testData.input, testData.maxLength) fmt.Printf("actual: %s,%d, input: %s,%d\n", actual, len(actual), testData.input, testData.maxLength) assert.Equal(t, testData.expected, actual) }) } } func TestBreakingChangeAllowed(t *testing.T) { var testDataSlice = []struct { left string right string breaking bool }{ {"0.1.0", "0.1.0", false}, {"0.1.0", "0.1.1", false}, {"0.1.0", "0.2.0", true}, {"0.1.0", "0.2.1", true}, {"1.2.3", "1.2.3", false}, {"1.2.3", "1.2.4", false}, {"1.2.3", "1.3.0", false}, {"1.2.3", "2.0.0", true}, {"1.2.3", "10.0.0", true}, {"foo", "1.0.0", false}, // version parse error {"1.0.0", "bar", false}, // version parse error } for index, testData := range testDataSlice { t.Run(strconv.Itoa(index), func(t *testing.T) { actual, _ := BreakingChangeAllowed(testData.left, testData.right) assert.Equal(t, testData.breaking, actual, fmt.Sprintf("input: %s,%s\n", testData.left, testData.right)) }) } } 070701000000B0000081ED0000000000000000000000016624BE45000002B0000000000000000000000000000000000000001E00000000chart-testing-3.11.0/setup.sh#!/usr/bin/env bash # Copyright The Helm Authors. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit go install golang.org/x/tools/cmd/goimports@latest 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!486 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor