mirror of https://github.com/helm/helm
Signed-off-by: god032396-del <god032396@gmail.com>pull/32213/head
commit
87dec420b7
@ -0,0 +1,70 @@
|
||||
name: Bug Report
|
||||
description: Report a bug encountered in Helm
|
||||
labels: kind/bug
|
||||
body:
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: |
|
||||
Please provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: What did you expect to happen?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: repro
|
||||
attributes:
|
||||
label: How can we reproduce it (as minimally and precisely as possible)?
|
||||
description: |
|
||||
Please list steps someone can follow to trigger the issue.
|
||||
|
||||
For example:
|
||||
1. Run `helm install mychart ./path-to-chart -f values.yaml --debug`
|
||||
2. Observe the following error: ...
|
||||
|
||||
You can include:
|
||||
- a sample `values.yaml` block
|
||||
- a link to a chart
|
||||
- specific `helm` commands used
|
||||
|
||||
This helps others reproduce and debug your issue more effectively.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: helmVersion
|
||||
attributes:
|
||||
label: Helm version
|
||||
value: |
|
||||
<details>
|
||||
|
||||
```console
|
||||
$ helm version
|
||||
# paste output here
|
||||
```
|
||||
</details>
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: kubeVersion
|
||||
attributes:
|
||||
label: Kubernetes version
|
||||
value: |
|
||||
<details>
|
||||
|
||||
```console
|
||||
$ kubectl version
|
||||
# paste output here
|
||||
```
|
||||
|
||||
</details>
|
||||
validations:
|
||||
required: true
|
||||
@ -0,0 +1,27 @@
|
||||
name: Documentation
|
||||
description: Report any mistakes or missing information from the documentation or the examples
|
||||
labels: kind/documentation
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
⚠️ **Note**: Most documentation lives in [helm/helm-www](https://github.com/helm/helm-www).
|
||||
If your issue is about Helm website documentation or examples, please [open an issue there](https://github.com/helm/helm-www/issues/new/choose).
|
||||
|
||||
- type: textarea
|
||||
id: feature
|
||||
attributes:
|
||||
label: What would you like to be added?
|
||||
description: |
|
||||
Link to the issue (please include a link to the specific documentation or example).
|
||||
Link to the issue raised in [Helm Documentation Improvement Proposal](https://github.com/helm/helm-www)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: rationale
|
||||
attributes:
|
||||
label: Why is this needed?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
name: Enhancement/feature
|
||||
description: Provide supporting details for a feature in development
|
||||
labels: kind/feature
|
||||
body:
|
||||
- type: textarea
|
||||
id: feature
|
||||
attributes:
|
||||
label: What would you like to be added?
|
||||
description: |
|
||||
Feature requests are unlikely to make progress as issues.
|
||||
Initial discussion and ideas can happen on an issue.
|
||||
But significant changes or features must be proposed as a [Helm Improvement Proposal](https://github.com/helm/community/blob/main/hips/hip-0001.md) (HIP)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: rationale
|
||||
attributes:
|
||||
label: Why is this needed?
|
||||
validations:
|
||||
required: true
|
||||
@ -0,0 +1,39 @@
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
- # Keep dev-v3 branch dependencies up to date, while Helm v3 is within support
|
||||
package-ecosystem: "gomod"
|
||||
target-branch: "dev-v3"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
groups:
|
||||
k8s.io:
|
||||
patterns:
|
||||
- "k8s.io/api"
|
||||
- "k8s.io/apiextensions-apiserver"
|
||||
- "k8s.io/apimachinery"
|
||||
- "k8s.io/apiserver"
|
||||
- "k8s.io/cli-runtime"
|
||||
- "k8s.io/client-go"
|
||||
- "k8s.io/kubectl"
|
||||
- package-ecosystem: "gomod"
|
||||
target-branch: "main"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
groups:
|
||||
k8s.io:
|
||||
patterns:
|
||||
- "k8s.io/api"
|
||||
- "k8s.io/apiextensions-apiserver"
|
||||
- "k8s.io/apimachinery"
|
||||
- "k8s.io/apiserver"
|
||||
- "k8s.io/cli-runtime"
|
||||
- "k8s.io/client-go"
|
||||
- "k8s.io/kubectl"
|
||||
- package-ecosystem: "github-actions"
|
||||
target-branch: "main"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
@ -0,0 +1,2 @@
|
||||
GOLANG_VERSION=1.26
|
||||
GOLANGCI_LINT_VERSION=v2.11.3
|
||||
@ -0,0 +1,12 @@
|
||||
<!-- Thanks for sending a pull request! Here are some tips for you:
|
||||
1. Make sure to read the Contributing Guide before submitting your PR: https://github.com/helm/helm/blob/main/CONTRIBUTING.md
|
||||
2. If this PR closes another issue, add 'closes #<issue number>' somewhere in the PR summary. GitHub will automatically close that issue when this PR gets merged. Alternatively, adding 'refs #<issue number>' will not close the issue, but help provide the reviewer more context.-->
|
||||
|
||||
**What this PR does / why we need it**:
|
||||
|
||||
**Special notes for your reviewer**:
|
||||
|
||||
**If applicable**:
|
||||
- [ ] this PR contains user facing changes (the `docs needed` label should be applied if so)
|
||||
- [ ] this PR contains unit tests
|
||||
- [ ] this PR has been tested for backwards compatibility
|
||||
@ -0,0 +1,36 @@
|
||||
name: build-test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- "dev-v3"
|
||||
- "release-**"
|
||||
pull_request:
|
||||
branches:
|
||||
- "main"
|
||||
- "dev-v3"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2
|
||||
- name: Add variables to environment file
|
||||
run: cat ".github/env" >> "$GITHUB_ENV"
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # pin@6.4.0
|
||||
with:
|
||||
go-version: '${{ env.GOLANG_VERSION }}'
|
||||
check-latest: true
|
||||
- name: Test source headers are present
|
||||
run: make test-source-headers
|
||||
- name: Check if go modules need to be tidied
|
||||
run: go mod tidy -diff
|
||||
- name: Run unit tests
|
||||
run: make test-coverage
|
||||
- name: Test build
|
||||
run: make build
|
||||
@ -0,0 +1,27 @@
|
||||
name: golangci-lint
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
golangci:
|
||||
name: golangci-lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2
|
||||
- name: Add variables to environment file
|
||||
run: cat ".github/env" >> "$GITHUB_ENV"
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # pin@6.4.0
|
||||
with:
|
||||
go-version: '${{ env.GOLANG_VERSION }}'
|
||||
check-latest: true
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee #pin@9.2.1
|
||||
with:
|
||||
version: ${{ env.GOLANGCI_LINT_VERSION }}
|
||||
@ -0,0 +1,35 @@
|
||||
name: govulncheck
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- go.sum
|
||||
- .github/workflows/govulncheck.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- go.sum
|
||||
- .github/workflows/govulncheck.yml
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
govulncheck:
|
||||
name: govulncheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Add variables to environment file
|
||||
run: cat ".github/env" >> "$GITHUB_ENV"
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # pin@6.4.0
|
||||
with:
|
||||
go-version: '${{ env.GOLANG_VERSION }}'
|
||||
check-latest: true
|
||||
- name: govulncheck
|
||||
uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # pin@1.0.4
|
||||
with:
|
||||
go-package: ./...
|
||||
@ -0,0 +1,117 @@
|
||||
name: release
|
||||
on:
|
||||
create:
|
||||
tags:
|
||||
- v*
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions: read-all
|
||||
|
||||
# Note the only differences between release and canary-release jobs are:
|
||||
# - only canary passes --overwrite flag
|
||||
# - the VERSION make variable passed to 'make dist checksum' is expected to
|
||||
# be "canary" if the job is triggered by a push to "main" branch. If the
|
||||
# job is triggered by a tag push, VERSION should be the tag ref.
|
||||
jobs:
|
||||
release:
|
||||
if: startsWith(github.ref, 'refs/tags/v') && github.repository == 'helm/helm'
|
||||
runs-on: ubuntu-latest-16-cores
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Add variables to environment file
|
||||
run: cat ".github/env" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # pin@6.4.0
|
||||
with:
|
||||
go-version: '${{ env.GOLANG_VERSION }}'
|
||||
check-latest: true
|
||||
- name: Run unit tests
|
||||
run: make test-coverage
|
||||
- name: Build Helm Binaries
|
||||
run: |
|
||||
set -eu -o pipefail
|
||||
|
||||
make build-cross VERSION="${{ github.ref_name }}"
|
||||
make dist checksum VERSION="${{ github.ref_name }}"
|
||||
|
||||
- name: Set latest version
|
||||
run: |
|
||||
set -eu -o pipefail
|
||||
|
||||
mkdir -p _dist_versions
|
||||
|
||||
# Push the latest semver tag, excluding prerelease tags
|
||||
LATEST_VERSION="$(git tag | sort -r --version-sort | grep '^v[0-9]' | grep -v '-' | head -n1)"
|
||||
echo "LATEST_VERSION=${LATEST_VERSION}"
|
||||
if [[ "${LATEST_VERSION}" != v4.* ]]; then
|
||||
echo "Error: Latest version ${LATEST_VERSION} is not a v4 release"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "${LATEST_VERSION}" > _dist_versions/helm-latest-version
|
||||
echo "${LATEST_VERSION}" > _dist_versions/helm4-latest-version
|
||||
|
||||
- name: Upload Binaries
|
||||
uses: bacongobbler/azure-blob-storage-upload@50f7d898b7697e864130ea04c303ca38b5751c50 # pin@3.0.0
|
||||
env:
|
||||
AZURE_STORAGE_CONNECTION_STRING: "${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}"
|
||||
AZURE_STORAGE_CONTAINER_NAME: "${{ secrets.AZURE_STORAGE_CONTAINER_NAME }}"
|
||||
with:
|
||||
source_dir: _dist
|
||||
container_name: ${{ secrets.AZURE_STORAGE_CONTAINER_NAME }}
|
||||
connection_string: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
|
||||
extra_args: '--pattern helm-*'
|
||||
|
||||
- name: Upload Version tag files
|
||||
uses: bacongobbler/azure-blob-storage-upload@50f7d898b7697e864130ea04c303ca38b5751c50 # pin@3.0.0
|
||||
env:
|
||||
AZURE_STORAGE_CONNECTION_STRING: "${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}"
|
||||
AZURE_STORAGE_CONTAINER_NAME: "${{ secrets.AZURE_STORAGE_CONTAINER_NAME }}"
|
||||
with:
|
||||
overwrite: 'true'
|
||||
source_dir: _dist_versions
|
||||
container_name: ${{ secrets.AZURE_STORAGE_CONTAINER_NAME }}
|
||||
connection_string: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
|
||||
|
||||
canary-release:
|
||||
runs-on: ubuntu-latest-16-cores
|
||||
if: github.ref == 'refs/heads/main' && github.repository == 'helm/helm'
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Add variables to environment file
|
||||
run: cat ".github/env" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # pin@6.4.0
|
||||
with:
|
||||
go-version: '${{ env.GOLANG_VERSION }}'
|
||||
check-latest: true
|
||||
|
||||
- name: Run unit tests
|
||||
run: make test-coverage
|
||||
|
||||
- name: Build Helm Binaries
|
||||
run: |
|
||||
make build-cross
|
||||
make dist checksum VERSION="canary"
|
||||
|
||||
- name: Upload Binaries
|
||||
uses: bacongobbler/azure-blob-storage-upload@50f7d898b7697e864130ea04c303ca38b5751c50 # pin@3.0.0
|
||||
with:
|
||||
source_dir: _dist
|
||||
container_name: ${{ secrets.AZURE_STORAGE_CONTAINER_NAME }}
|
||||
connection_string: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
|
||||
extra_args: '--pattern helm-*'
|
||||
# WARNING: this will overwrite existing blobs in your blob storage
|
||||
overwrite: 'true'
|
||||
@ -0,0 +1,69 @@
|
||||
name: Scorecard supply-chain security
|
||||
on:
|
||||
# For Branch-Protection check. Only the default branch is supported. See
|
||||
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
|
||||
branch_protection_rule:
|
||||
# To guarantee Maintained check is occasionally updated. See
|
||||
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
|
||||
schedule:
|
||||
- cron: '25 7 * * 0'
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecard analysis
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Needed to upload the results to code-scanning dashboard.
|
||||
security-events: write
|
||||
# Needed to publish results and get a badge (see publish_results below).
|
||||
id-token: write
|
||||
# Uncomment the permissions below if installing in a private repository.
|
||||
# contents: read
|
||||
# actions: read
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
|
||||
# - you want to enable the Branch-Protection check on a *public* repository, or
|
||||
# - you are installing Scorecard on a *private* repository
|
||||
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional.
|
||||
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
|
||||
|
||||
# Public repositories:
|
||||
# - Publish results to OpenSSF REST API for easy access by consumers
|
||||
# - Allows the repository to include the Scorecard badge.
|
||||
# - See https://github.com/ossf/scorecard-action#publishing-results.
|
||||
# For private repositories:
|
||||
# - `publish_results` will always be set to `false`, regardless
|
||||
# of the value entered here.
|
||||
publish_results: true
|
||||
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard (optional).
|
||||
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
@ -0,0 +1,23 @@
|
||||
name: "Close stale issues"
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: 'This issue has been marked as stale because it has been open for 90 days with no activity. This thread will be automatically closed in 30 days if no further activity occurs.'
|
||||
stale-pr-message: 'This pull request has been marked as stale because it has been open for 90 days with no activity. This pull request will be automatically closed in 30 days if no further activity occurs.'
|
||||
exempt-issue-labels: 'keep open,v4.x,in progress'
|
||||
days-before-stale: 90
|
||||
days-before-close: 30
|
||||
operations-per-run: 200
|
||||
@ -0,0 +1,17 @@
|
||||
*.exe
|
||||
*.swp
|
||||
.DS_Store
|
||||
.coverage/
|
||||
.idea
|
||||
.claude
|
||||
.vimrc
|
||||
.vscode/
|
||||
.devcontainer/
|
||||
_dist/
|
||||
_dist_versions/
|
||||
bin/
|
||||
vendor/
|
||||
# Ignores charts pulled for dependency build tests
|
||||
cmd/helm/testdata/testcharts/issue-7233/charts/*
|
||||
pkg/cmd/testdata/testcharts/issue-7233/charts/*
|
||||
.pre-commit-config.yaml
|
||||
@ -0,0 +1,170 @@
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- goimports
|
||||
|
||||
exclusions:
|
||||
generated: lax
|
||||
|
||||
settings:
|
||||
gofmt:
|
||||
simplify: true
|
||||
|
||||
goimports:
|
||||
local-prefixes:
|
||||
- helm.sh/helm/v4
|
||||
|
||||
linters:
|
||||
default: none
|
||||
|
||||
# Keep sorted alphabetically
|
||||
enable:
|
||||
- bidichk
|
||||
- depguard
|
||||
- dupl
|
||||
- errorlint
|
||||
- exhaustive
|
||||
- gocritic
|
||||
- gomodguard
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- modernize
|
||||
- nakedret
|
||||
- nolintlint
|
||||
- perfsprint
|
||||
- revive
|
||||
- sloglint
|
||||
- staticcheck
|
||||
- testifylint
|
||||
- thelper
|
||||
- unused
|
||||
- usestdlibvars
|
||||
- usetesting
|
||||
- whitespace
|
||||
|
||||
exclusions:
|
||||
|
||||
generated: lax
|
||||
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
|
||||
warn-unused: true
|
||||
|
||||
# Keep sorted alphabetically
|
||||
settings:
|
||||
depguard:
|
||||
rules:
|
||||
Main:
|
||||
deny:
|
||||
- pkg: github.com/hashicorp/go-multierror
|
||||
desc: "use errors instead"
|
||||
- pkg: github.com/pkg/errors
|
||||
desc: "use errors instead"
|
||||
|
||||
dupl:
|
||||
threshold: 400
|
||||
|
||||
errorlint:
|
||||
# Check for plain type assertions and type switches.
|
||||
asserts: false
|
||||
# Check for plain error comparisons.
|
||||
comparison: false
|
||||
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: true
|
||||
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- appendAssign
|
||||
- appendCombine
|
||||
- assignOp
|
||||
- badCall
|
||||
- commentedOutCode
|
||||
- commentFormatting
|
||||
- deferInLoop
|
||||
- elseif
|
||||
- emptyStringTest
|
||||
- exposedSyncMutex
|
||||
- filepathJoin
|
||||
- httpNoBody
|
||||
- hugeParam
|
||||
- ifElseChain
|
||||
- importShadow
|
||||
- nestingReduce
|
||||
- nilValReturn
|
||||
- octalLiteral
|
||||
- paramTypeCombine
|
||||
- preferStringWriter
|
||||
- ptrToRefParam
|
||||
- rangeValCopy
|
||||
- regexpSimplify
|
||||
- singleCaseSwitch
|
||||
- sloppyReassign
|
||||
- sloppyLen
|
||||
- sprintfQuotedString
|
||||
- stringConcatSimplify
|
||||
- stringsCompare
|
||||
- stringXbytes
|
||||
- todoCommentWithoutDetail
|
||||
- tooManyResultsChecker
|
||||
- typeAssertChain
|
||||
- uncheckedInlineErr
|
||||
- unnamedResult
|
||||
- unnecessaryDefer
|
||||
- unslice
|
||||
- whyNoLint
|
||||
enable-all: true
|
||||
|
||||
gomodguard:
|
||||
blocked:
|
||||
modules:
|
||||
- github.com/evanphx/json-patch:
|
||||
recommendations:
|
||||
- github.com/evanphx/json-patch/v5
|
||||
|
||||
nolintlint:
|
||||
require-specific: true
|
||||
|
||||
revive:
|
||||
enable-all-rules: false
|
||||
enable-default-rules: true
|
||||
max-open-files: 2048
|
||||
rules:
|
||||
- name: unnecessary-format
|
||||
- name: var-naming
|
||||
arguments:
|
||||
- ["ID"] # AllowList
|
||||
- ["VM"] # DenyList
|
||||
- - skip-initialism-name-checks: true
|
||||
upper-case-const: true
|
||||
skip-package-name-checks: true
|
||||
skip-package-name-collision-with-go-std: true
|
||||
extra-bad-package-names:
|
||||
- helpers
|
||||
- models
|
||||
|
||||
testifylint:
|
||||
disable:
|
||||
- empty
|
||||
- encoded-compare
|
||||
- equal-values
|
||||
- expected-actual
|
||||
- float-compare
|
||||
- go-require
|
||||
- len
|
||||
- nil-compare
|
||||
- require-error
|
||||
- suite-dont-use-pkg
|
||||
- suite-extra-assert-call
|
||||
# Intentionally enable all testifylint rules so new checks are adopted automatically.
|
||||
enable-all: true
|
||||
|
||||
run:
|
||||
timeout: 10m
|
||||
|
||||
version: "2"
|
||||
@ -0,0 +1,74 @@
|
||||
version: 2
|
||||
|
||||
project_name: helm
|
||||
|
||||
dist: _dist
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
- arm
|
||||
- "386"
|
||||
- ppc64le
|
||||
- s390x
|
||||
- riscv64
|
||||
- loong64
|
||||
goamd64:
|
||||
- v1
|
||||
goarm:
|
||||
- "7"
|
||||
goarm64:
|
||||
- v8.0
|
||||
go386:
|
||||
- sse2
|
||||
goriscv64:
|
||||
- rva20u64
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: "386"
|
||||
- goos: darwin
|
||||
goarch: arm
|
||||
- goos: darwin
|
||||
goarch: ppc64le
|
||||
- goos: darwin
|
||||
goarch: s390x
|
||||
- goos: darwin
|
||||
goarch: riscv64
|
||||
- goos: darwin
|
||||
goarch: loong64
|
||||
- goos: windows
|
||||
goarch: "386"
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
- goos: windows
|
||||
goarch: ppc64le
|
||||
- goos: windows
|
||||
goarch: s390x
|
||||
- goos: windows
|
||||
goarch: riscv64
|
||||
- goos: windows
|
||||
goarch: loong64
|
||||
main: ./cmd/helm
|
||||
no_unique_dist_dir: true
|
||||
binary: "{{ .Os }}-{{ .Arch }}/helm"
|
||||
ldflags:
|
||||
- "{{ .Env.LDFLAGS }}"
|
||||
flags:
|
||||
- -trimpath
|
||||
dir: .
|
||||
|
||||
snapshot:
|
||||
version_template: "{{ if index .Env \"GORELEASER_CURRENT_TAG\" }}{{ .Env.GORELEASER_CURRENT_TAG }}{{ else }}{{ incpatch .Version }}-next{{ end }}"
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
@ -0,0 +1,25 @@
|
||||
To add your organization to this list, open a pull request that adds your
|
||||
organization's name, optionally with a link. The list is in alphabetical order.
|
||||
|
||||
(Remember to use `git commit --signoff` to comply with the DCO)
|
||||
|
||||
# Organizations Using Helm
|
||||
|
||||
- [IBM](https://www.ibm.com)
|
||||
- [InfoCert](https://www.infocert.it/)
|
||||
- [Intercept](https://Intercept.cloud)
|
||||
- [Microsoft](https://microsoft.com)
|
||||
- [New Relic](https://www.newrelic.com)
|
||||
- [Octopus Deploy](https://octopus.com/)
|
||||
- [Omnistrate](https://omnistrate.com)
|
||||
- [Oracle](www.oracle.com)
|
||||
- [Percona](https://www.percona.com)
|
||||
- [Qovery](https://www.qovery.com/)
|
||||
- [Samsung SDS](https://www.samsungsds.com/)
|
||||
- [Softonic](https://hello.softonic.com/)
|
||||
- [SyncTune](https://mb-consulting.dev)
|
||||
- [Syself](https://syself.com)
|
||||
- [Ville de Montreal](https://montreal.ca)
|
||||
|
||||
|
||||
_This file is part of the CNCF official documentation for projects._
|
||||
@ -0,0 +1,88 @@
|
||||
# AGENTS.md
|
||||
|
||||
## Overview
|
||||
|
||||
Helm is a package manager for Kubernetes written in Go. It enables users to define, install, and upgrade complex Kubernetes applications using charts.
|
||||
This document provides an overview of the codebase structure, development guidelines, and key patterns for contributors.
|
||||
|
||||
The codebase supports both an SDK for advanced users, and a CLI for direct end user usage.
|
||||
|
||||
The project currently supports Helm v3 and Helm v4 versions, based on the `dev-v3` and `main` branches respectively.
|
||||
|
||||
## Build and test
|
||||
|
||||
```bash
|
||||
make build # Build binary
|
||||
make test # Run all tests (style + unit)
|
||||
make test-unit # Unit tests only
|
||||
make test-coverage # With coverage
|
||||
make test-style # Linting (wraps golangci-lint)
|
||||
go test -run TestName # Specific test
|
||||
```
|
||||
|
||||
## Code structure
|
||||
|
||||
Major packages:
|
||||
|
||||
- `cmd/helm/` - CLI entry point, wires CLI flags to `pkg/cmd/` commands
|
||||
- `pkg/` - Public API
|
||||
- `action/` - Core operations (install, upgrade, rollback)
|
||||
- `cmd/` - Cobra command implementations bridging CLI flags to `pkg/action/`
|
||||
- `chart/v2/` - Stable chart format
|
||||
- `engine/` - Template rendering (Go templates + Sprig)
|
||||
- `kube/` - Kubernetes client abstraction layer
|
||||
- `registry/` - OCI support
|
||||
- `release/` - Release types and interfaces (`v1/`, `common/`)
|
||||
- `repo/` - Chart repository indexing and interaction
|
||||
- `storage/` - Release backends (Secrets/ConfigMaps/SQL)
|
||||
- `internal/` - Private implementations
|
||||
- `chart/v3/` - Next-gen chart format
|
||||
- `release/v2/` - Release package for chart v3 support
|
||||
|
||||
## Development
|
||||
|
||||
### Compatibility
|
||||
|
||||
Changes are required to maintain backward compatibility as described in [HIP-0004: Document backwards-compatibility rules](https://github.com/helm/community/blob/main/hips/hip-0004.md).
|
||||
|
||||
Typically this means that:
|
||||
|
||||
- the signatures of public APIs, i.e., those in the `pkg/` directory should not change
|
||||
- CLI commands and parameters should not be removed or changed in a way that would break existing scripts or workflows
|
||||
- functional behaviour (as implied or documented) must not be modified in a way that would break existing users' expectations
|
||||
|
||||
An exception to the above is where incompatible changes are needed to fix a security vulnerability, where minimal breaking changes may be made to address the issue.
|
||||
|
||||
### Code standards
|
||||
|
||||
- Use table-driven tests with testify
|
||||
- Golden files in `testdata/` for complex output
|
||||
- Mock Kubernetes clients for action tests
|
||||
- All commits must include DCO sign-off: `git commit -s`
|
||||
|
||||
### Branching
|
||||
|
||||
Standard workflow is for PR development changes to the `main` branch. Minor release branches are cut from `main`, then maintained for critical fixes via patch releases.
|
||||
Bug and security fixes are also backported to `dev-v3` where applicable.
|
||||
|
||||
Development branches:
|
||||
|
||||
- `main` - Helm v4
|
||||
- `dev-v3` - Helm v3 (backport security and bugfixes from main)
|
||||
|
||||
Release branches:
|
||||
|
||||
- `release-v3.X` - Release branches for v3.X versions
|
||||
- `release-v4.X` - Release branches for v4.X versions
|
||||
|
||||
### Major dependencies
|
||||
|
||||
- `k8s.io/client-go` - Kubernetes interaction
|
||||
- `github.com/spf13/cobra` - CLI framework
|
||||
- `github.com/Masterminds/sprig` - Template functions
|
||||
|
||||
### Key patterns
|
||||
|
||||
- **Actions**: High-level operations live in `pkg/action/`, typically using a shared Configuration
|
||||
- **Chart versions**: Charts v2 (stable) in `pkg/chart/v2`, v3 (under development) in `internal/chart/v3`
|
||||
- **Plugins and extensibility**: Enabling additional functionality via plugins and extension points, such as custom template functions or storage backends is preferred over incorporating into Helm's codebase
|
||||
@ -0,0 +1,202 @@
|
||||
|
||||
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 2016 The Kubernetes 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.
|
||||
@ -0,0 +1,32 @@
|
||||
maintainers:
|
||||
- banjoh
|
||||
- gjenkins8
|
||||
- joejulian
|
||||
- marckhouzam
|
||||
- mattfarina
|
||||
- robertsirc
|
||||
- sabre1041
|
||||
- scottrigby
|
||||
- technosophos
|
||||
- TerryHowe
|
||||
triage:
|
||||
- yxxhero
|
||||
- zonggen
|
||||
- z4ce
|
||||
emeritus:
|
||||
- adamreese
|
||||
- bacongobbler
|
||||
- fibonacci1729
|
||||
- hickeyma
|
||||
- jascott1
|
||||
- jdolitsky
|
||||
- michelleN
|
||||
- migmartri
|
||||
- nebril
|
||||
- prydonius
|
||||
- rimusz
|
||||
- seh
|
||||
- SlickNik
|
||||
- thomastaylor312
|
||||
- vaikas-google
|
||||
- viglesiasce
|
||||
@ -0,0 +1,87 @@
|
||||
# Helm
|
||||
|
||||
[](https://github.com/helm/helm/actions?workflow=release)
|
||||
[](https://goreportcard.com/report/helm.sh/helm/v4)
|
||||
[](https://pkg.go.dev/helm.sh/helm/v4)
|
||||
[](https://bestpractices.coreinfrastructure.org/projects/3131)
|
||||
[](https://scorecard.dev/viewer/?uri=github.com/helm/helm)
|
||||
[](https://insights.linuxfoundation.org/project/helm)
|
||||
|
||||
Helm is a tool for managing Charts. Charts are packages of pre-configured Kubernetes resources.
|
||||
|
||||
Use Helm to:
|
||||
|
||||
- Find and use [popular software packaged as Helm Charts](https://artifacthub.io/packages/search?kind=0) to run in Kubernetes
|
||||
- Share your own applications as Helm Charts
|
||||
- Create reproducible builds of your Kubernetes applications
|
||||
- Intelligently manage your Kubernetes manifest files
|
||||
- Manage releases of Helm packages
|
||||
|
||||
## Helm in a Handbasket
|
||||
|
||||
Helm is a tool that streamlines installing and managing Kubernetes applications.
|
||||
Think of it like apt/yum/homebrew for Kubernetes.
|
||||
|
||||
- Helm renders your templates and communicates with the Kubernetes API
|
||||
- Helm runs on your laptop, CI/CD, or wherever you want it to run.
|
||||
- Charts are Helm packages that contain at least two things:
|
||||
- A description of the package (`Chart.yaml`)
|
||||
- One or more templates, which contain Kubernetes manifest files
|
||||
- Charts can be stored on disk, or fetched from remote chart repositories
|
||||
(like Debian or RedHat packages)
|
||||
|
||||
## Helm Development and Stable Versions
|
||||
|
||||
Helm v4 is the current stable release, developed on the `main` branch.
|
||||
Helm v3 is in support mode on the `dev-v3` branch: bug fixes until July 8th 2026, security fixes until November 11th 2026.
|
||||
|
||||
## Install
|
||||
|
||||
Binary downloads of the Helm client can be found on [the Releases page](https://github.com/helm/helm/releases/latest).
|
||||
|
||||
Unpack the `helm` binary and add it to your PATH and you are good to go!
|
||||
|
||||
If you want to use a package manager:
|
||||
|
||||
- [Homebrew](https://brew.sh/) users can use `brew install helm`.
|
||||
- [Chocolatey](https://chocolatey.org/) users can use `choco install kubernetes-helm`.
|
||||
- [Winget](https://learn.microsoft.com/en-us/windows/package-manager/) users can use `winget install Helm.Helm`.
|
||||
- [Scoop](https://scoop.sh/) users can use `scoop install helm`.
|
||||
- [Snapcraft](https://snapcraft.io/) users can use `snap install helm --classic`.
|
||||
- [Flox](https://flox.dev) users can use `flox install kubernetes-helm`.
|
||||
- [Mise-en-place](https://mise.jdx.dev/) users can use `mise use -g helm@latest`
|
||||
|
||||
To rapidly get Helm up and running, start with the [Quick Start Guide](https://helm.sh/docs/intro/quickstart/).
|
||||
|
||||
See the [installation guide](https://helm.sh/docs/intro/install/) for more options,
|
||||
including installing pre-releases.
|
||||
|
||||
## Docs
|
||||
|
||||
Get started with the [Quick Start guide](https://helm.sh/docs/intro/quickstart/) or plunge into the [complete documentation](https://helm.sh/docs).
|
||||
|
||||
## Roadmap
|
||||
|
||||
The [Helm roadmap uses GitHub milestones](https://github.com/helm/helm/milestones) to track the progress of the project.
|
||||
|
||||
Helm v4 development happens on the `main` branch. Helm v3 is in support mode on the `dev-v3` branch and receives only bug and security fixes.
|
||||
|
||||
## Community, discussion, contribution, and support
|
||||
|
||||
You can reach the Helm community and developers via the following channels:
|
||||
|
||||
- [Kubernetes Slack](https://kubernetes.slack.com):
|
||||
- [#helm-users](https://kubernetes.slack.com/messages/helm-users)
|
||||
- [#helm-dev](https://kubernetes.slack.com/messages/helm-dev)
|
||||
- [#charts](https://kubernetes.slack.com/messages/charts)
|
||||
- Mailing List:
|
||||
- [Helm Mailing List](https://lists.cncf.io/g/cncf-helm)
|
||||
- Developer Call: Thursdays at 9:30-10:00 Pacific ([meeting details](https://github.com/helm/community/blob/master/communication.md#meetings))
|
||||
|
||||
### Contribution
|
||||
|
||||
If you're interested in contributing, please refer to the [Contributing Guide](CONTRIBUTING.md) **before submitting a pull request**.
|
||||
|
||||
### Code of conduct
|
||||
|
||||
Participation in the Helm community is governed by the [Code of Conduct](code-of-conduct.md).
|
||||
@ -0,0 +1,3 @@
|
||||
# Helm Security Reporting and Policy
|
||||
|
||||
The Helm project has [a common process and policy that can be found here](https://github.com/helm/community/blob/master/SECURITY.md).
|
||||
@ -0,0 +1,51 @@
|
||||
/*
|
||||
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 "helm.sh/helm/v4/cmd/helm"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
// Import to initialize client auth plugins.
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
|
||||
helmcmd "helm.sh/helm/v4/pkg/cmd"
|
||||
"helm.sh/helm/v4/pkg/kube"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Setting the name of the app for managedFields in the Kubernetes client.
|
||||
// It is set here to the full name of "helm" so that renaming of helm to
|
||||
// another name (e.g., helm2 or helm3) does not change the name of the
|
||||
// manager as picked up by the automated name detection.
|
||||
kube.ManagedFieldsManager = "helm"
|
||||
|
||||
cmd, err := helmcmd.NewRootCmd(os.Stdout, os.Args[1:], helmcmd.SetupLogging)
|
||||
if err != nil {
|
||||
slog.Warn("command failed", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
var cerr helmcmd.CommandError
|
||||
if errors.As(err, &cerr) {
|
||||
os.Exit(cerr.ExitCode)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
/*
|
||||
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 (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCliPluginExitCode(t *testing.T) {
|
||||
if os.Getenv("RUN_MAIN_FOR_TESTING") == "1" {
|
||||
os.Args = []string{"helm", "exitwith", "43"}
|
||||
|
||||
// We DO call helm's main() here. So this looks like a normal `helm` process.
|
||||
main()
|
||||
|
||||
// As main calls os.Exit, we never reach this line.
|
||||
// But the test called this block of code catches and verifies the exit code.
|
||||
return
|
||||
}
|
||||
|
||||
// Currently, plugins assume a Linux subsystem. Skip the execution
|
||||
// tests until this is fixed
|
||||
if runtime.GOOS != "windows" {
|
||||
// Do a second run of this specific test(TestPluginExitCode) with RUN_MAIN_FOR_TESTING=1 set,
|
||||
// So that the second run is able to run main() and this first run can verify the exit status returned by that.
|
||||
//
|
||||
// This technique originates from https://talks.golang.org/2014/testing.slide#23.
|
||||
cmd := exec.Command(os.Args[0], "-test.run=TestCliPluginExitCode")
|
||||
cmd.Env = append(
|
||||
os.Environ(),
|
||||
"RUN_MAIN_FOR_TESTING=1",
|
||||
// See pkg/cli/environment.go for which envvars can be used for configuring these passes
|
||||
// and also see plugin_test.go for how a plugin env can be set up.
|
||||
// This mimics the "exitwith" test case in TestLoadPlugins using envvars
|
||||
"HELM_PLUGINS=../../pkg/cmd/testdata/helmhome/helm/plugins",
|
||||
)
|
||||
stdout := &bytes.Buffer{}
|
||||
stderr := &bytes.Buffer{}
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
err := cmd.Run()
|
||||
|
||||
exiterr := &exec.ExitError{}
|
||||
ok := errors.As(err, &exiterr)
|
||||
if !ok {
|
||||
t.Fatalf("Unexpected error type returned by os.Exit: %T", err)
|
||||
}
|
||||
|
||||
assert.Empty(t, stdout.String())
|
||||
|
||||
expectedStderr := "level=WARN msg=\"failed to load plugin (ignoring)\" plugin_yaml=../../pkg/cmd/testdata/helmhome/helm/plugins/noversion/plugin.yaml error=\"failed to load plugin \\\"../../pkg/cmd/testdata/helmhome/helm/plugins/noversion\\\": plugin `version` is required\"\nError: plugin \"exitwith\" exited with error\n"
|
||||
if stderr.String() != expectedStderr {
|
||||
t.Errorf("Expected %q written to stderr: Got %q", expectedStderr, stderr.String())
|
||||
}
|
||||
|
||||
if exiterr.ExitCode() != 43 {
|
||||
t.Errorf("Expected exit code 43: Got %d", exiterr.ExitCode())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
# Community Code of Conduct
|
||||
|
||||
Helm follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||
@ -0,0 +1,181 @@
|
||||
module helm.sh/helm/v4
|
||||
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24
|
||||
github.com/BurntSushi/toml v1.6.0
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||
github.com/Masterminds/semver/v3 v3.5.0
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/Masterminds/squirrel v1.5.4
|
||||
github.com/Masterminds/vcs v1.13.3
|
||||
github.com/ProtonMail/go-crypto v1.4.1
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
||||
github.com/cyphar/filepath-securejoin v0.6.1
|
||||
github.com/distribution/distribution/v3 v3.1.1
|
||||
github.com/evanphx/json-patch/v5 v5.9.11
|
||||
github.com/extism/go-sdk v1.7.1
|
||||
github.com/fatih/color v1.19.0
|
||||
github.com/fluxcd/cli-utils v1.2.1
|
||||
github.com/foxcpp/go-mockdns v1.2.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gofrs/flock v0.13.0
|
||||
github.com/gosuri/uitable v0.0.4
|
||||
github.com/jmoiron/sqlx v1.4.0
|
||||
github.com/lib/pq v1.12.3
|
||||
github.com/mattn/go-shellwords v1.0.13
|
||||
github.com/moby/term v0.5.2
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.1.1
|
||||
github.com/rubenv/sql-migrate v1.8.1
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/tetratelabs/wazero v1.12.0
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/crypto v0.53.0
|
||||
golang.org/x/term v0.44.0
|
||||
golang.org/x/text v0.38.0
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/api v0.36.1
|
||||
k8s.io/apiextensions-apiserver v0.36.1
|
||||
k8s.io/apimachinery v0.36.1
|
||||
k8s.io/apiserver v0.36.1
|
||||
k8s.io/cli-runtime v0.36.1
|
||||
k8s.io/client-go v0.36.1
|
||||
k8s.io/klog/v2 v2.140.0
|
||||
k8s.io/kubectl v0.36.1
|
||||
oras.land/oras-go/v2 v2.6.1
|
||||
sigs.k8s.io/controller-runtime v0.24.1
|
||||
sigs.k8s.io/kustomize/kyaml v0.21.1
|
||||
sigs.k8s.io/yaml v1.6.0
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/MakeNowJust/heredoc v1.0.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/bshuster-repo/logrus-logstash-hook v1.1.0 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.2 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.7.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.5 // indirect
|
||||
github.com/docker/go-events v0.0.0-20250808211157-605354379745 // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/go-errors/errors v1.5.1 // indirect
|
||||
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/gnostic-models v0.7.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/handlers v1.5.2 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
|
||||
github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.18.4 // indirect
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/miekg/dns v1.1.57 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/onsi/gomega v1.39.1 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.5 // indirect
|
||||
github.com/prometheus/otlptranslator v1.0.0 // indirect
|
||||
github.com/prometheus/procfs v0.20.1 // indirect
|
||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect
|
||||
github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect
|
||||
github.com/redis/go-redis/v9 v9.7.3 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xlab/treeprint v1.2.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 // indirect
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
|
||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.64.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/log v0.19.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/log v0.19.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
golang.org/x/mod v0.36.0 // indirect
|
||||
golang.org/x/net v0.55.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sync v0.21.0 // indirect
|
||||
golang.org/x/sys v0.46.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
golang.org/x/tools v0.45.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
google.golang.org/grpc v1.80.0 // indirect
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/component-base v0.36.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.21.1 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
|
||||
)
|
||||
@ -0,0 +1,525 @@
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
||||
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
|
||||
github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
||||
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
|
||||
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
|
||||
github.com/Masterminds/vcs v1.13.3 h1:IIA2aBdXvfbIM+yl/eTnL4hb1XwdpvuQLglAix1gweE=
|
||||
github.com/Masterminds/vcs v1.13.3/go.mod h1:TiE7xuEjl1N4j016moRd6vezp6e6Lz23gypeXfzXeW8=
|
||||
github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
|
||||
github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI=
|
||||
github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||
github.com/bshuster-repo/logrus-logstash-hook v1.1.0 h1:o2FzZifLg+z/DN1OFmzTWzZZx/roaqt8IPZCIVco8r4=
|
||||
github.com/bshuster-repo/logrus-logstash-hook v1.1.0/go.mod h1:Q2aXOe7rNuPgbBtPCOzYyWDvKX7+FpxE5sRdvcPoui0=
|
||||
github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk=
|
||||
github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
|
||||
github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||
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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/distribution/distribution/v3 v3.1.1 h1:KUbk7C8CfaLXy8kbf/hGq9cad/wCoLB6dbWH6DMbmX0=
|
||||
github.com/distribution/distribution/v3 v3.1.1/go.mod h1:d7lXwZpph0bVcOj4Aqn0nMrWHIwRQGdiV5TLeI+/w6Y=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
|
||||
github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
|
||||
github.com/docker/go-events v0.0.0-20250808211157-605354379745 h1:yOn6Ze6IbYI/KAw2lw/83ELYvZh6hvsygTVkD0dzMC4=
|
||||
github.com/docker/go-events v0.0.0-20250808211157-605354379745/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
|
||||
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
|
||||
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a h1:UwSIFv5g5lIvbGgtf3tVwC7Ky9rmMFBp0RMs+6f6YqE=
|
||||
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a/go.mod h1:C8DzXehI4zAbrdlbtOByKX6pfivJTBiV9Jjqv56Yd9Q=
|
||||
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
|
||||
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
|
||||
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
|
||||
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4=
|
||||
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc=
|
||||
github.com/extism/go-sdk v1.7.1 h1:lWJos6uY+tRFdlIHR+SJjwFDApY7OypS/2nMhiVQ9Sw=
|
||||
github.com/extism/go-sdk v1.7.1/go.mod h1:IT+Xdg5AZM9hVtpFUA+uZCJMge/hbvshl8bwzLtFyKA=
|
||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fluxcd/cli-utils v1.2.1 h1:ug9CicKW7H9QXnvNDapTSKuryZvWcu4Nw7pRvQa6jDY=
|
||||
github.com/fluxcd/cli-utils v1.2.1/go.mod h1:cky6M6eHvTQkoPtsuFYLIgAMYdpTCSLoor4IA6vueSw=
|
||||
github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0=
|
||||
github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
|
||||
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/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
|
||||
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
|
||||
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
|
||||
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
|
||||
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
|
||||
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
|
||||
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
|
||||
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=
|
||||
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
|
||||
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
|
||||
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
|
||||
github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw=
|
||||
github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca h1:T54Ema1DU8ngI+aef9ZhAhNGQhcRTrWxVeG07F+c/Rw=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
|
||||
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
|
||||
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-shellwords v1.0.13 h1:DC0OMEpGjm6LfNFU4ckYcvbQKyp2vE8atyFGXNtDcf4=
|
||||
github.com/mattn/go-shellwords v1.0.13/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
|
||||
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=
|
||||
github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
|
||||
github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
|
||||
github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY=
|
||||
github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
|
||||
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
|
||||
github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos=
|
||||
github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
|
||||
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
|
||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho=
|
||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U=
|
||||
github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc=
|
||||
github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ=
|
||||
github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
|
||||
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
|
||||
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rubenv/sql-migrate v1.8.1 h1:EPNwCvjAowHI3TnZ+4fQu3a915OpnQoPAjTXCGOy2U0=
|
||||
github.com/rubenv/sql-migrate v1.8.1/go.mod h1:BTIKBORjzyxZDS6dzoiw6eAFYJ1iNlGAtjn4LGeVjS8=
|
||||
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/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 h1:ZF+QBjOI+tILZjBaFj3HgFonKXUcwgJ4djLb6i42S3Q=
|
||||
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk=
|
||||
github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU=
|
||||
github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
|
||||
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 h1:dkBzNEAIKADEaFnuESzcXvpd09vxvDZsOjx11gjUqLk=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.67.0/go.mod h1:Z5RIwRkZgauOIfnG5IpidvLpERjhTninpP1dTG2jTl4=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9achKn3XgmxPxuMuqIL5rE8e8=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0/go.mod h1:ji9vId85hMxqfvICA0Jt8JqEdrXaAkcpkI9HPXya0ro=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K+pCJoPpQifuVpsKamUdn9U0zM3xUziVOqsGksUrY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0/go.mod h1:HBy4BjzgVE8139ieRI75oXm3EcDN+6GhD88JT1Kjvxg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.64.0/go.mod h1:UrgcjnarfdlBDP3GjDIJWe6HTprwSazNjwsI+Ru6hro=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 h1:KJVjPD3rcPb98rIs3HznyJlrfx9ge5oJvxxlGR+P/7s=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0/go.mod h1:K3kRa2ckmHWQaTWQdPRHc7qGXASuVuoEQXzrvlA98Ws=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 h1:lSZHgNHfbmQTPfuTmWVkEu8J8qXaQwuV30pjCcAUvP8=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0/go.mod h1:so9ounLcuoRDu033MW/E0AD4hhUjVqswrMF5FoZlBcw=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs=
|
||||
go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4=
|
||||
go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko=
|
||||
go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.19.0 h1:BEbF7ZBB6qQloV/Ub1+3NQoOUnVtcGkU3XX4Ws3GQfk=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.19.0/go.mod h1:Lua81/3yM0wOmoHTokLj9y9ADeA02v1naRrVrkAZuKk=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
|
||||
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
||||
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
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/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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=
|
||||
k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY=
|
||||
k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo=
|
||||
k8s.io/apiextensions-apiserver v0.36.1 h1:6JfYmPUsuUIHuN+3QxutXYWj492RqF5fBSx67GYK5Ks=
|
||||
k8s.io/apiextensions-apiserver v0.36.1/go.mod h1:pLzZin90riwisdzKwv/GoTwENooytoIx5zWJb4Hkby8=
|
||||
k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA=
|
||||
k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8=
|
||||
k8s.io/apiserver v0.36.1 h1:iMS5V+rPUertv5P9RaqJgmHHTuh4quWpoxchvMUY+JY=
|
||||
k8s.io/apiserver v0.36.1/go.mod h1:Cby1PbLWztu0GDOxoO6iFOyyqIsziHNEW+w9zVQ22Kw=
|
||||
k8s.io/cli-runtime v0.36.1 h1:yuC/BGnnj1YYPh6D1P+pZnzinCs6DvMq86yAeNqoqzM=
|
||||
k8s.io/cli-runtime v0.36.1/go.mod h1:ZQWHGt8xAF7KnviB79vX0lYNyUUqKIpU+LQg7exuFAw=
|
||||
k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0=
|
||||
k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU=
|
||||
k8s.io/component-base v0.36.1 h1:iG6GsELftXqTNG9HG6kiVjatSgAw1sf5pJ6R5a6N0kA=
|
||||
k8s.io/component-base v0.36.1/go.mod h1:nf9XPlntRdqO6WMeEWAA5F93Y4ICZQdeT9GeqLDB3JI=
|
||||
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
|
||||
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
|
||||
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
|
||||
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
|
||||
k8s.io/kubectl v0.36.1 h1:96HqS9twIdHM0MlJLTwbo14b9kUKPkOzZ4tlRDLv4qI=
|
||||
k8s.io/kubectl v0.36.1/go.mod h1:/DGPAIewKsFWF9VFgGvkPhao2Ev4SNuE3BioZo8yPbk=
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||
oras.land/oras-go/v2 v2.6.1 h1:bonOEkjLfp8tt6qXWRRWP6p1F+9octchOf2EqnWB4Zs=
|
||||
oras.land/oras-go/v2 v2.6.1/go.mod h1:dhtFrFOuZuDtAVeZ9FUnaa5zfzplG3ZnFX9/uH1J/Yk=
|
||||
sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4=
|
||||
sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs=
|
||||
sigs.k8s.io/kustomize/api v0.21.1/go.mod h1:f3wkKByTrgpgltLgySCntrYoq5d3q7aaxveSagwTlwI=
|
||||
sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI=
|
||||
sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ=
|
||||
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
||||
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||
@ -0,0 +1,180 @@
|
||||
/*
|
||||
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 v3
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"helm.sh/helm/v4/pkg/chart/common"
|
||||
)
|
||||
|
||||
// APIVersionV3 is the API version number for version 3.
|
||||
const APIVersionV3 = "v3"
|
||||
|
||||
// aliasNameFormat defines the characters that are legal in an alias name.
|
||||
var aliasNameFormat = regexp.MustCompile("^[a-zA-Z0-9_-]+$")
|
||||
|
||||
// Chart is a helm package that contains metadata, a default config, zero or more
|
||||
// optionally parameterizable templates, and zero or more charts (dependencies).
|
||||
type Chart struct {
|
||||
// Raw contains the raw contents of the files originally contained in the chart archive.
|
||||
//
|
||||
// This should not be used except in special cases like `helm show values`,
|
||||
// where we want to display the raw values, comments and all.
|
||||
Raw []*common.File `json:"-"`
|
||||
// Metadata is the contents of the Chartfile.
|
||||
Metadata *Metadata `json:"metadata"`
|
||||
// Lock is the contents of Chart.lock.
|
||||
Lock *Lock `json:"lock"`
|
||||
// Templates for this chart.
|
||||
Templates []*common.File `json:"templates"`
|
||||
// Values are default config for this chart.
|
||||
Values map[string]any `json:"values"`
|
||||
// Schema is an optional JSON schema for imposing structure on Values
|
||||
Schema []byte `json:"schema"`
|
||||
// SchemaModTime the schema was last modified
|
||||
SchemaModTime time.Time `json:"schemamodtime"`
|
||||
// Files are miscellaneous files in a chart archive,
|
||||
// e.g. README, LICENSE, etc.
|
||||
Files []*common.File `json:"files"`
|
||||
// ModTime the chart metadata was last modified
|
||||
ModTime time.Time `json:"modtime,omitzero"`
|
||||
|
||||
parent *Chart
|
||||
dependencies []*Chart
|
||||
}
|
||||
|
||||
type CRD struct {
|
||||
// Name is the File.Name for the crd file
|
||||
Name string
|
||||
// Filename is the File obj Name including (sub-)chart.ChartFullPath
|
||||
Filename string
|
||||
// File is the File obj for the crd
|
||||
File *common.File
|
||||
}
|
||||
|
||||
// SetDependencies replaces the chart dependencies.
|
||||
func (ch *Chart) SetDependencies(charts ...*Chart) {
|
||||
ch.dependencies = nil
|
||||
ch.AddDependency(charts...)
|
||||
}
|
||||
|
||||
// Name returns the name of the chart.
|
||||
func (ch *Chart) Name() string {
|
||||
if ch.Metadata == nil {
|
||||
return ""
|
||||
}
|
||||
return ch.Metadata.Name
|
||||
}
|
||||
|
||||
// AddDependency determines if the chart is a subchart.
|
||||
func (ch *Chart) AddDependency(charts ...*Chart) {
|
||||
for i, x := range charts {
|
||||
charts[i].parent = ch
|
||||
ch.dependencies = append(ch.dependencies, x)
|
||||
}
|
||||
}
|
||||
|
||||
// Root finds the root chart.
|
||||
func (ch *Chart) Root() *Chart {
|
||||
if ch.IsRoot() {
|
||||
return ch
|
||||
}
|
||||
return ch.Parent().Root()
|
||||
}
|
||||
|
||||
// Dependencies are the charts that this chart depends on.
|
||||
func (ch *Chart) Dependencies() []*Chart { return ch.dependencies }
|
||||
|
||||
// IsRoot determines if the chart is the root chart.
|
||||
func (ch *Chart) IsRoot() bool { return ch.parent == nil }
|
||||
|
||||
// Parent returns a subchart's parent chart.
|
||||
func (ch *Chart) Parent() *Chart { return ch.parent }
|
||||
|
||||
// ChartPath returns the full path to this chart in dot notation.
|
||||
func (ch *Chart) ChartPath() string {
|
||||
if !ch.IsRoot() {
|
||||
return ch.Parent().ChartPath() + "." + ch.Name()
|
||||
}
|
||||
return ch.Name()
|
||||
}
|
||||
|
||||
// ChartFullPath returns the full path to this chart.
|
||||
// Note that the path may not correspond to the path where the file can be found on the file system if the path
|
||||
// points to an aliased subchart.
|
||||
func (ch *Chart) ChartFullPath() string {
|
||||
if !ch.IsRoot() {
|
||||
return ch.Parent().ChartFullPath() + "/charts/" + ch.Name()
|
||||
}
|
||||
return ch.Name()
|
||||
}
|
||||
|
||||
// Validate validates the metadata.
|
||||
func (ch *Chart) Validate() error {
|
||||
return ch.Metadata.Validate()
|
||||
}
|
||||
|
||||
// AppVersion returns the appversion of the chart.
|
||||
func (ch *Chart) AppVersion() string {
|
||||
if ch.Metadata == nil {
|
||||
return ""
|
||||
}
|
||||
return ch.Metadata.AppVersion
|
||||
}
|
||||
|
||||
// CRDs returns a list of File objects in the 'crds/' directory of a Helm chart.
|
||||
//
|
||||
// Deprecated: use CRDObjects()
|
||||
func (ch *Chart) CRDs() []*common.File {
|
||||
files := []*common.File{}
|
||||
// Find all resources in the crds/ directory
|
||||
for _, f := range ch.Files {
|
||||
if strings.HasPrefix(f.Name, "crds/") && hasManifestExtension(f.Name) {
|
||||
files = append(files, f)
|
||||
}
|
||||
}
|
||||
// Get CRDs from dependencies, too.
|
||||
for _, dep := range ch.Dependencies() {
|
||||
files = append(files, dep.CRDs()...)
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
// CRDObjects returns a list of CRD objects in the 'crds/' directory of a Helm chart & subcharts
|
||||
func (ch *Chart) CRDObjects() []CRD {
|
||||
crds := []CRD{}
|
||||
// Find all resources in the crds/ directory
|
||||
for _, f := range ch.Files {
|
||||
if strings.HasPrefix(f.Name, "crds/") && hasManifestExtension(f.Name) {
|
||||
mycrd := CRD{Name: f.Name, Filename: filepath.Join(ch.ChartFullPath(), f.Name), File: f}
|
||||
crds = append(crds, mycrd)
|
||||
}
|
||||
}
|
||||
// Get CRDs from dependencies, too.
|
||||
for _, dep := range ch.Dependencies() {
|
||||
crds = append(crds, dep.CRDObjects()...)
|
||||
}
|
||||
return crds
|
||||
}
|
||||
|
||||
func hasManifestExtension(fname string) bool {
|
||||
ext := filepath.Ext(fname)
|
||||
return strings.EqualFold(ext, ".yaml") || strings.EqualFold(ext, ".yml") || strings.EqualFold(ext, ".json")
|
||||
}
|
||||
@ -0,0 +1,229 @@
|
||||
/*
|
||||
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 v3
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"helm.sh/helm/v4/pkg/chart/common"
|
||||
)
|
||||
|
||||
func TestCRDs(t *testing.T) {
|
||||
modTime := time.Now()
|
||||
chrt := Chart{
|
||||
Files: []*common.File{
|
||||
{
|
||||
Name: "crds/foo.yaml",
|
||||
ModTime: modTime,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
{
|
||||
Name: "bar.yaml",
|
||||
ModTime: modTime,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
{
|
||||
Name: "crds/foo/bar/baz.yaml",
|
||||
ModTime: modTime,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
{
|
||||
Name: "crdsfoo/bar/baz.yaml",
|
||||
ModTime: modTime,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
{
|
||||
Name: "crds/README.md",
|
||||
ModTime: modTime,
|
||||
Data: []byte("# hello"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
is := assert.New(t)
|
||||
crds := chrt.CRDs()
|
||||
is.Equal(2, len(crds))
|
||||
is.Equal("crds/foo.yaml", crds[0].Name)
|
||||
is.Equal("crds/foo/bar/baz.yaml", crds[1].Name)
|
||||
}
|
||||
|
||||
func TestSaveChartNoRawData(t *testing.T) {
|
||||
chrt := Chart{
|
||||
Raw: []*common.File{
|
||||
{
|
||||
Name: "fhqwhgads.yaml",
|
||||
ModTime: time.Now(),
|
||||
Data: []byte("Everybody to the Limit"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
is := assert.New(t)
|
||||
data, err := json.Marshal(chrt)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res := &Chart{}
|
||||
if err := json.Unmarshal(data, res); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
is.Equal([]*common.File(nil), res.Raw)
|
||||
}
|
||||
|
||||
func TestMetadata(t *testing.T) {
|
||||
chrt := Chart{
|
||||
Metadata: &Metadata{
|
||||
Name: "foo.yaml",
|
||||
AppVersion: "1.0.0",
|
||||
APIVersion: "v3",
|
||||
Version: "1.0.0",
|
||||
Type: "application",
|
||||
},
|
||||
}
|
||||
|
||||
is := assert.New(t)
|
||||
|
||||
is.Equal("foo.yaml", chrt.Name())
|
||||
is.Equal("1.0.0", chrt.AppVersion())
|
||||
is.NoError(chrt.Validate())
|
||||
}
|
||||
|
||||
func TestIsRoot(t *testing.T) {
|
||||
chrt1 := Chart{
|
||||
parent: &Chart{
|
||||
Metadata: &Metadata{
|
||||
Name: "foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
chrt2 := Chart{
|
||||
Metadata: &Metadata{
|
||||
Name: "foo",
|
||||
},
|
||||
}
|
||||
|
||||
is := assert.New(t)
|
||||
|
||||
is.False(chrt1.IsRoot())
|
||||
is.True(chrt2.IsRoot())
|
||||
}
|
||||
|
||||
func TestChartPath(t *testing.T) {
|
||||
chrt1 := Chart{
|
||||
parent: &Chart{
|
||||
Metadata: &Metadata{
|
||||
Name: "foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
chrt2 := Chart{
|
||||
Metadata: &Metadata{
|
||||
Name: "foo",
|
||||
},
|
||||
}
|
||||
|
||||
is := assert.New(t)
|
||||
|
||||
is.Equal("foo.", chrt1.ChartPath())
|
||||
is.Equal("foo", chrt2.ChartPath())
|
||||
}
|
||||
|
||||
func TestChartFullPath(t *testing.T) {
|
||||
chrt1 := Chart{
|
||||
parent: &Chart{
|
||||
Metadata: &Metadata{
|
||||
Name: "foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
chrt2 := Chart{
|
||||
Metadata: &Metadata{
|
||||
Name: "foo",
|
||||
},
|
||||
}
|
||||
|
||||
is := assert.New(t)
|
||||
|
||||
is.Equal("foo/charts/", chrt1.ChartFullPath())
|
||||
is.Equal("foo", chrt2.ChartFullPath())
|
||||
}
|
||||
|
||||
func TestCRDObjects(t *testing.T) {
|
||||
modTime := time.Now()
|
||||
chrt := Chart{
|
||||
Files: []*common.File{
|
||||
{
|
||||
Name: "crds/foo.yaml",
|
||||
ModTime: modTime,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
{
|
||||
Name: "bar.yaml",
|
||||
ModTime: modTime,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
{
|
||||
Name: "crds/foo/bar/baz.yaml",
|
||||
ModTime: modTime,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
{
|
||||
Name: "crdsfoo/bar/baz.yaml",
|
||||
ModTime: modTime,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
{
|
||||
Name: "crds/README.md",
|
||||
ModTime: modTime,
|
||||
Data: []byte("# hello"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected := []CRD{
|
||||
{
|
||||
Name: "crds/foo.yaml",
|
||||
Filename: "crds/foo.yaml",
|
||||
File: &common.File{
|
||||
Name: "crds/foo.yaml",
|
||||
ModTime: modTime,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "crds/foo/bar/baz.yaml",
|
||||
Filename: "crds/foo/bar/baz.yaml",
|
||||
File: &common.File{
|
||||
Name: "crds/foo/bar/baz.yaml",
|
||||
ModTime: modTime,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
is := assert.New(t)
|
||||
crds := chrt.CRDObjects()
|
||||
is.Equal(expected, crds)
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
/*
|
||||
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 v3
|
||||
|
||||
import "time"
|
||||
|
||||
// Dependency describes a chart upon which another chart depends.
|
||||
//
|
||||
// Dependencies can be used to express developer intent, or to capture the state
|
||||
// of a chart.
|
||||
type Dependency struct {
|
||||
// Name is the name of the dependency.
|
||||
//
|
||||
// This must mach the name in the dependency's Chart.yaml.
|
||||
Name string `json:"name" yaml:"name"`
|
||||
// Version is the version (range) of this chart.
|
||||
//
|
||||
// A lock file will always produce a single version, while a dependency
|
||||
// may contain a semantic version range.
|
||||
Version string `json:"version,omitempty" yaml:"version,omitempty"`
|
||||
// The URL to the repository.
|
||||
//
|
||||
// Appending `index.yaml` to this string should result in a URL that can be
|
||||
// used to fetch the repository index.
|
||||
Repository string `json:"repository" yaml:"repository"`
|
||||
// A yaml path that resolves to a boolean, used for enabling/disabling charts (e.g. subchart1.enabled )
|
||||
Condition string `json:"condition,omitempty" yaml:"condition,omitempty"`
|
||||
// Tags can be used to group charts for enabling/disabling together
|
||||
Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
|
||||
// Enabled bool determines if chart should be loaded
|
||||
Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"`
|
||||
// ImportValues holds the mapping of source values to parent key to be imported. Each item can be a
|
||||
// string or pair of child/parent sublist items.
|
||||
ImportValues []any `json:"import-values,omitempty" yaml:"import-values,omitempty"`
|
||||
// Alias usable alias to be used for the chart
|
||||
Alias string `json:"alias,omitempty" yaml:"alias,omitempty"`
|
||||
}
|
||||
|
||||
// Validate checks for common problems with the dependency datastructure in
|
||||
// the chart. This check must be done at load time before the dependency's charts are
|
||||
// loaded.
|
||||
func (d *Dependency) Validate() error {
|
||||
if d == nil {
|
||||
return ValidationError("dependencies must not contain empty or null nodes")
|
||||
}
|
||||
d.Name = sanitizeString(d.Name)
|
||||
d.Version = sanitizeString(d.Version)
|
||||
d.Repository = sanitizeString(d.Repository)
|
||||
d.Condition = sanitizeString(d.Condition)
|
||||
for i := range d.Tags {
|
||||
d.Tags[i] = sanitizeString(d.Tags[i])
|
||||
}
|
||||
if d.Alias != "" && !aliasNameFormat.MatchString(d.Alias) {
|
||||
return ValidationErrorf("dependency %q has disallowed characters in the alias", d.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lock is a lock file for dependencies.
|
||||
//
|
||||
// It represents the state that the dependencies should be in.
|
||||
type Lock struct {
|
||||
// Generated is the date the lock file was last generated.
|
||||
Generated time.Time `json:"generated"`
|
||||
// Digest is a hash of the dependencies in Chart.yaml.
|
||||
Digest string `json:"digest"`
|
||||
// Dependencies is the list of dependencies that this lock file has locked.
|
||||
Dependencies []*Dependency `json:"dependencies"`
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
/*
|
||||
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 v3
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateDependency(t *testing.T) {
|
||||
dep := &Dependency{
|
||||
Name: "example",
|
||||
}
|
||||
for value, shouldFail := range map[string]bool{
|
||||
"abcdefghijklmenopQRSTUVWXYZ-0123456780_": false,
|
||||
"-okay": false,
|
||||
"_okay": false,
|
||||
"- bad": true,
|
||||
" bad": true,
|
||||
"bad\nvalue": true,
|
||||
"bad ": true,
|
||||
"bad$": true,
|
||||
} {
|
||||
dep.Alias = value
|
||||
res := dep.Validate()
|
||||
if res != nil && !shouldFail {
|
||||
t.Errorf("Failed on case %q", dep.Alias)
|
||||
} else if res == nil && shouldFail {
|
||||
t.Errorf("Expected failure for %q", dep.Alias)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
/*
|
||||
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 v3 provides chart handling for apiVersion v3 charts
|
||||
|
||||
This package and its sub-packages provide handling for apiVersion v3 charts.
|
||||
*/
|
||||
package v3
|
||||
@ -0,0 +1,30 @@
|
||||
/*
|
||||
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 v3
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ValidationError represents a data validation error.
|
||||
type ValidationError string
|
||||
|
||||
func (v ValidationError) Error() string {
|
||||
return "validation: " + string(v)
|
||||
}
|
||||
|
||||
// ValidationErrorf takes a message and formatting options and creates a ValidationError
|
||||
func ValidationErrorf(msg string, args ...any) ValidationError {
|
||||
return ValidationError(fmt.Sprintf(msg, args...))
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
/*
|
||||
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 v3
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
fuzz "github.com/AdaLogics/go-fuzz-headers"
|
||||
)
|
||||
|
||||
func FuzzMetadataValidate(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
fdp := fuzz.NewConsumer(data)
|
||||
// Add random values to the metadata
|
||||
md := &Metadata{}
|
||||
err := fdp.GenerateStruct(md)
|
||||
if err != nil {
|
||||
t.Skip()
|
||||
}
|
||||
md.Validate()
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzDependencyValidate(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
f := fuzz.NewConsumer(data)
|
||||
// Add random values to the dependenci
|
||||
d := &Dependency{}
|
||||
err := f.GenerateStruct(d)
|
||||
if err != nil {
|
||||
t.Skip()
|
||||
}
|
||||
d.Validate()
|
||||
})
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
/*
|
||||
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 lint // import "helm.sh/helm/v4/internal/chart/v3/lint"
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"helm.sh/helm/v4/internal/chart/v3/lint/rules"
|
||||
"helm.sh/helm/v4/internal/chart/v3/lint/support"
|
||||
"helm.sh/helm/v4/pkg/chart/common"
|
||||
)
|
||||
|
||||
type linterOptions struct {
|
||||
KubeVersion *common.KubeVersion
|
||||
SkipSchemaValidation bool
|
||||
}
|
||||
|
||||
type LinterOption func(lo *linterOptions)
|
||||
|
||||
func WithKubeVersion(kubeVersion *common.KubeVersion) LinterOption {
|
||||
return func(lo *linterOptions) {
|
||||
lo.KubeVersion = kubeVersion
|
||||
}
|
||||
}
|
||||
|
||||
func WithSkipSchemaValidation(skipSchemaValidation bool) LinterOption {
|
||||
return func(lo *linterOptions) {
|
||||
lo.SkipSchemaValidation = skipSchemaValidation
|
||||
}
|
||||
}
|
||||
|
||||
func RunAll(baseDir string, values map[string]any, namespace string, options ...LinterOption) support.Linter {
|
||||
chartDir, _ := filepath.Abs(baseDir)
|
||||
|
||||
lo := linterOptions{}
|
||||
for _, option := range options {
|
||||
option(&lo)
|
||||
}
|
||||
|
||||
result := support.Linter{
|
||||
ChartDir: chartDir,
|
||||
}
|
||||
|
||||
rules.Chartfile(&result)
|
||||
rules.ValuesWithOverrides(&result, values, lo.SkipSchemaValidation)
|
||||
rules.TemplatesWithSkipSchemaValidation(&result, values, namespace, lo.KubeVersion, lo.SkipSchemaValidation)
|
||||
rules.Dependencies(&result)
|
||||
rules.Crds(&result)
|
||||
|
||||
return result
|
||||
}
|
||||
@ -0,0 +1,243 @@
|
||||
/*
|
||||
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 lint
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"helm.sh/helm/v4/internal/chart/v3/lint/support"
|
||||
chartutil "helm.sh/helm/v4/internal/chart/v3/util"
|
||||
)
|
||||
|
||||
const namespace = "testNamespace"
|
||||
|
||||
const badChartDir = "rules/testdata/badchartfile"
|
||||
const badValuesFileDir = "rules/testdata/badvaluesfile"
|
||||
const badYamlFileDir = "rules/testdata/albatross"
|
||||
const badCrdFileDir = "rules/testdata/badcrdfile"
|
||||
const goodChartDir = "rules/testdata/goodone"
|
||||
const subChartValuesDir = "rules/testdata/withsubchart"
|
||||
const malformedTemplate = "rules/testdata/malformed-template"
|
||||
const invalidChartFileDir = "rules/testdata/invalidchartfile"
|
||||
|
||||
func TestBadChartV3(t *testing.T) {
|
||||
var values map[string]any
|
||||
m := RunAll(badChartDir, values, namespace).Messages
|
||||
if len(m) != 8 {
|
||||
t.Errorf("Number of errors %v", len(m))
|
||||
t.Errorf("All didn't fail with expected errors, got %#v", m)
|
||||
}
|
||||
// There should be one INFO, one WARNING, and 2 ERROR messages, check for them
|
||||
var i, w, e, e2, e3, e4, e5, e6 bool
|
||||
for _, msg := range m {
|
||||
if msg.Severity == support.InfoSev {
|
||||
if strings.Contains(msg.Err.Error(), "icon is recommended") {
|
||||
i = true
|
||||
}
|
||||
}
|
||||
if msg.Severity == support.WarningSev {
|
||||
if strings.Contains(msg.Err.Error(), "does not exist") {
|
||||
w = true
|
||||
}
|
||||
}
|
||||
if msg.Severity == support.ErrorSev {
|
||||
if strings.Contains(msg.Err.Error(), "version '0.0.0.0' is not a valid SemVerV2") {
|
||||
e = true
|
||||
}
|
||||
if strings.Contains(msg.Err.Error(), "name is required") {
|
||||
e2 = true
|
||||
}
|
||||
|
||||
if strings.Contains(msg.Err.Error(), "apiVersion is required. The value must be \"v3\"") {
|
||||
e3 = true
|
||||
}
|
||||
|
||||
if strings.Contains(msg.Err.Error(), "chart type is not valid in apiVersion") {
|
||||
e4 = true
|
||||
}
|
||||
|
||||
if strings.Contains(msg.Err.Error(), "dependencies are not valid in the Chart file with apiVersion") {
|
||||
e5 = true
|
||||
}
|
||||
// This comes from the dependency check, which loads dependency info from the Chart.yaml
|
||||
if strings.Contains(msg.Err.Error(), "unable to load chart") {
|
||||
e6 = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !e || !e2 || !e3 || !e4 || !e5 || !i || !e6 || !w {
|
||||
t.Errorf("Didn't find all the expected errors, got %#v", m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidYaml(t *testing.T) {
|
||||
var values map[string]any
|
||||
m := RunAll(badYamlFileDir, values, namespace).Messages
|
||||
if len(m) != 1 {
|
||||
t.Fatalf("All didn't fail with expected errors, got %#v", m)
|
||||
}
|
||||
if !strings.Contains(m[0].Err.Error(), "deliberateSyntaxError") {
|
||||
t.Error("All didn't have the error for deliberateSyntaxError")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidChartYamlV3(t *testing.T) {
|
||||
var values map[string]any
|
||||
m := RunAll(invalidChartFileDir, values, namespace).Messages
|
||||
t.Log(m)
|
||||
if len(m) != 3 {
|
||||
t.Fatalf("All didn't fail with expected errors, got %#v", m)
|
||||
}
|
||||
if !strings.Contains(m[0].Err.Error(), "failed to strictly parse chart metadata file") {
|
||||
t.Error("All didn't have the error for duplicate YAML keys")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadValuesV3(t *testing.T) {
|
||||
var values map[string]any
|
||||
m := RunAll(badValuesFileDir, values, namespace).Messages
|
||||
if len(m) < 1 {
|
||||
t.Fatalf("All didn't fail with expected errors, got %#v", m)
|
||||
}
|
||||
if !strings.Contains(m[0].Err.Error(), "unable to parse YAML") {
|
||||
t.Errorf("All didn't have the error for invalid key format: %s", m[0].Err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadCrdFileV3(t *testing.T) {
|
||||
var values map[string]any
|
||||
m := RunAll(badCrdFileDir, values, namespace).Messages
|
||||
assert.Lenf(t, m, 2, "All didn't fail with expected errors, got %#v", m)
|
||||
assert.ErrorContains(t, m[0].Err, "apiVersion is not in 'apiextensions.k8s.io'")
|
||||
assert.ErrorContains(t, m[1].Err, "object kind is not 'CustomResourceDefinition'")
|
||||
}
|
||||
|
||||
func TestGoodChart(t *testing.T) {
|
||||
var values map[string]any
|
||||
m := RunAll(goodChartDir, values, namespace).Messages
|
||||
if len(m) != 0 {
|
||||
t.Error("All returned linter messages when it shouldn't have")
|
||||
for i, msg := range m {
|
||||
t.Logf("Message %d: %s", i, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestHelmCreateChart tests that a `helm create` always passes a `helm lint` test.
|
||||
//
|
||||
// See https://github.com/helm/helm/issues/7923
|
||||
func TestHelmCreateChart(t *testing.T) {
|
||||
var values map[string]any
|
||||
dir := t.TempDir()
|
||||
|
||||
createdChart, err := chartutil.Create("testhelmcreatepasseslint", dir)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
// Fatal is bad because of the defer.
|
||||
return
|
||||
}
|
||||
|
||||
// Note: we test with strict=true here, even though others have
|
||||
// strict = false.
|
||||
m := RunAll(createdChart, values, namespace, WithSkipSchemaValidation(true)).Messages
|
||||
if ll := len(m); ll != 1 {
|
||||
t.Errorf("All should have had exactly 1 error. Got %d", ll)
|
||||
for i, msg := range m {
|
||||
t.Logf("Message %d: %s", i, msg.Error())
|
||||
}
|
||||
} else if msg := m[0].Err.Error(); !strings.Contains(msg, "icon is recommended") {
|
||||
t.Errorf("Unexpected lint error: %s", msg)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHelmCreateChart_CheckDeprecatedWarnings checks if any default template created by `helm create` throws
|
||||
// deprecated warnings in the linter check against the current Kubernetes version (provided using ldflags).
|
||||
//
|
||||
// See https://github.com/helm/helm/issues/11495
|
||||
//
|
||||
// Resources like hpa and ingress, which are disabled by default in values.yaml are enabled here using the equivalent
|
||||
// of the `--set` flag.
|
||||
func TestHelmCreateChart_CheckDeprecatedWarnings(t *testing.T) {
|
||||
createdChart, err := chartutil.Create("checkdeprecatedwarnings", t.TempDir())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Add values to enable hpa, and ingress which are disabled by default.
|
||||
// This is the equivalent of:
|
||||
// helm lint checkdeprecatedwarnings --set 'autoscaling.enabled=true,ingress.enabled=true'
|
||||
updatedValues := map[string]any{
|
||||
"autoscaling": map[string]any{
|
||||
"enabled": true,
|
||||
},
|
||||
"ingress": map[string]any{
|
||||
"enabled": true,
|
||||
},
|
||||
}
|
||||
|
||||
linterRunDetails := RunAll(createdChart, updatedValues, namespace, WithSkipSchemaValidation(true))
|
||||
for _, msg := range linterRunDetails.Messages {
|
||||
if strings.HasPrefix(msg.Error(), "[WARNING]") &&
|
||||
strings.Contains(msg.Error(), "deprecated") {
|
||||
// When there is a deprecation warning for an object created
|
||||
// by `helm create` for the current Kubernetes version, fail.
|
||||
t.Errorf("Unexpected deprecation warning for %q: %s", msg.Path, msg.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lint ignores import-values
|
||||
// See https://github.com/helm/helm/issues/9658
|
||||
func TestSubChartValuesChart(t *testing.T) {
|
||||
var values map[string]any
|
||||
m := RunAll(subChartValuesDir, values, namespace).Messages
|
||||
if len(m) != 0 {
|
||||
t.Error("All returned linter messages when it shouldn't have")
|
||||
for i, msg := range m {
|
||||
t.Logf("Message %d: %s", i, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lint stuck with malformed template object
|
||||
// See https://github.com/helm/helm/issues/11391
|
||||
func TestMalformedTemplate(t *testing.T) {
|
||||
var values map[string]any
|
||||
c := time.After(3 * time.Second)
|
||||
ch := make(chan int, 1)
|
||||
var m []support.Message
|
||||
go func() {
|
||||
m = RunAll(malformedTemplate, values, namespace).Messages
|
||||
ch <- 1
|
||||
}()
|
||||
select {
|
||||
case <-c:
|
||||
t.Fatal("lint malformed template timeout")
|
||||
case <-ch:
|
||||
if len(m) != 1 {
|
||||
t.Fatalf("All didn't fail with expected errors, got %#v", m)
|
||||
}
|
||||
if !strings.Contains(m[0].Err.Error(), "invalid character '{'") {
|
||||
t.Error("All didn't have the error for invalid character '{'")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,225 @@
|
||||
/*
|
||||
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 rules // import "helm.sh/helm/v4/internal/chart/v3/lint/rules"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/asaskevich/govalidator"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
chart "helm.sh/helm/v4/internal/chart/v3"
|
||||
"helm.sh/helm/v4/internal/chart/v3/lint/support"
|
||||
chartutil "helm.sh/helm/v4/internal/chart/v3/util"
|
||||
)
|
||||
|
||||
// Chartfile runs a set of linter rules related to Chart.yaml file
|
||||
func Chartfile(linter *support.Linter) {
|
||||
chartFileName := "Chart.yaml"
|
||||
chartPath := filepath.Join(linter.ChartDir, chartFileName)
|
||||
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartYamlNotDirectory(chartPath))
|
||||
|
||||
chartFile, err := chartutil.LoadChartfile(chartPath)
|
||||
validChartFile := linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartYamlFormat(err))
|
||||
|
||||
// Guard clause. Following linter rules require a parsable ChartFile
|
||||
if !validChartFile {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = chartutil.StrictLoadChartfile(chartPath)
|
||||
linter.RunLinterRule(support.WarningSev, chartFileName, validateChartYamlStrictFormat(err))
|
||||
|
||||
// type check for Chart.yaml . ignoring error as any parse
|
||||
// errors would already be caught in the above load function
|
||||
chartFileForTypeCheck, _ := loadChartFileForTypeCheck(chartPath)
|
||||
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartName(chartFile))
|
||||
|
||||
// Chart metadata
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartAPIVersion(chartFile))
|
||||
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartVersionType(chartFileForTypeCheck))
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartVersion(chartFile))
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartAppVersionType(chartFileForTypeCheck))
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartMaintainer(chartFile))
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartSources(chartFile))
|
||||
linter.RunLinterRule(support.InfoSev, chartFileName, validateChartIconPresence(chartFile))
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartIconURL(chartFile))
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartType(chartFile))
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartDependencies(chartFile))
|
||||
}
|
||||
|
||||
func validateChartVersionType(data map[string]any) error {
|
||||
return isStringValue(data, "version")
|
||||
}
|
||||
|
||||
func validateChartAppVersionType(data map[string]any) error {
|
||||
return isStringValue(data, "appVersion")
|
||||
}
|
||||
|
||||
func isStringValue(data map[string]any, key string) error {
|
||||
value, ok := data[key]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
valueType := fmt.Sprintf("%T", value)
|
||||
if valueType != "string" {
|
||||
return fmt.Errorf("%s should be of type string but it's of type %s", key, valueType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartYamlNotDirectory(chartPath string) error {
|
||||
fi, err := os.Stat(chartPath)
|
||||
|
||||
if err == nil && fi.IsDir() {
|
||||
return errors.New("should be a file, not a directory")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartYamlFormat(chartFileError error) error {
|
||||
if chartFileError != nil {
|
||||
return fmt.Errorf("unable to parse YAML\n\t%w", chartFileError)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartYamlStrictFormat(chartFileError error) error {
|
||||
if chartFileError != nil {
|
||||
return fmt.Errorf("failed to strictly parse chart metadata file\n\t%w", chartFileError)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartName(cf *chart.Metadata) error {
|
||||
if cf.Name == "" {
|
||||
return errors.New("name is required")
|
||||
}
|
||||
name := filepath.Base(cf.Name)
|
||||
if name != cf.Name {
|
||||
return fmt.Errorf("chart name %q is invalid", cf.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartAPIVersion(cf *chart.Metadata) error {
|
||||
if cf.APIVersion == "" {
|
||||
return errors.New("apiVersion is required. The value must be \"v3\"")
|
||||
}
|
||||
|
||||
if cf.APIVersion != chart.APIVersionV3 {
|
||||
return fmt.Errorf("apiVersion '%s' is not valid. The value must be \"v3\"", cf.APIVersion)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartVersion(cf *chart.Metadata) error {
|
||||
if cf.Version == "" {
|
||||
return errors.New("version is required")
|
||||
}
|
||||
|
||||
version, err := semver.StrictNewVersion(cf.Version)
|
||||
if err != nil {
|
||||
return fmt.Errorf("version '%s' is not a valid SemVerV2", cf.Version)
|
||||
}
|
||||
|
||||
c, err := semver.NewConstraint(">0.0.0-0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valid, msg := c.Validate(version)
|
||||
|
||||
if !valid && len(msg) > 0 {
|
||||
return fmt.Errorf("version %w", msg[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartMaintainer(cf *chart.Metadata) error {
|
||||
for _, maintainer := range cf.Maintainers {
|
||||
if maintainer == nil {
|
||||
return errors.New("a maintainer entry is empty")
|
||||
}
|
||||
if maintainer.Name == "" {
|
||||
return errors.New("each maintainer requires a name")
|
||||
} else if maintainer.Email != "" && !govalidator.IsEmail(maintainer.Email) {
|
||||
return fmt.Errorf("invalid email '%s' for maintainer '%s'", maintainer.Email, maintainer.Name)
|
||||
} else if maintainer.URL != "" && !govalidator.IsURL(maintainer.URL) {
|
||||
return fmt.Errorf("invalid url '%s' for maintainer '%s'", maintainer.URL, maintainer.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartSources(cf *chart.Metadata) error {
|
||||
for _, source := range cf.Sources {
|
||||
if source == "" || !govalidator.IsRequestURL(source) {
|
||||
return fmt.Errorf("invalid source URL '%s'", source)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartIconPresence(cf *chart.Metadata) error {
|
||||
if cf.Icon == "" {
|
||||
return errors.New("icon is recommended")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartIconURL(cf *chart.Metadata) error {
|
||||
if cf.Icon != "" && !govalidator.IsRequestURL(cf.Icon) {
|
||||
return fmt.Errorf("invalid icon URL '%s'", cf.Icon)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartDependencies(cf *chart.Metadata) error {
|
||||
if len(cf.Dependencies) > 0 && cf.APIVersion != chart.APIVersionV3 {
|
||||
return fmt.Errorf("dependencies are not valid in the Chart file with apiVersion '%s'. They are valid in apiVersion '%s'", cf.APIVersion, chart.APIVersionV3)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartType(cf *chart.Metadata) error {
|
||||
if len(cf.Type) > 0 && cf.APIVersion != chart.APIVersionV3 {
|
||||
return fmt.Errorf("chart type is not valid in apiVersion '%s'. It is valid in apiVersion '%s'", cf.APIVersion, chart.APIVersionV3)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadChartFileForTypeCheck loads the Chart.yaml
|
||||
// in a generic form of a map[string]interface{}, so that the type
|
||||
// of the values can be checked
|
||||
func loadChartFileForTypeCheck(filename string) (map[string]any, error) {
|
||||
b, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
y := make(map[string]any)
|
||||
err = yaml.Unmarshal(b, &y)
|
||||
return y, err
|
||||
}
|
||||
@ -0,0 +1,278 @@
|
||||
/*
|
||||
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 rules
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
chart "helm.sh/helm/v4/internal/chart/v3"
|
||||
"helm.sh/helm/v4/internal/chart/v3/lint/support"
|
||||
chartutil "helm.sh/helm/v4/internal/chart/v3/util"
|
||||
)
|
||||
|
||||
const (
|
||||
badChartNameDir = "testdata/badchartname"
|
||||
badChartDir = "testdata/badchartfile"
|
||||
anotherBadChartDir = "testdata/anotherbadchartfile"
|
||||
)
|
||||
|
||||
var (
|
||||
badChartNamePath = filepath.Join(badChartNameDir, "Chart.yaml")
|
||||
badChartFilePath = filepath.Join(badChartDir, "Chart.yaml")
|
||||
nonExistingChartFilePath = filepath.Join(os.TempDir(), "Chart.yaml")
|
||||
)
|
||||
|
||||
var badChart, _ = chartutil.LoadChartfile(badChartFilePath)
|
||||
var badChartName, _ = chartutil.LoadChartfile(badChartNamePath)
|
||||
|
||||
// Validation functions Test
|
||||
func TestValidateChartYamlNotDirectory(t *testing.T) {
|
||||
_ = os.Mkdir(nonExistingChartFilePath, os.ModePerm)
|
||||
defer os.Remove(nonExistingChartFilePath)
|
||||
|
||||
err := validateChartYamlNotDirectory(nonExistingChartFilePath)
|
||||
if err == nil {
|
||||
t.Error("validateChartYamlNotDirectory to return a linter error, got no error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateChartYamlFormat(t *testing.T) {
|
||||
err := validateChartYamlFormat(errors.New("Read error"))
|
||||
if err == nil {
|
||||
t.Error("validateChartYamlFormat to return a linter error, got no error")
|
||||
}
|
||||
|
||||
err = validateChartYamlFormat(nil)
|
||||
if err != nil {
|
||||
t.Error("validateChartYamlFormat to return no error, got a linter error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateChartName(t *testing.T) {
|
||||
err := validateChartName(badChart)
|
||||
if err == nil {
|
||||
t.Error("validateChartName to return a linter error, got no error")
|
||||
}
|
||||
|
||||
err = validateChartName(badChartName)
|
||||
if err == nil {
|
||||
t.Error("expected validateChartName to return a linter error for an invalid name, got no error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateChartVersion(t *testing.T) {
|
||||
var failTest = []struct {
|
||||
Version string
|
||||
ErrorMsg string
|
||||
}{
|
||||
{"", "version is required"},
|
||||
{"1.2.3.4", "version '1.2.3.4' is not a valid SemVerV2"},
|
||||
{"waps", "'waps' is not a valid SemVerV2"},
|
||||
{"-3", "'-3' is not a valid SemVerV2"},
|
||||
{"1.1", "'1.1' is not a valid SemVerV2"},
|
||||
{"1", "'1' is not a valid SemVerV2"},
|
||||
}
|
||||
|
||||
var successTest = []string{"0.0.1", "0.0.1+build", "0.0.1-beta"}
|
||||
|
||||
for _, test := range failTest {
|
||||
badChart.Version = test.Version
|
||||
err := validateChartVersion(badChart)
|
||||
if err == nil || !strings.Contains(err.Error(), test.ErrorMsg) {
|
||||
t.Errorf("validateChartVersion(%s) to return \"%s\", got no error", test.Version, test.ErrorMsg)
|
||||
}
|
||||
}
|
||||
|
||||
for _, version := range successTest {
|
||||
badChart.Version = version
|
||||
err := validateChartVersion(badChart)
|
||||
if err != nil {
|
||||
t.Errorf("validateChartVersion(%s) to return no error, got a linter error", version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateChartMaintainer(t *testing.T) {
|
||||
var failTest = []struct {
|
||||
Name string
|
||||
Email string
|
||||
ErrorMsg string
|
||||
}{
|
||||
{"", "", "each maintainer requires a name"},
|
||||
{"", "test@test.com", "each maintainer requires a name"},
|
||||
{"John Snow", "wrongFormatEmail.com", "invalid email"},
|
||||
}
|
||||
|
||||
var successTest = []struct {
|
||||
Name string
|
||||
Email string
|
||||
}{
|
||||
{"John Snow", ""},
|
||||
{"John Snow", "john@winterfell.com"},
|
||||
}
|
||||
|
||||
for _, test := range failTest {
|
||||
badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}}
|
||||
err := validateChartMaintainer(badChart)
|
||||
if err == nil || !strings.Contains(err.Error(), test.ErrorMsg) {
|
||||
t.Errorf("validateChartMaintainer(%s, %s) to return \"%s\", got no error", test.Name, test.Email, test.ErrorMsg)
|
||||
}
|
||||
}
|
||||
|
||||
for _, test := range successTest {
|
||||
badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}}
|
||||
err := validateChartMaintainer(badChart)
|
||||
if err != nil {
|
||||
t.Errorf("validateChartMaintainer(%s, %s) to return no error, got %s", test.Name, test.Email, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Testing for an empty maintainer
|
||||
badChart.Maintainers = []*chart.Maintainer{nil}
|
||||
err := validateChartMaintainer(badChart)
|
||||
if err == nil {
|
||||
t.Error("validateChartMaintainer did not return error for nil maintainer as expected")
|
||||
}
|
||||
if err.Error() != "a maintainer entry is empty" {
|
||||
t.Errorf("validateChartMaintainer returned unexpected error for nil maintainer: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateChartSources(t *testing.T) {
|
||||
var failTest = []string{"", "RiverRun", "john@winterfell", "riverrun.io"}
|
||||
var successTest = []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish"}
|
||||
for _, test := range failTest {
|
||||
badChart.Sources = []string{test}
|
||||
err := validateChartSources(badChart)
|
||||
if err == nil || !strings.Contains(err.Error(), "invalid source URL") {
|
||||
t.Errorf("validateChartSources(%s) to return \"invalid source URL\", got no error", test)
|
||||
}
|
||||
}
|
||||
|
||||
for _, test := range successTest {
|
||||
badChart.Sources = []string{test}
|
||||
err := validateChartSources(badChart)
|
||||
if err != nil {
|
||||
t.Errorf("validateChartSources(%s) to return no error, got %s", test, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateChartIconPresence(t *testing.T) {
|
||||
t.Run("Icon absent", func(t *testing.T) {
|
||||
testChart := &chart.Metadata{
|
||||
Icon: "",
|
||||
}
|
||||
|
||||
err := validateChartIconPresence(testChart)
|
||||
|
||||
if err == nil {
|
||||
t.Error("validateChartIconPresence to return a linter error, got no error")
|
||||
} else if !strings.Contains(err.Error(), "icon is recommended") {
|
||||
t.Errorf("expected %q, got %q", "icon is recommended", err.Error())
|
||||
}
|
||||
})
|
||||
t.Run("Icon present", func(t *testing.T) {
|
||||
testChart := &chart.Metadata{
|
||||
Icon: "http://example.org/icon.png",
|
||||
}
|
||||
|
||||
err := validateChartIconPresence(testChart)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %q", err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidateChartIconURL(t *testing.T) {
|
||||
var failTest = []string{"RiverRun", "john@winterfell", "riverrun.io"}
|
||||
var successTest = []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish.png"}
|
||||
for _, test := range failTest {
|
||||
badChart.Icon = test
|
||||
err := validateChartIconURL(badChart)
|
||||
if err == nil || !strings.Contains(err.Error(), "invalid icon URL") {
|
||||
t.Errorf("validateChartIconURL(%s) to return \"invalid icon URL\", got no error", test)
|
||||
}
|
||||
}
|
||||
|
||||
for _, test := range successTest {
|
||||
badChart.Icon = test
|
||||
err := validateChartSources(badChart)
|
||||
if err != nil {
|
||||
t.Errorf("validateChartIconURL(%s) to return no error, got %s", test, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestV3Chartfile(t *testing.T) {
|
||||
t.Run("Chart.yaml basic validity issues", func(t *testing.T) {
|
||||
linter := support.Linter{ChartDir: badChartDir}
|
||||
Chartfile(&linter)
|
||||
msgs := linter.Messages
|
||||
expectedNumberOfErrorMessages := 6
|
||||
|
||||
if len(msgs) != expectedNumberOfErrorMessages {
|
||||
t.Errorf("Expected %d errors, got %d", expectedNumberOfErrorMessages, len(msgs))
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.Contains(msgs[0].Err.Error(), "name is required") {
|
||||
t.Errorf("Unexpected message 0: %s", msgs[0].Err)
|
||||
}
|
||||
|
||||
if !strings.Contains(msgs[1].Err.Error(), "apiVersion is required. The value must be \"v3\"") {
|
||||
t.Errorf("Unexpected message 1: %s", msgs[1].Err)
|
||||
}
|
||||
|
||||
if !strings.Contains(msgs[2].Err.Error(), "version '0.0.0.0' is not a valid SemVer") {
|
||||
t.Errorf("Unexpected message 2: %s", msgs[2].Err)
|
||||
}
|
||||
|
||||
if !strings.Contains(msgs[3].Err.Error(), "icon is recommended") {
|
||||
t.Errorf("Unexpected message 3: %s", msgs[3].Err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Chart.yaml validity issues due to type mismatch", func(t *testing.T) {
|
||||
linter := support.Linter{ChartDir: anotherBadChartDir}
|
||||
Chartfile(&linter)
|
||||
msgs := linter.Messages
|
||||
expectedNumberOfErrorMessages := 3
|
||||
|
||||
if len(msgs) != expectedNumberOfErrorMessages {
|
||||
t.Errorf("Expected %d errors, got %d", expectedNumberOfErrorMessages, len(msgs))
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.Contains(msgs[0].Err.Error(), "version should be of type string") {
|
||||
t.Errorf("Unexpected message 0: %s", msgs[0].Err)
|
||||
}
|
||||
|
||||
if !strings.Contains(msgs[1].Err.Error(), "version '7.2445e+06' is not a valid SemVer") {
|
||||
t.Errorf("Unexpected message 1: %s", msgs[1].Err)
|
||||
}
|
||||
|
||||
if !strings.Contains(msgs[2].Err.Error(), "appVersion should be of type string") {
|
||||
t.Errorf("Unexpected message 2: %s", msgs[2].Err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -0,0 +1,114 @@
|
||||
/*
|
||||
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 rules
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
|
||||
"helm.sh/helm/v4/internal/chart/v3/lint/support"
|
||||
"helm.sh/helm/v4/internal/chart/v3/loader"
|
||||
)
|
||||
|
||||
// Crds lints the CRDs in the Linter.
|
||||
func Crds(linter *support.Linter) {
|
||||
fpath := "crds/"
|
||||
crdsPath := filepath.Join(linter.ChartDir, fpath)
|
||||
|
||||
// crds directory is optional
|
||||
if _, err := os.Stat(crdsPath); errors.Is(err, fs.ErrNotExist) {
|
||||
return
|
||||
}
|
||||
|
||||
crdsDirValid := linter.RunLinterRule(support.ErrorSev, fpath, validateCrdsDir(crdsPath))
|
||||
if !crdsDirValid {
|
||||
return
|
||||
}
|
||||
|
||||
// Load chart and parse CRDs
|
||||
chart, err := loader.Load(linter.ChartDir)
|
||||
|
||||
chartLoaded := linter.RunLinterRule(support.ErrorSev, fpath, err)
|
||||
|
||||
if !chartLoaded {
|
||||
return
|
||||
}
|
||||
|
||||
/* Iterate over all the CRDs to check:
|
||||
1. It is a YAML file and not a template
|
||||
2. The API version is apiextensions.k8s.io
|
||||
3. The kind is CustomResourceDefinition
|
||||
*/
|
||||
for _, crd := range chart.CRDObjects() {
|
||||
fileName := crd.Name
|
||||
fpath = fileName
|
||||
|
||||
decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(crd.File.Data), 4096)
|
||||
for {
|
||||
var yamlStruct *k8sYamlStruct
|
||||
|
||||
err := decoder.Decode(&yamlStruct)
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
|
||||
// If YAML parsing fails here, it will always fail in the next block as well, so we should return here.
|
||||
// This also confirms the YAML is not a template, since templates can't be decoded into a K8sYamlStruct.
|
||||
if !linter.RunLinterRule(support.ErrorSev, fpath, validateYamlContent(err)) {
|
||||
return
|
||||
}
|
||||
|
||||
if yamlStruct != nil {
|
||||
linter.RunLinterRule(support.ErrorSev, fpath, validateCrdAPIVersion(yamlStruct))
|
||||
linter.RunLinterRule(support.ErrorSev, fpath, validateCrdKind(yamlStruct))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validation functions
|
||||
func validateCrdsDir(crdsPath string) error {
|
||||
fi, err := os.Stat(crdsPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return errors.New("not a directory")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateCrdAPIVersion(obj *k8sYamlStruct) error {
|
||||
if !strings.HasPrefix(obj.APIVersion, "apiextensions.k8s.io") {
|
||||
return errors.New("apiVersion is not in 'apiextensions.k8s.io'")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateCrdKind(obj *k8sYamlStruct) error {
|
||||
if obj.Kind != "CustomResourceDefinition" {
|
||||
return errors.New("object kind is not 'CustomResourceDefinition'")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
/*
|
||||
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 rules
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"helm.sh/helm/v4/internal/chart/v3/lint/support"
|
||||
)
|
||||
|
||||
const invalidCrdsDir = "./testdata/invalidcrdsdir"
|
||||
|
||||
func TestInvalidCrdsDir(t *testing.T) {
|
||||
linter := support.Linter{ChartDir: invalidCrdsDir}
|
||||
Crds(&linter)
|
||||
res := linter.Messages
|
||||
|
||||
assert.Len(t, res, 1)
|
||||
assert.ErrorContains(t, res[0].Err, "not a directory")
|
||||
}
|
||||
|
||||
// multi-document YAML with empty documents would panic
|
||||
func TestCrdWithEmptyDocument(t *testing.T) {
|
||||
chartDir := t.TempDir()
|
||||
|
||||
os.WriteFile(filepath.Join(chartDir, "Chart.yaml"), []byte(
|
||||
`apiVersion: v1
|
||||
name: test
|
||||
version: 0.1.0
|
||||
`), 0644)
|
||||
|
||||
// CRD with comments before --- (creates empty document)
|
||||
crdsDir := filepath.Join(chartDir, "crds")
|
||||
os.Mkdir(crdsDir, 0755)
|
||||
os.WriteFile(filepath.Join(crdsDir, "test.yaml"), []byte(
|
||||
`# Comments create empty document
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: test.example.io
|
||||
`), 0644)
|
||||
|
||||
linter := support.Linter{ChartDir: chartDir}
|
||||
Crds(&linter)
|
||||
|
||||
assert.Len(t, linter.Messages, 0)
|
||||
}
|
||||
@ -0,0 +1,101 @@
|
||||
/*
|
||||
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 rules // import "helm.sh/helm/v4/internal/chart/v3/lint/rules"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
chart "helm.sh/helm/v4/internal/chart/v3"
|
||||
"helm.sh/helm/v4/internal/chart/v3/lint/support"
|
||||
"helm.sh/helm/v4/internal/chart/v3/loader"
|
||||
)
|
||||
|
||||
// Dependencies runs lints against a chart's dependencies
|
||||
//
|
||||
// See https://github.com/helm/helm/issues/7910
|
||||
func Dependencies(linter *support.Linter) {
|
||||
c, err := loader.LoadDir(linter.ChartDir)
|
||||
if !linter.RunLinterRule(support.ErrorSev, "", validateChartFormat(err)) {
|
||||
return
|
||||
}
|
||||
|
||||
linter.RunLinterRule(support.ErrorSev, linter.ChartDir, validateDependencyInMetadata(c))
|
||||
linter.RunLinterRule(support.ErrorSev, linter.ChartDir, validateDependenciesUnique(c))
|
||||
linter.RunLinterRule(support.WarningSev, linter.ChartDir, validateDependencyInChartsDir(c))
|
||||
}
|
||||
|
||||
func validateChartFormat(chartError error) error {
|
||||
if chartError != nil {
|
||||
return fmt.Errorf("unable to load chart\n\t%w", chartError)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDependencyInChartsDir(c *chart.Chart) (err error) {
|
||||
dependencies := map[string]struct{}{}
|
||||
missing := []string{}
|
||||
for _, dep := range c.Dependencies() {
|
||||
dependencies[dep.Metadata.Name] = struct{}{}
|
||||
}
|
||||
for _, dep := range c.Metadata.Dependencies {
|
||||
if _, ok := dependencies[dep.Name]; !ok {
|
||||
missing = append(missing, dep.Name)
|
||||
}
|
||||
}
|
||||
if len(missing) > 0 {
|
||||
err = fmt.Errorf("chart directory is missing these dependencies: %s", strings.Join(missing, ","))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func validateDependencyInMetadata(c *chart.Chart) (err error) {
|
||||
dependencies := map[string]struct{}{}
|
||||
missing := []string{}
|
||||
for _, dep := range c.Metadata.Dependencies {
|
||||
dependencies[dep.Name] = struct{}{}
|
||||
}
|
||||
for _, dep := range c.Dependencies() {
|
||||
if _, ok := dependencies[dep.Metadata.Name]; !ok {
|
||||
missing = append(missing, dep.Metadata.Name)
|
||||
}
|
||||
}
|
||||
if len(missing) > 0 {
|
||||
err = fmt.Errorf("chart metadata is missing these dependencies: %s", strings.Join(missing, ","))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func validateDependenciesUnique(c *chart.Chart) (err error) {
|
||||
dependencies := map[string]*chart.Dependency{}
|
||||
shadowing := []string{}
|
||||
|
||||
for _, dep := range c.Metadata.Dependencies {
|
||||
key := dep.Name
|
||||
if dep.Alias != "" {
|
||||
key = dep.Alias
|
||||
}
|
||||
if dependencies[key] != nil {
|
||||
shadowing = append(shadowing, key)
|
||||
}
|
||||
dependencies[key] = dep
|
||||
}
|
||||
if len(shadowing) > 0 {
|
||||
err = fmt.Errorf("multiple dependencies with name or alias: %s", strings.Join(shadowing, ","))
|
||||
}
|
||||
return err
|
||||
}
|
||||
@ -0,0 +1,157 @@
|
||||
/*
|
||||
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 rules
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
chart "helm.sh/helm/v4/internal/chart/v3"
|
||||
"helm.sh/helm/v4/internal/chart/v3/lint/support"
|
||||
chartutil "helm.sh/helm/v4/internal/chart/v3/util"
|
||||
)
|
||||
|
||||
func chartWithBadDependencies() chart.Chart {
|
||||
badChartDeps := chart.Chart{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "badchart",
|
||||
Version: "0.1.0",
|
||||
APIVersion: "v2",
|
||||
Dependencies: []*chart.Dependency{
|
||||
{
|
||||
Name: "sub2",
|
||||
},
|
||||
{
|
||||
Name: "sub3",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
badChartDeps.SetDependencies(
|
||||
&chart.Chart{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "sub1",
|
||||
Version: "0.1.0",
|
||||
APIVersion: "v2",
|
||||
},
|
||||
},
|
||||
&chart.Chart{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "sub2",
|
||||
Version: "0.1.0",
|
||||
APIVersion: "v2",
|
||||
},
|
||||
},
|
||||
)
|
||||
return badChartDeps
|
||||
}
|
||||
|
||||
func TestValidateDependencyInChartsDir(t *testing.T) {
|
||||
c := chartWithBadDependencies()
|
||||
|
||||
if err := validateDependencyInChartsDir(&c); err == nil {
|
||||
t.Error("chart should have been flagged for missing deps in chart directory")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDependencyInMetadata(t *testing.T) {
|
||||
c := chartWithBadDependencies()
|
||||
|
||||
if err := validateDependencyInMetadata(&c); err == nil {
|
||||
t.Error("chart should have been flagged for missing deps in chart metadata")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDependenciesUnique(t *testing.T) {
|
||||
tests := []struct {
|
||||
chart chart.Chart
|
||||
}{
|
||||
{chart.Chart{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "badchart",
|
||||
Version: "0.1.0",
|
||||
APIVersion: "v2",
|
||||
Dependencies: []*chart.Dependency{
|
||||
{
|
||||
Name: "foo",
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
{chart.Chart{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "badchart",
|
||||
Version: "0.1.0",
|
||||
APIVersion: "v2",
|
||||
Dependencies: []*chart.Dependency{
|
||||
{
|
||||
Name: "foo",
|
||||
Alias: "bar",
|
||||
},
|
||||
{
|
||||
Name: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
{chart.Chart{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "badchart",
|
||||
Version: "0.1.0",
|
||||
APIVersion: "v2",
|
||||
Dependencies: []*chart.Dependency{
|
||||
{
|
||||
Name: "foo",
|
||||
Alias: "baz",
|
||||
},
|
||||
{
|
||||
Name: "bar",
|
||||
Alias: "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if err := validateDependenciesUnique(&tt.chart); err == nil {
|
||||
t.Error("chart should have been flagged for dependency shadowing")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDependencies(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
|
||||
c := chartWithBadDependencies()
|
||||
err := chartutil.SaveDir(&c, tmp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
linter := support.Linter{ChartDir: filepath.Join(tmp, c.Metadata.Name)}
|
||||
|
||||
Dependencies(&linter)
|
||||
if l := len(linter.Messages); l != 2 {
|
||||
t.Errorf("expected 2 linter errors for bad chart dependencies. Got %d.", l)
|
||||
for i, msg := range linter.Messages {
|
||||
t.Logf("Message: %d, Error: %#v", i, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
/*
|
||||
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 rules // import "helm.sh/helm/v4/internal/chart/v3/lint/rules"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"helm.sh/helm/v4/pkg/chart/common"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/endpoints/deprecation"
|
||||
kscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
// deprecatedAPIError indicates that an API is deprecated in Kubernetes
|
||||
type deprecatedAPIError struct {
|
||||
Deprecated string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e deprecatedAPIError) Error() string {
|
||||
msg := e.Message
|
||||
return msg
|
||||
}
|
||||
|
||||
func validateNoDeprecations(resource *k8sYamlStruct, kubeVersion *common.KubeVersion) error {
|
||||
// if `resource` does not have an APIVersion or Kind, we cannot test it for deprecation
|
||||
if resource.APIVersion == "" {
|
||||
return nil
|
||||
}
|
||||
if resource.Kind == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if kubeVersion == nil {
|
||||
kubeVersion = &common.DefaultCapabilities.KubeVersion
|
||||
}
|
||||
|
||||
kubeVersionMajor, err := strconv.Atoi(kubeVersion.Major)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kubeVersionMinor, err := strconv.Atoi(kubeVersion.Minor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
runtimeObject, err := resourceToRuntimeObject(resource)
|
||||
if err != nil {
|
||||
// do not error for non-kubernetes resources
|
||||
if runtime.IsNotRegisteredError(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if !deprecation.IsDeprecated(runtimeObject, kubeVersionMajor, kubeVersionMinor) {
|
||||
return nil
|
||||
}
|
||||
gvk := fmt.Sprintf("%s %s", resource.APIVersion, resource.Kind)
|
||||
return deprecatedAPIError{
|
||||
Deprecated: gvk,
|
||||
Message: deprecation.WarningMessage(runtimeObject),
|
||||
}
|
||||
}
|
||||
|
||||
func resourceToRuntimeObject(resource *k8sYamlStruct) (runtime.Object, error) {
|
||||
scheme := runtime.NewScheme()
|
||||
kscheme.AddToScheme(scheme)
|
||||
|
||||
gvk := schema.FromAPIVersionAndKind(resource.APIVersion, resource.Kind)
|
||||
out, err := scheme.New(gvk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
return out, nil
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
/*
|
||||
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 rules // import "helm.sh/helm/v4/internal/chart/v3/lint/rules"
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestValidateNoDeprecations(t *testing.T) {
|
||||
deprecated := &k8sYamlStruct{
|
||||
APIVersion: "extensions/v1beta1",
|
||||
Kind: "Deployment",
|
||||
}
|
||||
err := validateNoDeprecations(deprecated, nil)
|
||||
if err == nil {
|
||||
t.Fatal("Expected deprecated extension to be flagged")
|
||||
}
|
||||
depErr := err.(deprecatedAPIError)
|
||||
if depErr.Message == "" {
|
||||
t.Fatalf("Expected error message to be non-blank: %v", err)
|
||||
}
|
||||
|
||||
if err := validateNoDeprecations(&k8sYamlStruct{
|
||||
APIVersion: "v1",
|
||||
Kind: "Pod",
|
||||
}, nil); err != nil {
|
||||
t.Error("Expected a v1 Pod to not be deprecated")
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,353 @@
|
||||
/*
|
||||
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 rules
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/validate/content"
|
||||
"k8s.io/apimachinery/pkg/api/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
|
||||
"helm.sh/helm/v4/internal/chart/v3/lint/support"
|
||||
"helm.sh/helm/v4/internal/chart/v3/loader"
|
||||
chartutil "helm.sh/helm/v4/internal/chart/v3/util"
|
||||
"helm.sh/helm/v4/pkg/chart/common"
|
||||
"helm.sh/helm/v4/pkg/chart/common/util"
|
||||
"helm.sh/helm/v4/pkg/engine"
|
||||
)
|
||||
|
||||
// Templates lints the templates in the Linter.
|
||||
func Templates(linter *support.Linter, values map[string]any, namespace string, _ bool) {
|
||||
TemplatesWithKubeVersion(linter, values, namespace, nil)
|
||||
}
|
||||
|
||||
// TemplatesWithKubeVersion lints the templates in the Linter, allowing to specify the kubernetes version.
|
||||
func TemplatesWithKubeVersion(linter *support.Linter, values map[string]any, namespace string, kubeVersion *common.KubeVersion) {
|
||||
TemplatesWithSkipSchemaValidation(linter, values, namespace, kubeVersion, false)
|
||||
}
|
||||
|
||||
// TemplatesWithSkipSchemaValidation lints the templates in the Linter, allowing to specify the kubernetes version and if schema validation is enabled or not.
|
||||
func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string]any, namespace string, kubeVersion *common.KubeVersion, skipSchemaValidation bool) {
|
||||
fpath := "templates/"
|
||||
templatesPath := filepath.Join(linter.ChartDir, fpath)
|
||||
|
||||
// Templates directory is optional for now
|
||||
templatesDirExists := linter.RunLinterRule(support.WarningSev, fpath, templatesDirExists(templatesPath))
|
||||
if !templatesDirExists {
|
||||
return
|
||||
}
|
||||
|
||||
validTemplatesDir := linter.RunLinterRule(support.ErrorSev, fpath, validateTemplatesDir(templatesPath))
|
||||
if !validTemplatesDir {
|
||||
return
|
||||
}
|
||||
|
||||
// Load chart and parse templates
|
||||
chart, err := loader.Load(linter.ChartDir)
|
||||
|
||||
chartLoaded := linter.RunLinterRule(support.ErrorSev, fpath, err)
|
||||
|
||||
if !chartLoaded {
|
||||
return
|
||||
}
|
||||
|
||||
options := common.ReleaseOptions{
|
||||
Name: "test-release",
|
||||
Namespace: namespace,
|
||||
}
|
||||
|
||||
caps := common.DefaultCapabilities.Copy()
|
||||
if kubeVersion != nil {
|
||||
caps.KubeVersion = *kubeVersion
|
||||
}
|
||||
|
||||
// lint ignores import-values
|
||||
// See https://github.com/helm/helm/issues/9658
|
||||
if err := chartutil.ProcessDependencies(chart, values); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cvals, err := util.CoalesceValues(chart, values)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
valuesToRender, err := util.ToRenderValuesWithSchemaValidation(chart, cvals, options, caps, skipSchemaValidation)
|
||||
if err != nil {
|
||||
linter.RunLinterRule(support.ErrorSev, fpath, err)
|
||||
return
|
||||
}
|
||||
var e engine.Engine
|
||||
e.LintMode = true
|
||||
renderedContentMap, err := e.Render(chart, valuesToRender)
|
||||
|
||||
renderOk := linter.RunLinterRule(support.ErrorSev, fpath, err)
|
||||
|
||||
if !renderOk {
|
||||
return
|
||||
}
|
||||
|
||||
/* Iterate over all the templates to check:
|
||||
- It is a .yaml file
|
||||
- All the values in the template file is defined
|
||||
- {{}} include | quote
|
||||
- Generated content is a valid Yaml file
|
||||
- Metadata.Namespace is not set
|
||||
*/
|
||||
for _, template := range chart.Templates {
|
||||
fileName := template.Name
|
||||
fpath = fileName
|
||||
|
||||
linter.RunLinterRule(support.ErrorSev, fpath, validateAllowedExtension(fileName))
|
||||
|
||||
// We only apply the following lint rules to yaml files
|
||||
if !isYamlFileExtension(fileName) {
|
||||
continue
|
||||
}
|
||||
|
||||
// NOTE: disabled for now, Refs https://github.com/helm/helm/issues/1463
|
||||
// Check that all the templates have a matching value
|
||||
// linter.RunLinterRule(support.WarningSev, fpath, validateNoMissingValues(templatesPath, valuesToRender, preExecutedTemplate))
|
||||
|
||||
// NOTE: disabled for now, Refs https://github.com/helm/helm/issues/1037
|
||||
// linter.RunLinterRule(support.WarningSev, fpath, validateQuotes(string(preExecutedTemplate)))
|
||||
|
||||
renderedContent := renderedContentMap[path.Join(chart.Name(), fileName)]
|
||||
if strings.TrimSpace(renderedContent) != "" {
|
||||
linter.RunLinterRule(support.WarningSev, fpath, validateTopIndentLevel(renderedContent))
|
||||
|
||||
decoder := yaml.NewYAMLOrJSONDecoder(strings.NewReader(renderedContent), 4096)
|
||||
|
||||
// Lint all resources if the file contains multiple documents separated by ---
|
||||
for {
|
||||
// Even though k8sYamlStruct only defines a few fields, an error in any other
|
||||
// key will be raised as well
|
||||
var yamlStruct *k8sYamlStruct
|
||||
|
||||
err := decoder.Decode(&yamlStruct)
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
|
||||
// If YAML linting fails here, it will always fail in the next block as well, so we should return here.
|
||||
// fix https://github.com/helm/helm/issues/11391
|
||||
if !linter.RunLinterRule(support.ErrorSev, fpath, validateYamlContent(err)) {
|
||||
return
|
||||
}
|
||||
if yamlStruct != nil {
|
||||
// NOTE: set to warnings to allow users to support out-of-date kubernetes
|
||||
// Refs https://github.com/helm/helm/issues/8596
|
||||
linter.RunLinterRule(support.WarningSev, fpath, validateMetadataName(yamlStruct))
|
||||
linter.RunLinterRule(support.WarningSev, fpath, validateNoDeprecations(yamlStruct, kubeVersion))
|
||||
|
||||
linter.RunLinterRule(support.ErrorSev, fpath, validateMatchSelector(yamlStruct, renderedContent))
|
||||
linter.RunLinterRule(support.ErrorSev, fpath, validateListAnnotations(yamlStruct, renderedContent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// validateTopIndentLevel checks that the content does not start with an indent level > 0.
|
||||
//
|
||||
// This error can occur when a template accidentally inserts space. It can cause
|
||||
// unpredictable errors depending on whether the text is normalized before being passed
|
||||
// into the YAML parser. So we trap it here.
|
||||
//
|
||||
// See https://github.com/helm/helm/issues/8467
|
||||
func validateTopIndentLevel(content string) error {
|
||||
// Read lines until we get to a non-empty one
|
||||
scanner := bufio.NewScanner(bytes.NewBufferString(content))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
// If line is empty, skip
|
||||
if strings.TrimSpace(line) == "" {
|
||||
continue
|
||||
}
|
||||
// If it starts with one or more spaces, this is an error
|
||||
if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
|
||||
return fmt.Errorf("document starts with an illegal indent: %q, which may cause parsing problems", line)
|
||||
}
|
||||
// Any other condition passes.
|
||||
return nil
|
||||
}
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
// Validation functions
|
||||
func templatesDirExists(templatesPath string) error {
|
||||
_, err := os.Stat(templatesPath)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return errors.New("directory does not exist")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateTemplatesDir(templatesPath string) error {
|
||||
fi, err := os.Stat(templatesPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return errors.New("not a directory")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateAllowedExtension(fileName string) error {
|
||||
ext := filepath.Ext(fileName)
|
||||
validExtensions := []string{".yaml", ".yml", ".tpl", ".txt"}
|
||||
|
||||
if slices.Contains(validExtensions, ext) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("file extension '%s' not valid. Valid extensions are .yaml, .yml, .tpl, or .txt", ext)
|
||||
}
|
||||
|
||||
func validateYamlContent(err error) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse YAML: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateMetadataName uses the correct validation function for the object
|
||||
// Kind, or if not set, defaults to the standard definition of a subdomain in
|
||||
// DNS (RFC 1123), used by most resources.
|
||||
func validateMetadataName(obj *k8sYamlStruct) error {
|
||||
fn := validateMetadataNameFunc(obj)
|
||||
allErrs := field.ErrorList{}
|
||||
for _, msg := range fn(obj.Metadata.Name, false) {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath("metadata").Child("name"), obj.Metadata.Name, msg))
|
||||
}
|
||||
if len(allErrs) > 0 {
|
||||
return fmt.Errorf("object name does not conform to Kubernetes naming requirements: %q: %w", obj.Metadata.Name, allErrs.ToAggregate())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateMetadataNameFunc will return a name validation function for the
|
||||
// object kind, if defined below.
|
||||
//
|
||||
// Rules should match those set in the various api validations:
|
||||
// https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/core/validation/validation.go#L205-L274
|
||||
// https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/apps/validation/validation.go#L39
|
||||
// ...
|
||||
//
|
||||
// Implementing here to avoid importing k/k.
|
||||
//
|
||||
// If no mapping is defined, returns NameIsDNSSubdomain. This is used by object
|
||||
// kinds that don't have special requirements, so is the most likely to work if
|
||||
// new kinds are added.
|
||||
func validateMetadataNameFunc(obj *k8sYamlStruct) validation.ValidateNameFunc {
|
||||
switch strings.ToLower(obj.Kind) {
|
||||
case "pod", "node", "secret", "endpoints", "resourcequota", // core
|
||||
"controllerrevision", "daemonset", "deployment", "replicaset", "statefulset", // apps
|
||||
"autoscaler", // autoscaler
|
||||
"cronjob", "job", // batch
|
||||
"lease", // coordination
|
||||
"endpointslice", // discovery
|
||||
"networkpolicy", "ingress", // networking
|
||||
"podsecuritypolicy", // policy
|
||||
"priorityclass", // scheduling
|
||||
"podpreset", // settings
|
||||
"storageclass", "volumeattachment", "csinode": // storage
|
||||
return validation.NameIsDNSSubdomain
|
||||
case "service":
|
||||
return validation.NameIsDNS1035Label
|
||||
case "namespace":
|
||||
return validation.ValidateNamespaceName
|
||||
case "serviceaccount":
|
||||
return validation.ValidateServiceAccountName
|
||||
case "certificatesigningrequest":
|
||||
// No validation.
|
||||
// https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/certificates/validation/validation.go#L137-L140
|
||||
return func(_ string, _ bool) []string { return nil }
|
||||
case "role", "clusterrole", "rolebinding", "clusterrolebinding":
|
||||
// https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/rbac/validation/validation.go#L32-L34
|
||||
return func(name string, _ bool) []string {
|
||||
return content.IsPathSegmentName(name)
|
||||
}
|
||||
default:
|
||||
return validation.NameIsDNSSubdomain
|
||||
}
|
||||
}
|
||||
|
||||
// validateMatchSelector ensures that template specs have a selector declared.
|
||||
// See https://github.com/helm/helm/issues/1990
|
||||
func validateMatchSelector(yamlStruct *k8sYamlStruct, manifest string) error {
|
||||
switch yamlStruct.Kind {
|
||||
case "Deployment", "ReplicaSet", "DaemonSet", "StatefulSet":
|
||||
// verify that matchLabels or matchExpressions is present
|
||||
if !strings.Contains(manifest, "matchLabels") && !strings.Contains(manifest, "matchExpressions") {
|
||||
return fmt.Errorf("a %s must contain matchLabels or matchExpressions, and %q does not", yamlStruct.Kind, yamlStruct.Metadata.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateListAnnotations(yamlStruct *k8sYamlStruct, manifest string) error {
|
||||
if yamlStruct.Kind == "List" {
|
||||
m := struct {
|
||||
Items []struct {
|
||||
Metadata struct {
|
||||
Annotations map[string]string
|
||||
}
|
||||
}
|
||||
}{}
|
||||
|
||||
if err := yaml.Unmarshal([]byte(manifest), &m); err != nil {
|
||||
return validateYamlContent(err)
|
||||
}
|
||||
|
||||
for _, i := range m.Items {
|
||||
if _, ok := i.Metadata.Annotations["helm.sh/resource-policy"]; ok {
|
||||
return errors.New("annotation 'helm.sh/resource-policy' within List objects are ignored")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isYamlFileExtension(fileName string) bool {
|
||||
ext := strings.ToLower(filepath.Ext(fileName))
|
||||
return ext == ".yaml" || ext == ".yml"
|
||||
}
|
||||
|
||||
// k8sYamlStruct stubs a Kubernetes YAML file.
|
||||
type k8sYamlStruct struct {
|
||||
APIVersion string `json:"apiVersion"`
|
||||
Kind string
|
||||
Metadata k8sYamlMetadata
|
||||
}
|
||||
|
||||
type k8sYamlMetadata struct {
|
||||
Namespace string
|
||||
Name string
|
||||
}
|
||||
@ -0,0 +1,464 @@
|
||||
/*
|
||||
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 rules
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
chart "helm.sh/helm/v4/internal/chart/v3"
|
||||
"helm.sh/helm/v4/internal/chart/v3/lint/support"
|
||||
chartutil "helm.sh/helm/v4/internal/chart/v3/util"
|
||||
"helm.sh/helm/v4/pkg/chart/common"
|
||||
)
|
||||
|
||||
const templateTestBasedir = "./testdata/albatross"
|
||||
|
||||
func TestValidateAllowedExtension(t *testing.T) {
|
||||
var failTest = []string{"/foo", "/test.toml"}
|
||||
for _, test := range failTest {
|
||||
err := validateAllowedExtension(test)
|
||||
if err == nil || !strings.Contains(err.Error(), "Valid extensions are .yaml, .yml, .tpl, or .txt") {
|
||||
t.Errorf("validateAllowedExtension('%s') to return \"Valid extensions are .yaml, .yml, .tpl, or .txt\", got no error", test)
|
||||
}
|
||||
}
|
||||
var successTest = []string{"/foo.yaml", "foo.yaml", "foo.tpl", "/foo/bar/baz.yaml", "NOTES.txt"}
|
||||
for _, test := range successTest {
|
||||
err := validateAllowedExtension(test)
|
||||
if err != nil {
|
||||
t.Errorf("validateAllowedExtension('%s') to return no error but got \"%s\"", test, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var values = map[string]any{"nameOverride": "", "httpPort": 80}
|
||||
|
||||
const namespace = "testNamespace"
|
||||
const strict = false
|
||||
|
||||
func TestTemplateParsing(t *testing.T) {
|
||||
linter := support.Linter{ChartDir: templateTestBasedir}
|
||||
Templates(&linter, values, namespace, strict)
|
||||
res := linter.Messages
|
||||
|
||||
if len(res) != 1 {
|
||||
t.Fatalf("Expected one error, got %d, %v", len(res), res)
|
||||
}
|
||||
|
||||
if !strings.Contains(res[0].Err.Error(), "deliberateSyntaxError") {
|
||||
t.Errorf("Unexpected error: %s", res[0])
|
||||
}
|
||||
}
|
||||
|
||||
var wrongTemplatePath = filepath.Join(templateTestBasedir, "templates", "fail.yaml")
|
||||
var ignoredTemplatePath = filepath.Join(templateTestBasedir, "fail.yaml.ignored")
|
||||
|
||||
// Test a template with all the existing features:
|
||||
// namespaces, partial templates
|
||||
func TestTemplateIntegrationHappyPath(t *testing.T) {
|
||||
// Rename file so it gets ignored by the linter
|
||||
os.Rename(wrongTemplatePath, ignoredTemplatePath)
|
||||
defer os.Rename(ignoredTemplatePath, wrongTemplatePath)
|
||||
|
||||
linter := support.Linter{ChartDir: templateTestBasedir}
|
||||
Templates(&linter, values, namespace, strict)
|
||||
res := linter.Messages
|
||||
|
||||
if len(res) != 0 {
|
||||
t.Fatalf("Expected no error, got %d, %v", len(res), res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiTemplateFail(t *testing.T) {
|
||||
linter := support.Linter{ChartDir: "./testdata/multi-template-fail"}
|
||||
Templates(&linter, values, namespace, strict)
|
||||
res := linter.Messages
|
||||
|
||||
if len(res) != 1 {
|
||||
t.Fatalf("Expected 1 error, got %d, %v", len(res), res)
|
||||
}
|
||||
|
||||
if !strings.Contains(res[0].Err.Error(), "object name does not conform to Kubernetes naming requirements") {
|
||||
t.Errorf("Unexpected error: %s", res[0].Err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateMetadataName(t *testing.T) {
|
||||
tests := []struct {
|
||||
obj *k8sYamlStruct
|
||||
wantErr bool
|
||||
}{
|
||||
// Most kinds use IsDNS1123Subdomain.
|
||||
{&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: ""}}, true},
|
||||
{&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
|
||||
{&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "foo.bar1234baz.seventyone"}}, false},
|
||||
{&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "FOO"}}, true},
|
||||
{&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
|
||||
{&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "foo.BAR.baz"}}, true},
|
||||
{&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "one-two"}}, false},
|
||||
{&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "-two"}}, true},
|
||||
{&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "one_two"}}, true},
|
||||
{&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "a..b"}}, true},
|
||||
{&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "%^&#$%*@^*@&#^"}}, true},
|
||||
{&k8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "operator:pod"}}, true},
|
||||
{&k8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
|
||||
{&k8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "foo.bar1234baz.seventyone"}}, false},
|
||||
{&k8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "FOO"}}, true},
|
||||
{&k8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "operator:sa"}}, true},
|
||||
|
||||
// Service uses IsDNS1035Label.
|
||||
{&k8sYamlStruct{Kind: "Service", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
|
||||
{&k8sYamlStruct{Kind: "Service", Metadata: k8sYamlMetadata{Name: "123baz"}}, true},
|
||||
{&k8sYamlStruct{Kind: "Service", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, true},
|
||||
|
||||
// Namespace uses IsDNS1123Label.
|
||||
{&k8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
|
||||
{&k8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
|
||||
{&k8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, true},
|
||||
{&k8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "foo-bar"}}, false},
|
||||
|
||||
// CertificateSigningRequest has no validation.
|
||||
{&k8sYamlStruct{Kind: "CertificateSigningRequest", Metadata: k8sYamlMetadata{Name: ""}}, false},
|
||||
{&k8sYamlStruct{Kind: "CertificateSigningRequest", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
|
||||
{&k8sYamlStruct{Kind: "CertificateSigningRequest", Metadata: k8sYamlMetadata{Name: "%^&#$%*@^*@&#^"}}, false},
|
||||
|
||||
// RBAC uses path validation.
|
||||
{&k8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
|
||||
{&k8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
|
||||
{&k8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, false},
|
||||
{&k8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false},
|
||||
{&k8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "operator/role"}}, true},
|
||||
{&k8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "operator%role"}}, true},
|
||||
{&k8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
|
||||
{&k8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
|
||||
{&k8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, false},
|
||||
{&k8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false},
|
||||
{&k8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "operator/role"}}, true},
|
||||
{&k8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "operator%role"}}, true},
|
||||
{&k8sYamlStruct{Kind: "RoleBinding", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false},
|
||||
{&k8sYamlStruct{Kind: "ClusterRoleBinding", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false},
|
||||
|
||||
// Unknown Kind
|
||||
{&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: ""}}, true},
|
||||
{&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
|
||||
{&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "foo.bar1234baz.seventyone"}}, false},
|
||||
{&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "FOO"}}, true},
|
||||
{&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
|
||||
{&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "foo.BAR.baz"}}, true},
|
||||
{&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "one-two"}}, false},
|
||||
{&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "-two"}}, true},
|
||||
{&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "one_two"}}, true},
|
||||
{&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "a..b"}}, true},
|
||||
{&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "%^&#$%*@^*@&#^"}}, true},
|
||||
{&k8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "operator:pod"}}, true},
|
||||
|
||||
// No kind
|
||||
{&k8sYamlStruct{Metadata: k8sYamlMetadata{Name: "foo"}}, false},
|
||||
{&k8sYamlStruct{Metadata: k8sYamlMetadata{Name: "operator:pod"}}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(fmt.Sprintf("%s/%s", tt.obj.Kind, tt.obj.Metadata.Name), func(t *testing.T) {
|
||||
if err := validateMetadataName(tt.obj); (err != nil) != tt.wantErr {
|
||||
t.Errorf("validateMetadataName() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeprecatedAPIFails(t *testing.T) {
|
||||
modTime := time.Now()
|
||||
mychart := chart.Chart{
|
||||
Metadata: &chart.Metadata{
|
||||
APIVersion: "v2",
|
||||
Name: "failapi",
|
||||
Version: "0.1.0",
|
||||
Icon: "satisfy-the-linting-gods.gif",
|
||||
},
|
||||
Templates: []*common.File{
|
||||
{
|
||||
Name: "templates/baddeployment.yaml",
|
||||
ModTime: modTime,
|
||||
Data: []byte("apiVersion: apps/v1beta1\nkind: Deployment\nmetadata:\n name: baddep\nspec: {selector: {matchLabels: {foo: bar}}}"),
|
||||
},
|
||||
{
|
||||
Name: "templates/goodsecret.yaml",
|
||||
ModTime: modTime,
|
||||
Data: []byte("apiVersion: v1\nkind: Secret\nmetadata:\n name: goodsecret"),
|
||||
},
|
||||
},
|
||||
}
|
||||
tmpdir := t.TempDir()
|
||||
|
||||
if err := chartutil.SaveDir(&mychart, tmpdir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())}
|
||||
Templates(&linter, values, namespace, strict)
|
||||
if l := len(linter.Messages); l != 1 {
|
||||
for i, msg := range linter.Messages {
|
||||
t.Logf("Message %d: %s", i, msg)
|
||||
}
|
||||
t.Fatalf("Expected 1 lint error, got %d", l)
|
||||
}
|
||||
|
||||
err := linter.Messages[0].Err.(deprecatedAPIError)
|
||||
if err.Deprecated != "apps/v1beta1 Deployment" {
|
||||
t.Errorf("Surprised to learn that %q is deprecated", err.Deprecated)
|
||||
}
|
||||
}
|
||||
|
||||
const manifest = `apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: foo
|
||||
data:
|
||||
myval1: {{default "val" .Values.mymap.key1 }}
|
||||
myval2: {{default "val" .Values.mymap.key2 }}
|
||||
`
|
||||
|
||||
// TestStrictTemplateParsingMapError is a regression test.
|
||||
//
|
||||
// The template engine should not produce an error when a map in values.yaml does
|
||||
// not contain all possible keys.
|
||||
//
|
||||
// See https://github.com/helm/helm/issues/7483
|
||||
func TestStrictTemplateParsingMapError(t *testing.T) {
|
||||
ch := chart.Chart{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "regression7483",
|
||||
APIVersion: "v2",
|
||||
Version: "0.1.0",
|
||||
},
|
||||
Values: map[string]any{
|
||||
"mymap": map[string]string{
|
||||
"key1": "val1",
|
||||
},
|
||||
},
|
||||
Templates: []*common.File{
|
||||
{
|
||||
Name: "templates/configmap.yaml",
|
||||
ModTime: time.Now(),
|
||||
Data: []byte(manifest),
|
||||
},
|
||||
},
|
||||
}
|
||||
dir := t.TempDir()
|
||||
if err := chartutil.SaveDir(&ch, dir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
linter := &support.Linter{
|
||||
ChartDir: filepath.Join(dir, ch.Metadata.Name),
|
||||
}
|
||||
Templates(linter, ch.Values, namespace, strict)
|
||||
if len(linter.Messages) != 0 {
|
||||
t.Errorf("expected zero messages, got %d", len(linter.Messages))
|
||||
for i, msg := range linter.Messages {
|
||||
t.Logf("Message %d: %q", i, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateMatchSelector(t *testing.T) {
|
||||
md := &k8sYamlStruct{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Metadata: k8sYamlMetadata{
|
||||
Name: "mydeployment",
|
||||
},
|
||||
}
|
||||
manifest := `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.14.2
|
||||
`
|
||||
if err := validateMatchSelector(md, manifest); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
manifest = `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchExpressions:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.14.2
|
||||
`
|
||||
if err := validateMatchSelector(md, manifest); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
manifest = `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
replicas: 3
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.14.2
|
||||
`
|
||||
if err := validateMatchSelector(md, manifest); err == nil {
|
||||
t.Error("expected Deployment with no selector to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateTopIndentLevel(t *testing.T) {
|
||||
for doc, shouldFail := range map[string]bool{
|
||||
// Should not fail
|
||||
"\n\n\n\t\n \t\n": false,
|
||||
"apiVersion:foo\n bar:baz": false,
|
||||
"\n\n\napiVersion:foo\n\n\n": false,
|
||||
// Should fail
|
||||
" apiVersion:foo": true,
|
||||
"\n\n apiVersion:foo\n\n": true,
|
||||
} {
|
||||
if err := validateTopIndentLevel(doc); (err == nil) == shouldFail {
|
||||
t.Errorf("Expected %t for %q", shouldFail, doc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestEmptyWithCommentsManifests checks the lint is not failing against empty manifests that contains only comments
|
||||
// See https://github.com/helm/helm/issues/8621
|
||||
func TestEmptyWithCommentsManifests(t *testing.T) {
|
||||
mychart := chart.Chart{
|
||||
Metadata: &chart.Metadata{
|
||||
APIVersion: "v2",
|
||||
Name: "emptymanifests",
|
||||
Version: "0.1.0",
|
||||
Icon: "satisfy-the-linting-gods.gif",
|
||||
},
|
||||
Templates: []*common.File{
|
||||
{
|
||||
Name: "templates/empty-with-comments.yaml",
|
||||
ModTime: time.Now(),
|
||||
Data: []byte("#@formatter:off\n"),
|
||||
},
|
||||
},
|
||||
}
|
||||
tmpdir := t.TempDir()
|
||||
|
||||
if err := chartutil.SaveDir(&mychart, tmpdir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())}
|
||||
Templates(&linter, values, namespace, strict)
|
||||
if l := len(linter.Messages); l > 0 {
|
||||
for i, msg := range linter.Messages {
|
||||
t.Logf("Message %d: %s", i, msg)
|
||||
}
|
||||
t.Fatalf("Expected 0 lint errors, got %d", l)
|
||||
}
|
||||
}
|
||||
func TestValidateListAnnotations(t *testing.T) {
|
||||
md := &k8sYamlStruct{
|
||||
APIVersion: "v1",
|
||||
Kind: "List",
|
||||
Metadata: k8sYamlMetadata{
|
||||
Name: "list",
|
||||
},
|
||||
}
|
||||
manifest := `
|
||||
apiVersion: v1
|
||||
kind: List
|
||||
items:
|
||||
- apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
annotations:
|
||||
helm.sh/resource-policy: keep
|
||||
`
|
||||
|
||||
if err := validateListAnnotations(md, manifest); err == nil {
|
||||
t.Fatal("expected list with nested keep annotations to fail")
|
||||
}
|
||||
|
||||
manifest = `
|
||||
apiVersion: v1
|
||||
kind: List
|
||||
metadata:
|
||||
annotations:
|
||||
helm.sh/resource-policy: keep
|
||||
items:
|
||||
- apiVersion: v1
|
||||
kind: ConfigMap
|
||||
`
|
||||
|
||||
if err := validateListAnnotations(md, manifest); err != nil {
|
||||
t.Fatalf("List objects keep annotations should pass. got: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsYamlFileExtension(t *testing.T) {
|
||||
tests := []struct {
|
||||
filename string
|
||||
expected bool
|
||||
}{
|
||||
{"test.yaml", true},
|
||||
{"test.yml", true},
|
||||
{"test.txt", false},
|
||||
{"test", false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := isYamlFileExtension(test.filename)
|
||||
if result != test.expected {
|
||||
t.Errorf("isYamlFileExtension(%s) = %v; want %v", test.filename, result, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
apiVersion: v3
|
||||
name: albatross
|
||||
description: testing chart
|
||||
version: 199.44.12345-Alpha.1+cafe009
|
||||
icon: http://riverrun.io
|
||||
@ -0,0 +1,16 @@
|
||||
{{/* vim: set filetype=mustache: */}}
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{define "name"}}{{default "nginx" .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).
|
||||
*/}}
|
||||
{{define "fullname"}}
|
||||
{{- $name := default "nginx" .Values.nameOverride -}}
|
||||
{{printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{end}}
|
||||
@ -0,0 +1 @@
|
||||
{{ deliberateSyntaxError }}
|
||||
@ -0,0 +1,19 @@
|
||||
# This is a service gateway to the replica set created by the deployment.
|
||||
# Take a look at the deployment.yaml for general notes about this chart.
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: "{{ .Values.name }}"
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service | quote }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name | quote }}
|
||||
helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}"
|
||||
kubeVersion: {{ .Capabilities.KubeVersion.Major }}
|
||||
spec:
|
||||
ports:
|
||||
- port: {{default 80 .Values.httpPort | quote}}
|
||||
targetPort: 80
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app.kubernetes.io/name: {{template "fullname" .}}
|
||||
@ -0,0 +1 @@
|
||||
name: "mariner"
|
||||
@ -0,0 +1,15 @@
|
||||
name: "some-chart"
|
||||
apiVersion: v3
|
||||
description: A Helm chart for Kubernetes
|
||||
version: 72445e2
|
||||
home: ""
|
||||
type: application
|
||||
appVersion: 72225e2
|
||||
icon: "https://some-url.com/icon.jpeg"
|
||||
dependencies:
|
||||
- name: mariadb
|
||||
version: 5.x.x
|
||||
repository: https://charts.helm.sh/stable/
|
||||
condition: mariadb.enabled
|
||||
tags:
|
||||
- database
|
||||
@ -0,0 +1,11 @@
|
||||
description: A Helm chart for Kubernetes
|
||||
version: 0.0.0.0
|
||||
home: ""
|
||||
type: application
|
||||
dependencies:
|
||||
- name: mariadb
|
||||
version: 5.x.x
|
||||
repository: https://charts.helm.sh/stable/
|
||||
condition: mariadb.enabled
|
||||
tags:
|
||||
- database
|
||||
@ -0,0 +1 @@
|
||||
# Default values for badchartfile.
|
||||
@ -0,0 +1,5 @@
|
||||
apiVersion: v3
|
||||
description: A Helm chart for Kubernetes
|
||||
version: 0.1.0
|
||||
name: "../badchartname"
|
||||
type: application
|
||||
@ -0,0 +1 @@
|
||||
# Default values for badchartfile.
|
||||
@ -0,0 +1,6 @@
|
||||
apiVersion: v3
|
||||
description: A Helm chart for Kubernetes
|
||||
version: 0.1.0
|
||||
name: badcrdfile
|
||||
type: application
|
||||
icon: http://riverrun.io
|
||||
@ -0,0 +1,2 @@
|
||||
apiVersion: bad.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
@ -0,0 +1,2 @@
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: NotACustomResourceDefinition
|
||||
@ -0,0 +1 @@
|
||||
# Default values for badcrdfile.
|
||||
@ -0,0 +1,6 @@
|
||||
apiVersion: v3
|
||||
name: badvaluesfile
|
||||
description: A Helm chart for Kubernetes
|
||||
version: 0.0.1
|
||||
home: ""
|
||||
icon: http://riverrun.io
|
||||
@ -0,0 +1,2 @@
|
||||
metadata:
|
||||
name: {{.name | default "foo" | title}}
|
||||
@ -0,0 +1,2 @@
|
||||
# Invalid value for badvaluesfile for testing lint fails with invalid yaml format
|
||||
name= "value"
|
||||
@ -0,0 +1,5 @@
|
||||
apiVersion: v3
|
||||
name: goodone
|
||||
description: good testing chart
|
||||
version: 199.44.12345-Alpha.1+cafe009
|
||||
icon: http://riverrun.io
|
||||
@ -0,0 +1,19 @@
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: tests.test.io
|
||||
spec:
|
||||
group: test.io
|
||||
names:
|
||||
kind: Test
|
||||
listKind: TestList
|
||||
plural: tests
|
||||
singular: test
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name : v1alpha2
|
||||
served: true
|
||||
storage: true
|
||||
- name : v1alpha1
|
||||
served: true
|
||||
storage: false
|
||||
@ -0,0 +1,2 @@
|
||||
metadata:
|
||||
name: {{ .Values.name | default "foo" | lower }}
|
||||
@ -0,0 +1 @@
|
||||
name: "goodone-here"
|
||||
@ -0,0 +1,6 @@
|
||||
name: some-chart
|
||||
apiVersion: v2
|
||||
apiVersion: v1
|
||||
description: A Helm chart for Kubernetes
|
||||
version: 1.3.0
|
||||
icon: http://example.com
|
||||
@ -0,0 +1,6 @@
|
||||
apiVersion: v3
|
||||
description: A Helm chart for Kubernetes
|
||||
version: 0.1.0
|
||||
name: invalidcrdsdir
|
||||
type: application
|
||||
icon: http://riverrun.io
|
||||
@ -0,0 +1 @@
|
||||
# Default values for invalidcrdsdir.
|
||||
@ -0,0 +1,23 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
@ -0,0 +1,25 @@
|
||||
apiVersion: v3
|
||||
name: test
|
||||
description: A Helm chart for Kubernetes
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
#
|
||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||
# to be deployed.
|
||||
#
|
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.1.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "1.16.0"
|
||||
icon: https://riverrun.io
|
||||
@ -0,0 +1 @@
|
||||
{ {- $relname := .Release.Name -}}
|
||||
@ -0,0 +1,82 @@
|
||||
# Default values for test.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: nginx
|
||||
pullPolicy: IfNotPresent
|
||||
# Overrides the image tag whose default is the chart appVersion.
|
||||
tag: ""
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: true
|
||||
# Annotations to add to the service account
|
||||
annotations: {}
|
||||
# The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name: ""
|
||||
|
||||
podAnnotations: {}
|
||||
|
||||
podSecurityContext: {}
|
||||
# fsGroup: 2000
|
||||
|
||||
securityContext: {}
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
# readOnlyRootFilesystem: true
|
||||
# runAsNonRoot: true
|
||||
# runAsUser: 1000
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
className: ""
|
||||
annotations: {}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
hosts:
|
||||
- host: chart-example.local
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
tls: []
|
||||
# - secretName: chart-example-tls
|
||||
# hosts:
|
||||
# - chart-example.local
|
||||
|
||||
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
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 1
|
||||
maxReplicas: 100
|
||||
targetCPUUtilizationPercentage: 80
|
||||
# targetMemoryUtilizationPercentage: 80
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
||||
@ -0,0 +1,21 @@
|
||||
apiVersion: v3
|
||||
name: multi-template-fail
|
||||
description: A Helm chart for Kubernetes
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
#
|
||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||
# to be deployed.
|
||||
#
|
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
version: 0.1.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application and it is recommended to use it with quotes.
|
||||
appVersion: "1.16.0"
|
||||
@ -0,0 +1,13 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: game-config
|
||||
data:
|
||||
game.properties: cheat
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: -this:name-is-not_valid$
|
||||
data:
|
||||
game.properties: empty
|
||||
@ -0,0 +1,21 @@
|
||||
apiVersion: v3
|
||||
name: v3-fail
|
||||
description: A Helm chart for Kubernetes
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
#
|
||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||
# to be deployed.
|
||||
#
|
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
version: 0.1.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application and it is recommended to use it with quotes.
|
||||
appVersion: "1.16.0"
|
||||
@ -0,0 +1,63 @@
|
||||
{{/* vim: set filetype=mustache: */}}
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "v3-fail.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 "v3-fail.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 "v3-fail.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "v3-fail.labels" -}}
|
||||
helm.sh/chart: {{ include "v3-fail.chart" . }}
|
||||
{{ include "v3-fail.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "v3-fail.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "v3-fail.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "v3-fail.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
{{ default (include "v3-fail.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else -}}
|
||||
{{ default "default" .Values.serviceAccount.name }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
@ -0,0 +1,56 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "v3-fail.fullname" . }}
|
||||
labels:
|
||||
nope: {{ .Release.Time }}
|
||||
{{- include "v3-fail.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "v3-fail.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "v3-fail.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "v3-fail.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Chart.AppVersion }}"
|
||||
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 }}
|
||||
@ -0,0 +1,62 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "v3-fail.fullname" . -}}
|
||||
{{- $svcPort := .Values.service.port -}}
|
||||
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
|
||||
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
labels:
|
||||
{{- include "v3-fail.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
"helm.sh/hook": crd-install
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
|
||||
pathType: {{ .pathType }}
|
||||
{{- end }}
|
||||
backend:
|
||||
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
service:
|
||||
name: {{ $fullName }}
|
||||
port:
|
||||
number: {{ $svcPort }}
|
||||
{{- else }}
|
||||
serviceName: {{ $fullName }}
|
||||
servicePort: {{ $svcPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@ -0,0 +1,17 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "v3-fail.fullname" . }}
|
||||
annotations:
|
||||
helm.sh/hook: crd-install
|
||||
labels:
|
||||
{{- include "v3-fail.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
{{- include "v3-fail.selectorLabels" . | nindent 4 }}
|
||||
@ -0,0 +1,66 @@
|
||||
# Default values for v3-fail.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: nginx
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: true
|
||||
# The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name:
|
||||
|
||||
podSecurityContext: {}
|
||||
# fsGroup: 2000
|
||||
|
||||
securityContext: {}
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
# readOnlyRootFilesystem: true
|
||||
# runAsNonRoot: true
|
||||
# runAsUser: 1000
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
annotations: {}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
hosts:
|
||||
- host: chart-example.local
|
||||
paths: []
|
||||
tls: []
|
||||
# - secretName: chart-example-tls
|
||||
# hosts:
|
||||
# - chart-example.local
|
||||
|
||||
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: {}
|
||||
@ -0,0 +1,16 @@
|
||||
apiVersion: v3
|
||||
name: withsubchart
|
||||
description: A Helm chart for Kubernetes
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "1.16.0"
|
||||
icon: http://riverrun.io
|
||||
|
||||
dependencies:
|
||||
- name: subchart
|
||||
version: 0.1.16
|
||||
repository: "file://../subchart"
|
||||
import-values:
|
||||
- child: subchart
|
||||
parent: subchart
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
apiVersion: v3
|
||||
name: subchart
|
||||
description: A Helm chart for Kubernetes
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "1.16.0"
|
||||
@ -0,0 +1,2 @@
|
||||
metadata:
|
||||
name: {{ .Values.subchart.name | lower }}
|
||||
@ -0,0 +1,2 @@
|
||||
subchart:
|
||||
name: subchart
|
||||
@ -0,0 +1,2 @@
|
||||
metadata:
|
||||
name: {{ .Values.subchart.name | lower }}
|
||||
@ -0,0 +1,85 @@
|
||||
/*
|
||||
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 rules
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"helm.sh/helm/v4/internal/chart/v3/lint/support"
|
||||
"helm.sh/helm/v4/pkg/chart/common"
|
||||
"helm.sh/helm/v4/pkg/chart/common/util"
|
||||
)
|
||||
|
||||
// ValuesWithOverrides tests the values.yaml file.
|
||||
//
|
||||
// If a schema is present in the chart, values are tested against that. Otherwise,
|
||||
// they are only tested for well-formedness.
|
||||
//
|
||||
// If additional values are supplied, they are coalesced into the values in values.yaml.
|
||||
func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]any, skipSchemaValidation bool) {
|
||||
file := "values.yaml"
|
||||
vf := filepath.Join(linter.ChartDir, file)
|
||||
fileExists := linter.RunLinterRule(support.InfoSev, file, validateValuesFileExistence(vf))
|
||||
|
||||
if !fileExists {
|
||||
return
|
||||
}
|
||||
|
||||
linter.RunLinterRule(support.ErrorSev, file, validateValuesFile(vf, valueOverrides, skipSchemaValidation))
|
||||
}
|
||||
|
||||
func validateValuesFileExistence(valuesPath string) error {
|
||||
_, err := os.Stat(valuesPath)
|
||||
if err != nil {
|
||||
return errors.New("file does not exist")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateValuesFile(valuesPath string, overrides map[string]any, skipSchemaValidation bool) error {
|
||||
values, err := common.ReadValuesFile(valuesPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse YAML: %w", err)
|
||||
}
|
||||
|
||||
// Helm 3.0.0 carried over the values linting from Helm 2.x, which only tests the top
|
||||
// level values against the top-level expectations. Subchart values are not linted.
|
||||
// We could change that. For now, though, we retain that strategy, and thus can
|
||||
// coalesce tables (like reuse-values does) instead of doing the full chart
|
||||
// CoalesceValues
|
||||
coalescedValues := util.CoalesceTables(make(map[string]any, len(overrides)), overrides)
|
||||
coalescedValues = util.CoalesceTables(coalescedValues, values)
|
||||
|
||||
ext := filepath.Ext(valuesPath)
|
||||
schemaPath := valuesPath[:len(valuesPath)-len(ext)] + ".schema.json"
|
||||
schema, err := os.ReadFile(schemaPath)
|
||||
if len(schema) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !skipSchemaValidation {
|
||||
return util.ValidateAgainstSingleSchema(coalescedValues, schema)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -0,0 +1,183 @@
|
||||
/*
|
||||
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 rules
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"helm.sh/helm/v4/internal/test/ensure"
|
||||
)
|
||||
|
||||
var nonExistingValuesFilePath = filepath.Join("/fake/dir", "values.yaml")
|
||||
|
||||
const testSchema = `
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "helm values test schema",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"username",
|
||||
"password"
|
||||
],
|
||||
"properties": {
|
||||
"username": {
|
||||
"description": "Your username",
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"description": "Your password",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
func TestValidateValuesYamlNotDirectory(t *testing.T) {
|
||||
_ = os.Mkdir(nonExistingValuesFilePath, os.ModePerm)
|
||||
defer os.Remove(nonExistingValuesFilePath)
|
||||
|
||||
err := validateValuesFileExistence(nonExistingValuesFilePath)
|
||||
if err == nil {
|
||||
t.Error("validateValuesFileExistence to return a linter error, got no error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateValuesFileWellFormed(t *testing.T) {
|
||||
badYaml := `
|
||||
not:well[]{}formed
|
||||
`
|
||||
tmpdir := ensure.TempFile(t, "values.yaml", []byte(badYaml))
|
||||
valfile := filepath.Join(tmpdir, "values.yaml")
|
||||
if err := validateValuesFile(valfile, map[string]any{}, false); err == nil {
|
||||
t.Fatal("expected values file to fail parsing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateValuesFileSchema(t *testing.T) {
|
||||
yaml := "username: admin\npassword: swordfish"
|
||||
tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml))
|
||||
createTestingSchema(t, tmpdir)
|
||||
|
||||
valfile := filepath.Join(tmpdir, "values.yaml")
|
||||
if err := validateValuesFile(valfile, map[string]any{}, false); err != nil {
|
||||
t.Fatalf("Failed validation with %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateValuesFileSchemaFailure(t *testing.T) {
|
||||
// 1234 is an int, not a string. This should fail.
|
||||
yaml := "username: 1234\npassword: swordfish"
|
||||
tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml))
|
||||
createTestingSchema(t, tmpdir)
|
||||
|
||||
valfile := filepath.Join(tmpdir, "values.yaml")
|
||||
|
||||
err := validateValuesFile(valfile, map[string]any{}, false)
|
||||
if err == nil {
|
||||
t.Fatal("expected values file to fail parsing")
|
||||
}
|
||||
|
||||
assert.Contains(t, err.Error(), "- at '/username': got number, want string")
|
||||
}
|
||||
|
||||
func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T) {
|
||||
// 1234 is an int, not a string. This should fail normally but pass with skipSchemaValidation.
|
||||
yaml := "username: 1234\npassword: swordfish"
|
||||
tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml))
|
||||
createTestingSchema(t, tmpdir)
|
||||
|
||||
valfile := filepath.Join(tmpdir, "values.yaml")
|
||||
|
||||
err := validateValuesFile(valfile, map[string]any{}, true)
|
||||
if err != nil {
|
||||
t.Fatal("expected values file to pass parsing because of skipSchemaValidation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateValuesFileSchemaOverrides(t *testing.T) {
|
||||
yaml := "username: admin"
|
||||
overrides := map[string]any{
|
||||
"password": "swordfish",
|
||||
}
|
||||
tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml))
|
||||
createTestingSchema(t, tmpdir)
|
||||
|
||||
valfile := filepath.Join(tmpdir, "values.yaml")
|
||||
if err := validateValuesFile(valfile, overrides, false); err != nil {
|
||||
t.Fatalf("Failed validation with %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateValuesFile(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
yaml string
|
||||
overrides map[string]any
|
||||
errorMessage string
|
||||
}{
|
||||
{
|
||||
name: "value added",
|
||||
yaml: "username: admin",
|
||||
overrides: map[string]any{"password": "swordfish"},
|
||||
},
|
||||
{
|
||||
name: "value not overridden",
|
||||
yaml: "username: admin\npassword:",
|
||||
overrides: map[string]any{"username": "anotherUser"},
|
||||
errorMessage: "- at '/password': got null, want string",
|
||||
},
|
||||
{
|
||||
name: "value overridden",
|
||||
yaml: "username: admin\npassword:",
|
||||
overrides: map[string]any{"username": "anotherUser", "password": "swordfish"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tmpdir := ensure.TempFile(t, "values.yaml", []byte(tt.yaml))
|
||||
createTestingSchema(t, tmpdir)
|
||||
|
||||
valfile := filepath.Join(tmpdir, "values.yaml")
|
||||
|
||||
err := validateValuesFile(valfile, tt.overrides, false)
|
||||
|
||||
switch {
|
||||
case err != nil && tt.errorMessage == "":
|
||||
t.Errorf("Failed validation with %s", err)
|
||||
case err == nil && tt.errorMessage != "":
|
||||
t.Error("expected values file to fail parsing")
|
||||
case err != nil && tt.errorMessage != "":
|
||||
assert.Contains(t, err.Error(), tt.errorMessage, "Failed with unexpected error")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createTestingSchema(t *testing.T, dir string) string {
|
||||
t.Helper()
|
||||
schemafile := filepath.Join(dir, "values.schema.json")
|
||||
if err := os.WriteFile(schemafile, []byte(testSchema), 0700); err != nil {
|
||||
t.Fatalf("Failed to write schema to tmpdir: %s", err)
|
||||
}
|
||||
return schemafile
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
/*
|
||||
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 support contains tools for linting charts.
|
||||
|
||||
Linting is the process of testing charts for errors or warnings regarding
|
||||
formatting, compilation, or standards compliance.
|
||||
*/
|
||||
package support // import "helm.sh/helm/v4/internal/chart/v3/lint/support"
|
||||
@ -0,0 +1,76 @@
|
||||
/*
|
||||
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 support
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Severity indicates the severity of a Message.
|
||||
const (
|
||||
// UnknownSev indicates that the severity of the error is unknown, and should not stop processing.
|
||||
UnknownSev = iota
|
||||
// InfoSev indicates information, for example missing values.yaml file
|
||||
InfoSev
|
||||
// WarningSev indicates that something does not meet code standards, but will likely function.
|
||||
WarningSev
|
||||
// ErrorSev indicates that something will not likely function.
|
||||
ErrorSev
|
||||
)
|
||||
|
||||
// sev matches the *Sev states.
|
||||
var sev = []string{"UNKNOWN", "INFO", "WARNING", "ERROR"}
|
||||
|
||||
// Linter encapsulates a linting run of a particular chart.
|
||||
type Linter struct {
|
||||
Messages []Message
|
||||
// The highest severity of all the failing lint rules
|
||||
HighestSeverity int
|
||||
ChartDir string
|
||||
}
|
||||
|
||||
// Message describes an error encountered while linting.
|
||||
type Message struct {
|
||||
// Severity is one of the *Sev constants
|
||||
Severity int
|
||||
Path string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (m Message) Error() string {
|
||||
return fmt.Sprintf("[%s] %s: %s", sev[m.Severity], m.Path, m.Err.Error())
|
||||
}
|
||||
|
||||
// NewMessage creates a new Message struct
|
||||
func NewMessage(severity int, path string, err error) Message {
|
||||
return Message{Severity: severity, Path: path, Err: err}
|
||||
}
|
||||
|
||||
// RunLinterRule returns true if the validation passed
|
||||
func (l *Linter) RunLinterRule(severity int, path string, err error) bool {
|
||||
// severity is out of bound
|
||||
if severity < 0 || severity >= len(sev) {
|
||||
return false
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
l.Messages = append(l.Messages, NewMessage(severity, path, err))
|
||||
|
||||
if severity > l.HighestSeverity {
|
||||
l.HighestSeverity = severity
|
||||
}
|
||||
}
|
||||
return err == nil
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
/*
|
||||
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 support
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var errLint = errors.New("lint failed")
|
||||
|
||||
func TestRunLinterRule(t *testing.T) {
|
||||
var tests = []struct {
|
||||
Severity int
|
||||
LintError error
|
||||
ExpectedMessages int
|
||||
ExpectedReturn bool
|
||||
ExpectedHighestSeverity int
|
||||
}{
|
||||
{InfoSev, errLint, 1, false, InfoSev},
|
||||
{WarningSev, errLint, 2, false, WarningSev},
|
||||
{ErrorSev, errLint, 3, false, ErrorSev},
|
||||
// No error so it returns true
|
||||
{ErrorSev, nil, 3, true, ErrorSev},
|
||||
// Retains highest severity
|
||||
{InfoSev, errLint, 4, false, ErrorSev},
|
||||
// Invalid severity values
|
||||
{4, errLint, 4, false, ErrorSev},
|
||||
{22, errLint, 4, false, ErrorSev},
|
||||
{-1, errLint, 4, false, ErrorSev},
|
||||
}
|
||||
|
||||
linter := Linter{}
|
||||
for _, test := range tests {
|
||||
isValid := linter.RunLinterRule(test.Severity, "chart", test.LintError)
|
||||
if len(linter.Messages) != test.ExpectedMessages {
|
||||
t.Errorf("RunLinterRule(%d, \"chart\", %v), linter.Messages should now have %d message, we got %d", test.Severity, test.LintError, test.ExpectedMessages, len(linter.Messages))
|
||||
}
|
||||
|
||||
if linter.HighestSeverity != test.ExpectedHighestSeverity {
|
||||
t.Errorf("RunLinterRule(%d, \"chart\", %v), linter.HighestSeverity should be %d, we got %d", test.Severity, test.LintError, test.ExpectedHighestSeverity, linter.HighestSeverity)
|
||||
}
|
||||
|
||||
if isValid != test.ExpectedReturn {
|
||||
t.Errorf("RunLinterRule(%d, \"chart\", %v), should have returned %t but returned %t", test.Severity, test.LintError, test.ExpectedReturn, isValid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMessage(t *testing.T) {
|
||||
m := Message{ErrorSev, "Chart.yaml", errors.New("Foo")}
|
||||
if m.Error() != "[ERROR] Chart.yaml: Foo" {
|
||||
t.Errorf("Unexpected output: %s", m.Error())
|
||||
}
|
||||
|
||||
m = Message{WarningSev, "templates/", errors.New("Bar")}
|
||||
if m.Error() != "[WARNING] templates/: Bar" {
|
||||
t.Errorf("Unexpected output: %s", m.Error())
|
||||
}
|
||||
|
||||
m = Message{InfoSev, "templates/rc.yaml", errors.New("FooBar")}
|
||||
if m.Error() != "[INFO] templates/rc.yaml: FooBar" {
|
||||
t.Errorf("Unexpected output: %s", m.Error())
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
/*
|
||||
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 loader
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
chart "helm.sh/helm/v4/internal/chart/v3"
|
||||
"helm.sh/helm/v4/pkg/chart/loader/archive"
|
||||
)
|
||||
|
||||
// FileLoader loads a chart from a file
|
||||
type FileLoader string
|
||||
|
||||
// Load loads a chart
|
||||
func (l FileLoader) Load() (*chart.Chart, error) {
|
||||
return LoadFile(string(l))
|
||||
}
|
||||
|
||||
// LoadFile loads from an archive file.
|
||||
func LoadFile(name string) (*chart.Chart, error) {
|
||||
if fi, err := os.Stat(name); err != nil {
|
||||
return nil, err
|
||||
} else if fi.IsDir() {
|
||||
return nil, errors.New("cannot load a directory")
|
||||
}
|
||||
|
||||
raw, err := os.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer raw.Close()
|
||||
|
||||
err = archive.EnsureArchive(name, raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := LoadArchive(raw)
|
||||
if err != nil {
|
||||
if errors.Is(err, gzip.ErrHeader) {
|
||||
return nil, fmt.Errorf("file '%s' does not appear to be a valid chart file (details: %w)", name, err)
|
||||
}
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
|
||||
// LoadArchive loads from a reader containing a compressed tar archive.
|
||||
func LoadArchive(in io.Reader) (*chart.Chart, error) {
|
||||
files, err := archive.LoadArchiveFiles(in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return LoadFiles(files)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue