Merge branch 'fix/to-pretty-json-html-escaping' of https://github.com/piotrlaczykowski/helm; branch 'main' of https://github.com/helm/helm into fix/to-pretty-json-html-escaping

pull/31964/head
piotr.laczykowski 4 months ago
commit 5203982eea

2
.github/env vendored

@ -1,2 +1,2 @@
GOLANG_VERSION=1.25 GOLANG_VERSION=1.26
GOLANGCI_LINT_VERSION=v2.11.3 GOLANGCI_LINT_VERSION=v2.11.3

@ -22,7 +22,7 @@ jobs:
- name: Add variables to environment file - name: Add variables to environment file
run: cat ".github/env" >> "$GITHUB_ENV" run: cat ".github/env" >> "$GITHUB_ENV"
- name: Setup Go - name: Setup Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # pin@6.2.0 uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # pin@6.4.0
with: with:
go-version: '${{ env.GOLANG_VERSION }}' go-version: '${{ env.GOLANG_VERSION }}'
check-latest: true check-latest: true

@ -24,14 +24,15 @@ on:
schedule: schedule:
- cron: '29 6 * * 6' - cron: '29 6 * * 6'
permissions: permissions: {}
contents: read
security-events: write
jobs: jobs:
analyze: analyze:
name: Analyze name: Analyze
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
strategy: strategy:
fail-fast: false fail-fast: false
@ -47,7 +48,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@e296a935590eb16afc0c0108289f68c87e2a89a5 # pinv4.30.7 uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # pinv4.36.0
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@ -58,7 +59,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@e296a935590eb16afc0c0108289f68c87e2a89a5 # pinv4.30.7 uses: github/codeql-action/autobuild@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # pinv4.36.0
# ℹ️ Command-line programs to run using the OS shell. # ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@ -72,4 +73,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@e296a935590eb16afc0c0108289f68c87e2a89a5 # pinv4.30.7 uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # pinv4.36.0

@ -17,11 +17,11 @@ jobs:
- name: Add variables to environment file - name: Add variables to environment file
run: cat ".github/env" >> "$GITHUB_ENV" run: cat ".github/env" >> "$GITHUB_ENV"
- name: Setup Go - name: Setup Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # pin@6.2.0 uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # pin@6.4.0
with: with:
go-version: '${{ env.GOLANG_VERSION }}' go-version: '${{ env.GOLANG_VERSION }}'
check-latest: true check-latest: true
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 #pin@9.2.0 uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee #pin@9.2.1
with: with:
version: ${{ env.GOLANGCI_LINT_VERSION }} version: ${{ env.GOLANGCI_LINT_VERSION }}

@ -25,7 +25,7 @@ jobs:
- name: Add variables to environment file - name: Add variables to environment file
run: cat ".github/env" >> "$GITHUB_ENV" run: cat ".github/env" >> "$GITHUB_ENV"
- name: Setup Go - name: Setup Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # pin@6.2.0 uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # pin@6.4.0
with: with:
go-version: '${{ env.GOLANG_VERSION }}' go-version: '${{ env.GOLANG_VERSION }}'
check-latest: true check-latest: true

@ -28,7 +28,7 @@ jobs:
run: cat ".github/env" >> "$GITHUB_ENV" run: cat ".github/env" >> "$GITHUB_ENV"
- name: Setup Go - name: Setup Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # pin@6.2.0 uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # pin@6.4.0
with: with:
go-version: '${{ env.GOLANG_VERSION }}' go-version: '${{ env.GOLANG_VERSION }}'
check-latest: true check-latest: true
@ -86,12 +86,14 @@ jobs:
steps: steps:
- name: Checkout source code - name: Checkout source code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2
with:
fetch-depth: 0
- name: Add variables to environment file - name: Add variables to environment file
run: cat ".github/env" >> "$GITHUB_ENV" run: cat ".github/env" >> "$GITHUB_ENV"
- name: Setup Go - name: Setup Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # pin@6.2.0 uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # pin@6.4.0
with: with:
go-version: '${{ env.GOLANG_VERSION }}' go-version: '${{ env.GOLANG_VERSION }}'
check-latest: true check-latest: true

@ -55,7 +55,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab. # format to the repository Actions tab.
- name: "Upload artifact" - name: "Upload artifact"
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: SARIF file name: SARIF file
path: results.sarif path: results.sarif
@ -64,6 +64,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard (optional). # Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard # Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@v4 uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
with: with:
sarif_file: results.sarif sarif_file: results.sarif

@ -3,11 +3,16 @@ on:
schedule: schedule:
- cron: "0 0 * * *" - cron: "0 0 * * *"
permissions: {}
jobs: jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps: steps:
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} 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-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.'

1
.gitignore vendored

@ -3,6 +3,7 @@
.DS_Store .DS_Store
.coverage/ .coverage/
.idea .idea
.claude
.vimrc .vimrc
.vscode/ .vscode/
.devcontainer/ .devcontainer/

@ -19,6 +19,7 @@ linters:
# Keep sorted alphabetically # Keep sorted alphabetically
enable: enable:
- bidichk
- depguard - depguard
- dupl - dupl
- exhaustive - exhaustive
@ -33,10 +34,12 @@ linters:
- revive - revive
- sloglint - sloglint
- staticcheck - staticcheck
- testifylint
- thelper - thelper
- unused - unused
- usestdlibvars - usestdlibvars
- usetesting - usetesting
- whitespace
exclusions: exclusions:
@ -95,6 +98,22 @@ linters:
- helpers - helpers
- models - 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: run:
timeout: 10m timeout: 10m

@ -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:'

@ -15,7 +15,7 @@ chance to try to fix the issue before it is exploited in the wild.
Helm v4 development takes place on the `main` branch while Helm v3 is on the `dev-v3` branch. Helm v4 development takes place on the `main` branch while Helm v3 is on the `dev-v3` branch.
Helm v3 will continue to receive bug fixes and updates for new Kubernetes releases until July 8th 2026. Security enhancement will still be applied until November 11th 2026. See the blog <https://helm.sh/blog/helm-4-released#helm-v3-support> for more details. Helm v3 will continue to receive bug fixes and updates for new Kubernetes releases until July 8th 2026. Security enhancements will still be applied until November 11th 2026. See the blog <https://helm.sh/blog/helm-4-released#helm-v3-support> for more details.
Bugs should first be fixed on Helm v4 and then backported to Helm v3. Helm v3 (and the `dev-v3` branch) is no longer accepting new features. Bugs should first be fixed on Helm v4 and then backported to Helm v3. Helm v3 (and the `dev-v3` branch) is no longer accepting new features.
@ -162,9 +162,9 @@ There are 5 types of issues (each with their own corresponding [label](#labels))
for future reference. Generally these are questions that are too complex or large to store in the for future reference. Generally these are questions that are too complex or large to store in the
Slack channel or have particular interest to the community as a whole. Depending on the Slack channel or have particular interest to the community as a whole. Depending on the
discussion, these can turn into `feature` or `bug` issues. discussion, these can turn into `feature` or `bug` issues.
- `proposal`: Used for items (like this one) that propose a new ideas or functionality that require - `proposal`: Used for items (like this one) that propose new ideas or functionality that require
a larger community discussion. This allows for feedback from others in the community before a a larger community discussion. This allows for feedback from others in the community before a
feature is actually developed. This is not needed for small additions. Final word on whether feature is actually developed. This is not needed for small additions. Final word on whether
a feature needs a proposal is up to the core maintainers. All issues that are proposals should a feature needs a proposal is up to the core maintainers. All issues that are proposals should
both have a label and an issue title of "Proposal: [the rest of the title]." A proposal can become both have a label and an issue title of "Proposal: [the rest of the title]." A proposal can become
a `feature` and does not require a milestone. a `feature` and does not require a milestone.

@ -1,7 +1,6 @@
BINDIR := $(CURDIR)/bin BINDIR := $(CURDIR)/bin
INSTALL_PATH ?= /usr/local/bin INSTALL_PATH ?= /usr/local/bin
DIST_DIRS := find * -type d -exec DIST_DIRS := find * -type d -exec
TARGETS := darwin/amd64 darwin/arm64 linux/amd64 linux/386 linux/arm linux/arm64 linux/loong64 linux/ppc64le linux/s390x linux/riscv64 windows/amd64 windows/arm64
TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 darwin-amd64.tar.gz.sha256sum darwin-arm64.tar.gz darwin-arm64.tar.gz.sha256 darwin-arm64.tar.gz.sha256sum linux-amd64.tar.gz linux-amd64.tar.gz.sha256 linux-amd64.tar.gz.sha256sum linux-386.tar.gz linux-386.tar.gz.sha256 linux-386.tar.gz.sha256sum linux-arm.tar.gz linux-arm.tar.gz.sha256 linux-arm.tar.gz.sha256sum linux-arm64.tar.gz linux-arm64.tar.gz.sha256 linux-arm64.tar.gz.sha256sum linux-loong64.tar.gz linux-loong64.tar.gz.sha256 linux-loong64.tar.gz.sha256sum linux-ppc64le.tar.gz linux-ppc64le.tar.gz.sha256 linux-ppc64le.tar.gz.sha256sum linux-s390x.tar.gz linux-s390x.tar.gz.sha256 linux-s390x.tar.gz.sha256sum linux-riscv64.tar.gz linux-riscv64.tar.gz.sha256 linux-riscv64.tar.gz.sha256sum windows-amd64.zip windows-amd64.zip.sha256 windows-amd64.zip.sha256sum windows-arm64.zip windows-arm64.zip.sha256 windows-arm64.zip.sha256sum TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 darwin-amd64.tar.gz.sha256sum darwin-arm64.tar.gz darwin-arm64.tar.gz.sha256 darwin-arm64.tar.gz.sha256sum linux-amd64.tar.gz linux-amd64.tar.gz.sha256 linux-amd64.tar.gz.sha256sum linux-386.tar.gz linux-386.tar.gz.sha256 linux-386.tar.gz.sha256sum linux-arm.tar.gz linux-arm.tar.gz.sha256 linux-arm.tar.gz.sha256sum linux-arm64.tar.gz linux-arm64.tar.gz.sha256 linux-arm64.tar.gz.sha256sum linux-loong64.tar.gz linux-loong64.tar.gz.sha256 linux-loong64.tar.gz.sha256sum linux-ppc64le.tar.gz linux-ppc64le.tar.gz.sha256 linux-ppc64le.tar.gz.sha256sum linux-s390x.tar.gz linux-s390x.tar.gz.sha256 linux-s390x.tar.gz.sha256sum linux-riscv64.tar.gz linux-riscv64.tar.gz.sha256 linux-riscv64.tar.gz.sha256sum windows-amd64.zip windows-amd64.zip.sha256 windows-amd64.zip.sha256sum windows-arm64.zip windows-arm64.zip.sha256 windows-arm64.zip.sha256sum
BINNAME ?= helm BINNAME ?= helm
@ -9,7 +8,7 @@ GOBIN = $(shell go env GOBIN)
ifeq ($(GOBIN),) ifeq ($(GOBIN),)
GOBIN = $(shell go env GOPATH)/bin GOBIN = $(shell go env GOPATH)/bin
endif endif
GOX = $(GOBIN)/gox GORELEASER = $(GOBIN)/goreleaser
GOIMPORTS = $(GOBIN)/goimports GOIMPORTS = $(GOBIN)/goimports
ARCH = $(shell go env GOARCH) ARCH = $(shell go env GOARCH)
@ -130,8 +129,7 @@ test-source-headers:
@scripts/validate-license.sh @scripts/validate-license.sh
.PHONY: test-acceptance .PHONY: test-acceptance
test-acceptance: TARGETS = linux/amd64 test-acceptance: build
test-acceptance: build build-cross
@if [ -d "${ACCEPTANCE_DIR}" ]; then \ @if [ -d "${ACCEPTANCE_DIR}" ]; then \
cd ${ACCEPTANCE_DIR} && \ cd ${ACCEPTANCE_DIR} && \
ROBOT_RUN_TESTS=$(ACCEPTANCE_RUN_TESTS) ROBOT_HELM_PATH='$(BINDIR)' make acceptance; \ ROBOT_RUN_TESTS=$(ACCEPTANCE_RUN_TESTS) ROBOT_HELM_PATH='$(BINDIR)' make acceptance; \
@ -162,8 +160,8 @@ gen-test-golden: test-unit
# dependencies to the go.mod file. To avoid that we change to a directory # dependencies to the go.mod file. To avoid that we change to a directory
# without a go.mod file when downloading the following dependencies # without a go.mod file when downloading the following dependencies
$(GOX): $(GORELEASER):
(cd /; go install github.com/mitchellh/gox@v1.0.2-0.20220701044238-9f712387e2d2) (cd /; go install github.com/goreleaser/goreleaser/v2@latest)
$(GOIMPORTS): $(GOIMPORTS):
(cd /; go install golang.org/x/tools/cmd/goimports@latest) (cd /; go install golang.org/x/tools/cmd/goimports@latest)
@ -173,8 +171,8 @@ $(GOIMPORTS):
.PHONY: build-cross .PHONY: build-cross
build-cross: LDFLAGS += -extldflags "-static" build-cross: LDFLAGS += -extldflags "-static"
build-cross: $(GOX) build-cross: $(GORELEASER)
GOFLAGS="-trimpath" CGO_ENABLED=0 $(GOX) -parallel=3 -output="_dist/{{.OS}}-{{.Arch}}/$(BINNAME)" -osarch='$(TARGETS)' $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/helm LDFLAGS='$(LDFLAGS)' $(GORELEASER) build --snapshot --clean
.PHONY: dist .PHONY: dist
dist: dist:

@ -32,8 +32,8 @@ Think of it like apt/yum/homebrew for Kubernetes.
## Helm Development and Stable Versions ## Helm Development and Stable Versions
Helm v4 is currently under development on the `main` branch. This is unstable and the APIs within the Go SDK and at the command line are changing. Helm v4 is the current stable release, developed on the `main` branch.
Helm v3 (current stable) is maintained on the `dev-v3` branch. APIs there follow semantic versioning. Helm v3 is in support mode on the `dev-v3` branch: bug fixes until July 8th 2026, security fixes until November 11th 2026.
## Install ## Install
@ -64,7 +64,7 @@ Get started with the [Quick Start guide](https://helm.sh/docs/intro/quickstart/)
The [Helm roadmap uses GitHub milestones](https://github.com/helm/helm/milestones) to track the progress of the project. The [Helm roadmap uses GitHub milestones](https://github.com/helm/helm/milestones) to track the progress of the project.
The development of Helm v4 is currently happening on the `main` branch while the development of Helm v3, the stable branch, is happening on the `dev-v3` branch. Changes should be made to the `main` branch prior to being added to the `dev-v3` branch so that all changes are carried along to Helm v4. 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 ## Community, discussion, contribution, and support

@ -69,7 +69,7 @@ func TestCliPluginExitCode(t *testing.T) {
assert.Empty(t, stdout.String()) assert.Empty(t, stdout.String())
expectedStderr := "Error: plugin \"exitwith\" exited with error\n" 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 { if stderr.String() != expectedStderr {
t.Errorf("Expected %q written to stderr: Got %q", expectedStderr, stderr.String()) t.Errorf("Expected %q written to stderr: Got %q", expectedStderr, stderr.String())
} }

131
go.mod

@ -1,30 +1,30 @@
module helm.sh/helm/v4 module helm.sh/helm/v4
go 1.25.0 go 1.26.0
require ( require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24
github.com/BurntSushi/toml v1.6.0 github.com/BurntSushi/toml v1.6.0
github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/Masterminds/semver/v3 v3.4.0 github.com/Masterminds/semver/v3 v3.5.0
github.com/Masterminds/sprig/v3 v3.3.0 github.com/Masterminds/sprig/v3 v3.3.0
github.com/Masterminds/squirrel v1.5.4 github.com/Masterminds/squirrel v1.5.4
github.com/Masterminds/vcs v1.13.3 github.com/Masterminds/vcs v1.13.3
github.com/ProtonMail/go-crypto v1.4.1 github.com/ProtonMail/go-crypto v1.4.1
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/cyphar/filepath-securejoin v0.6.1 github.com/cyphar/filepath-securejoin v0.6.1
github.com/distribution/distribution/v3 v3.0.0 github.com/distribution/distribution/v3 v3.1.1
github.com/evanphx/json-patch/v5 v5.9.11 github.com/evanphx/json-patch/v5 v5.9.11
github.com/extism/go-sdk v1.7.1 github.com/extism/go-sdk v1.7.1
github.com/fatih/color v1.19.0 github.com/fatih/color v1.19.0
github.com/fluxcd/cli-utils v0.37.2-flux.1 github.com/fluxcd/cli-utils v1.2.1
github.com/foxcpp/go-mockdns v1.2.0 github.com/foxcpp/go-mockdns v1.2.0
github.com/gobwas/glob v0.2.3 github.com/gobwas/glob v0.2.3
github.com/gofrs/flock v0.13.0 github.com/gofrs/flock v0.13.0
github.com/gosuri/uitable v0.0.4 github.com/gosuri/uitable v0.0.4
github.com/jmoiron/sqlx v1.4.0 github.com/jmoiron/sqlx v1.4.0
github.com/lib/pq v1.12.0 github.com/lib/pq v1.12.3
github.com/mattn/go-shellwords v1.0.12 github.com/mattn/go-shellwords v1.0.13
github.com/moby/term v0.5.2 github.com/moby/term v0.5.2
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1 github.com/opencontainers/image-spec v1.1.1
@ -33,22 +33,22 @@ require (
github.com/spf13/cobra v1.10.2 github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10 github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/tetratelabs/wazero v1.11.0 github.com/tetratelabs/wazero v1.12.0
go.yaml.in/yaml/v3 v3.0.4 go.yaml.in/yaml/v3 v3.0.4
golang.org/x/crypto v0.49.0 golang.org/x/crypto v0.52.0
golang.org/x/term v0.41.0 golang.org/x/term v0.43.0
golang.org/x/text v0.35.0 golang.org/x/text v0.37.0
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.35.1 k8s.io/api v0.36.1
k8s.io/apiextensions-apiserver v0.35.1 k8s.io/apiextensions-apiserver v0.36.1
k8s.io/apimachinery v0.35.1 k8s.io/apimachinery v0.36.1
k8s.io/apiserver v0.35.1 k8s.io/apiserver v0.36.1
k8s.io/cli-runtime v0.35.1 k8s.io/cli-runtime v0.36.1
k8s.io/client-go v0.35.1 k8s.io/client-go v0.36.1
k8s.io/klog/v2 v2.130.1 k8s.io/klog/v2 v2.140.0
k8s.io/kubectl v0.35.1 k8s.io/kubectl v0.36.1
oras.land/oras-go/v2 v2.6.0 oras.land/oras-go/v2 v2.6.0
sigs.k8s.io/controller-runtime v0.23.3 sigs.k8s.io/controller-runtime v0.24.1
sigs.k8s.io/kustomize/kyaml v0.21.1 sigs.k8s.io/kustomize/kyaml v0.21.1
sigs.k8s.io/yaml v1.6.0 sigs.k8s.io/yaml v1.6.0
) )
@ -60,21 +60,21 @@ require (
github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect
github.com/bshuster-repo/logrus-logstash-hook v1.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/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/cloudflare/circl v1.6.3 // indirect github.com/cloudflare/circl v1.6.3 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/coreos/go-systemd/v22 v22.7.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.6 // 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/distribution/reference v0.6.0 // indirect github.com/distribution/reference v0.6.0 // indirect
github.com/docker/docker-credential-helpers v0.8.2 // indirect github.com/docker/docker-credential-helpers v0.9.5 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-events v0.0.0-20250808211157-605354379745 // indirect
github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-metrics v0.0.1 // indirect
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect
@ -91,8 +91,7 @@ require (
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/mux v1.8.1 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect
github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect
github.com/hashicorp/golang-lru/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/huandu/xstrings v1.5.0 // indirect
@ -100,7 +99,7 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.18.4 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
@ -116,67 +115,67 @@ require (
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // 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/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/onsi/gomega v1.39.0 // indirect github.com/onsi/gomega v1.39.1 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // 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_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.5 // indirect github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/otlptranslator v1.0.0 // indirect github.com/prometheus/otlptranslator v1.0.0 // indirect
github.com/prometheus/procfs v0.19.2 // 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/rediscmd/v9 v9.0.5 // indirect
github.com/redis/go-redis/extra/redisotel/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/redis/go-redis/v9 v9.7.3 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.4 // indirect
github.com/spf13/cast v1.7.0 // indirect github.com/spf13/cast v1.7.0 // indirect
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
github.com/xlab/treeprint v1.2.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/bridges/prometheus v0.65.0 // indirect go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 // indirect
go.opentelemetry.io/contrib/exporters/autoexport v0.65.0 // indirect go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
go.opentelemetry.io/otel v1.40.0 // indirect go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/prometheus v0.62.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.64.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect
go.opentelemetry.io/otel/log v0.16.0 // indirect go.opentelemetry.io/otel/log v0.19.0 // indirect
go.opentelemetry.io/otel/metric v1.40.0 // indirect go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/sdk v1.40.0 // indirect go.opentelemetry.io/otel/sdk v1.43.0 // indirect
go.opentelemetry.io/otel/sdk/log v0.16.0 // indirect go.opentelemetry.io/otel/sdk/log v0.19.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.40.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/mod v0.33.0 // indirect golang.org/x/mod v0.35.0 // indirect
golang.org/x/net v0.51.0 // indirect golang.org/x/net v0.55.0 // indirect
golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.45.0 // indirect
golang.org/x/time v0.12.0 // indirect golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.42.0 // indirect golang.org/x/tools v0.44.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/grpc v1.79.3 // indirect google.golang.org/grpc v1.80.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/component-base v0.35.1 // indirect k8s.io/component-base v0.36.1 // indirect
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/kustomize/api v0.21.1 // indirect sigs.k8s.io/kustomize/api v0.21.1 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
) )

278
go.sum

@ -14,8 +14,8 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= 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 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= 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 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= 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 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
@ -26,6 +26,8 @@ github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67
github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= 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/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/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 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 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 v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@ -34,8 +36,8 @@ 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/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 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.1.0 h1:o2FzZifLg+z/DN1OFmzTWzZZx/roaqt8IPZCIVco8r4=
github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= 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.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 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
@ -51,8 +53,8 @@ github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNS
github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= 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 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 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 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 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 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
@ -65,22 +67,22 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN6UX90KJc4HjyM= github.com/distribution/distribution/v3 v3.1.1 h1:KUbk7C8CfaLXy8kbf/hGq9cad/wCoLB6dbWH6DMbmX0=
github.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU= 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 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= 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 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-events v0.0.0-20250808211157-605354379745 h1:yOn6Ze6IbYI/KAw2lw/83ELYvZh6hvsygTVkD0dzMC4=
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= 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 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= 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 h1:UwSIFv5g5lIvbGgtf3tVwC7Ky9rmMFBp0RMs+6f6YqE=
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a/go.mod h1:C8DzXehI4zAbrdlbtOByKX6pfivJTBiV9Jjqv56Yd9Q= github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a/go.mod h1:C8DzXehI4zAbrdlbtOByKX6pfivJTBiV9Jjqv56Yd9Q=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 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 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= 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 h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4=
@ -91,8 +93,8 @@ 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/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 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fluxcd/cli-utils v0.37.2-flux.1 h1:tQ588ghtRN+E+kHq415FddfqA9v4brn/1WWgrP6rQR0= github.com/fluxcd/cli-utils v1.2.1 h1:ug9CicKW7H9QXnvNDapTSKuryZvWcu4Nw7pRvQa6jDY=
github.com/fluxcd/cli-utils v0.37.2-flux.1/go.mod h1:LcWSu1NYET8d8U7O326RhEm5JkQXCMK6ITu4G1CT02c= 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 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0=
github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= 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 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
@ -126,7 +128,6 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 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 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= 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/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/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@ -153,10 +154,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= 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 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII=
github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= 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/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 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4=
@ -177,8 +176,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 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/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/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 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/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/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 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@ -192,8 +191,8 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtB
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= 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/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.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.12.0 h1:mC1zeiNamwKBecjHarAr26c/+d8V5w/u4J0I/yASbJo= github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
github.com/lib/pq v1.12.0/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= 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 h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= 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 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
@ -204,8 +203,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.13 h1:DC0OMEpGjm6LfNFU4ckYcvbQKyp2vE8atyFGXNtDcf4=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= 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 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 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/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@ -234,8 +233,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 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 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=
github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q= github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= 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 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 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 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
@ -266,8 +265,8 @@ github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVR
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 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.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= 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 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/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 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc=
@ -288,8 +287,9 @@ github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepq
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= 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.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 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 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 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 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
@ -309,67 +309,69 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 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 h1:ZF+QBjOI+tILZjBaFj3HgFonKXUcwgJ4djLb6i42S3Q=
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk= github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk=
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA= github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU=
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU= 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 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 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 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= 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/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 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/bridges/prometheus v0.65.0 h1:I/7S/yWobR3QHFLqHsJ8QOndoiFsj1VgHpQiq43KlUI= go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 h1:dkBzNEAIKADEaFnuESzcXvpd09vxvDZsOjx11gjUqLk=
go.opentelemetry.io/contrib/bridges/prometheus v0.65.0/go.mod h1:jPF6gn3y1E+nozCAEQj3c6NZ8KY+tvAgSVfvoOJUFac= go.opentelemetry.io/contrib/bridges/prometheus v0.67.0/go.mod h1:Z5RIwRkZgauOIfnG5IpidvLpERjhTninpP1dTG2jTl4=
go.opentelemetry.io/contrib/exporters/autoexport v0.65.0 h1:2gApdml7SznX9szEKFjKjM4qGcGSvAybYLBY319XG3g= go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9achKn3XgmxPxuMuqIL5rE8e8=
go.opentelemetry.io/contrib/exporters/autoexport v0.65.0/go.mod h1:0QqAGlbHXhmPYACG3n5hNzO5DnEqqtg4VcK5pr22RI0= 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.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 h1:ZVg+kCXxd9LtAaQNKBxAvJ5NpMf7LpvEr4MIZqb0TMQ= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0/go.mod h1:hh0tMeZ75CCXrHd9OXRYxTlCAdxcXioWHFIpYw2rZu8= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 h1:djrxvDxAe44mJUrKataUbOhCKhR3F8QCyWucO16hTQs= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0/go.mod h1:dt3nxpQEiSoKvfTVxp3TUg5fHPLhKtbcnN3Z1I1ePD0= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0/go.mod h1:ji9vId85hMxqfvICA0Jt8JqEdrXaAkcpkI9HPXya0ro=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 h1:NOyNnS19BF2SUDApbOKbDtWZ0IK7b8FJ2uAGdIWOGb0= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0/go.mod h1:VL6EgVikRLcJa9ftukrHu/ZkkhFBSo1lzvdBC9CF1ss= 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.40.0 h1:9y5sHvAxWzft1WQ4BwqcvA+IFVUJ1Ya75mSAUnFEVwE= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K+pCJoPpQifuVpsKamUdn9U0zM3xUziVOqsGksUrY=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0/go.mod h1:eQqT90eR3X5Dbs1g9YSM30RavwLF725Ris5/XSXWvqE= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0/go.mod h1:HBy4BjzgVE8139ieRI75oXm3EcDN+6GhD88JT1Kjvxg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= 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.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= 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.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40= 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.62.0 h1:krvC4JMfIOVdEuNPTtQ0ZjCiXrybhv+uOHMfHRmnvVo= go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs=
go.opentelemetry.io/otel/exporters/prometheus v0.62.0/go.mod h1:fgOE6FM/swEnsVQCqCnbOfRV4tOnWPg7bVeo4izBuhQ= go.opentelemetry.io/otel/exporters/prometheus v0.64.0/go.mod h1:UrgcjnarfdlBDP3GjDIJWe6HTprwSazNjwsI+Ru6hro=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0 h1:ivlbaajBWJqhcCPniDqDJmRwj4lc6sRT+dCAVKNmxlQ= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 h1:KJVjPD3rcPb98rIs3HznyJlrfx9ge5oJvxxlGR+P/7s=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0/go.mod h1:u/G56dEKDDwXNCVLsbSrllB2o8pbtFLUC4HpR66r2dc= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0/go.mod h1:K3kRa2ckmHWQaTWQdPRHc7qGXASuVuoEQXzrvlA98Ws=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 h1:ZrPRak/kS4xI3AVXy8F7pipuDXmDsrO8Lg+yQjBLjw0= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 h1:lSZHgNHfbmQTPfuTmWVkEu8J8qXaQwuV30pjCcAUvP8=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0/go.mod h1:3y6kQCWztq6hyW8Z9YxQDDm0Je9AJoFar2G0yDcmhRk= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0/go.mod h1:so9ounLcuoRDu033MW/E0AD4hhUjVqswrMF5FoZlBcw=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs=
go.opentelemetry.io/otel/log v0.16.0 h1:DeuBPqCi6pQwtCK0pO4fvMB5eBq6sNxEnuTs88pjsN4= go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4=
go.opentelemetry.io/otel/log v0.16.0/go.mod h1:rWsmqNVTLIA8UnwYVOItjyEZDbKIkMxdQunsIhpUMes= go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/log v0.16.0 h1:e/b4bdlQwC5fnGtG3dlXUrNOnP7c8YLVSpSfEBIkTnI= go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko=
go.opentelemetry.io/otel/sdk/log v0.16.0/go.mod h1:JKfP3T6ycy7QEuv3Hj8oKDy7KItrEkus8XJE6EoSzw4= go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg=
go.opentelemetry.io/otel/sdk/log/logtest v0.16.0 h1:/XVkpZ41rVRTP4DfMgYv1nEtNmf65XPPyAdqV90TMy4= go.opentelemetry.io/otel/sdk/log/logtest v0.19.0 h1:BEbF7ZBB6qQloV/Ub1+3NQoOUnVtcGkU3XX4Ws3GQfk=
go.opentelemetry.io/otel/sdk/log/logtest v0.16.0/go.mod h1:iOOPgQr5MY9oac/F5W86mXdeyWZGleIx3uXO98X2R6Y= go.opentelemetry.io/otel/sdk/log/logtest v0.19.0/go.mod h1:Lua81/3yM0wOmoHTokLj9y9ADeA02v1naRrVrkAZuKk=
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= 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 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 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 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= 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 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
@ -380,14 +382,14 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 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.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.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.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -398,10 +400,10 @@ 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.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 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.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= 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-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-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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -416,11 +418,11 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-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-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-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-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-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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -428,8 +430,8 @@ 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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.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.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.45.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-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.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@ -437,8 +439,8 @@ 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.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= 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.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@ -446,29 +448,29 @@ 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.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.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.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 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-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.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.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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= 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-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 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/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 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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@ -483,32 +485,32 @@ 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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.35.1 h1:0PO/1FhlK/EQNVK5+txc4FuhQibV25VLSdLMmGpDE/Q= k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY=
k8s.io/api v0.35.1/go.mod h1:28uR9xlXWml9eT0uaGo6y71xK86JBELShLy4wR1XtxM= k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo=
k8s.io/apiextensions-apiserver v0.35.1 h1:p5vvALkknlOcAqARwjS20kJffgzHqwyQRM8vHLwgU7w= k8s.io/apiextensions-apiserver v0.36.1 h1:6JfYmPUsuUIHuN+3QxutXYWj492RqF5fBSx67GYK5Ks=
k8s.io/apiextensions-apiserver v0.35.1/go.mod h1:2CN4fe1GZ3HMe4wBr25qXyJnJyZaquy4nNlNmb3R7AQ= k8s.io/apiextensions-apiserver v0.36.1/go.mod h1:pLzZin90riwisdzKwv/GoTwENooytoIx5zWJb4Hkby8=
k8s.io/apimachinery v0.35.1 h1:yxO6gV555P1YV0SANtnTjXYfiivaTPvCTKX6w6qdDsU= k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA=
k8s.io/apimachinery v0.35.1/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8=
k8s.io/apiserver v0.35.1 h1:potxdhhTL4i6AYAa2QCwtlhtB1eCdWQFvJV6fXgJzxs= k8s.io/apiserver v0.36.1 h1:iMS5V+rPUertv5P9RaqJgmHHTuh4quWpoxchvMUY+JY=
k8s.io/apiserver v0.35.1/go.mod h1:BiL6Dd3A2I/0lBnteXfWmCFobHM39vt5+hJQd7Lbpi4= k8s.io/apiserver v0.36.1/go.mod h1:Cby1PbLWztu0GDOxoO6iFOyyqIsziHNEW+w9zVQ22Kw=
k8s.io/cli-runtime v0.35.1 h1:uKcXFe8J7AMAM4Gm2JDK4mp198dBEq2nyeYtO+JfGJE= k8s.io/cli-runtime v0.36.1 h1:yuC/BGnnj1YYPh6D1P+pZnzinCs6DvMq86yAeNqoqzM=
k8s.io/cli-runtime v0.35.1/go.mod h1:55/hiXIq1C8qIJ3WBrWxEwDLdHQYhBNRdZOz9f7yvTw= k8s.io/cli-runtime v0.36.1/go.mod h1:ZQWHGt8xAF7KnviB79vX0lYNyUUqKIpU+LQg7exuFAw=
k8s.io/client-go v0.35.1 h1:+eSfZHwuo/I19PaSxqumjqZ9l5XiTEKbIaJ+j1wLcLM= k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0=
k8s.io/client-go v0.35.1/go.mod h1:1p1KxDt3a0ruRfc/pG4qT/3oHmUj1AhSHEcxNSGg+OA= k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU=
k8s.io/component-base v0.35.1 h1:XgvpRf4srp037QWfGBLFsYMUQJkE5yMa94UsJU7pmcE= k8s.io/component-base v0.36.1 h1:iG6GsELftXqTNG9HG6kiVjatSgAw1sf5pJ6R5a6N0kA=
k8s.io/component-base v0.35.1/go.mod h1:HI/6jXlwkiOL5zL9bqA3en1Ygv60F03oEpnuU1G56Bs= k8s.io/component-base v0.36.1/go.mod h1:nf9XPlntRdqO6WMeEWAA5F93Y4ICZQdeT9GeqLDB3JI=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
k8s.io/kubectl v0.35.1 h1:zP3Er8C5i1dcAFUMh9Eva0kVvZHptXIn/+8NtRWMxwg= k8s.io/kubectl v0.36.1 h1:96HqS9twIdHM0MlJLTwbo14b9kUKPkOzZ4tlRDLv4qI=
k8s.io/kubectl v0.35.1/go.mod h1:cQ2uAPs5IO/kx8R5s5J3Ihv3VCYwrx0obCXum0CvnXo= k8s.io/kubectl v0.36.1/go.mod h1:/DGPAIewKsFWF9VFgGvkPhao2Ev4SNuE3BioZo8yPbk=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc=
oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o=
sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80= sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4=
sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= 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 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= 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 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs=
@ -517,7 +519,7 @@ sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7
sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= 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 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs= sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= 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 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

@ -104,7 +104,7 @@ func TestMetadata(t *testing.T) {
is.Equal("foo.yaml", chrt.Name()) is.Equal("foo.yaml", chrt.Name())
is.Equal("1.0.0", chrt.AppVersion()) is.Equal("1.0.0", chrt.AppVersion())
is.Equal(nil, chrt.Validate()) is.NoError(chrt.Validate())
} }
func TestIsRoot(t *testing.T) { func TestIsRoot(t *testing.T) {
@ -124,8 +124,8 @@ func TestIsRoot(t *testing.T) {
is := assert.New(t) is := assert.New(t)
is.Equal(false, chrt1.IsRoot()) is.False(chrt1.IsRoot())
is.Equal(true, chrt2.IsRoot()) is.True(chrt2.IsRoot())
} }
func TestChartPath(t *testing.T) { func TestChartPath(t *testing.T) {

@ -44,7 +44,6 @@ func WithSkipSchemaValidation(skipSchemaValidation bool) LinterOption {
} }
func RunAll(baseDir string, values map[string]any, namespace string, options ...LinterOption) support.Linter { func RunAll(baseDir string, values map[string]any, namespace string, options ...LinterOption) support.Linter {
chartDir, _ := filepath.Abs(baseDir) chartDir, _ := filepath.Abs(baseDir)
lo := linterOptions{} lo := linterOptions{}

@ -28,7 +28,7 @@ import (
kscheme "k8s.io/client-go/kubernetes/scheme" kscheme "k8s.io/client-go/kubernetes/scheme"
) )
// deprecatedAPIError indicates than an API is deprecated in Kubernetes // deprecatedAPIError indicates that an API is deprecated in Kubernetes
type deprecatedAPIError struct { type deprecatedAPIError struct {
Deprecated string Deprecated string
Message string Message string

@ -28,8 +28,8 @@ import (
"slices" "slices"
"strings" "strings"
"k8s.io/apimachinery/pkg/api/validate/content"
"k8s.io/apimachinery/pkg/api/validation" "k8s.io/apimachinery/pkg/api/validation"
apipath "k8s.io/apimachinery/pkg/api/validation/path"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apimachinery/pkg/util/yaml" "k8s.io/apimachinery/pkg/util/yaml"
@ -292,7 +292,7 @@ func validateMetadataNameFunc(obj *k8sYamlStruct) validation.ValidateNameFunc {
case "role", "clusterrole", "rolebinding", "clusterrolebinding": case "role", "clusterrole", "rolebinding", "clusterrolebinding":
// https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/rbac/validation/validation.go#L32-L34 // https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/rbac/validation/validation.go#L32-L34
return func(name string, _ bool) []string { return func(name string, _ bool) []string {
return apipath.IsValidPathSegmentName(name) return content.IsPathSegmentName(name)
} }
default: default:
return validation.NameIsDNSSubdomain return validation.NameIsDNSSubdomain

@ -242,7 +242,6 @@ data:
// //
// See https://github.com/helm/helm/issues/7483 // See https://github.com/helm/helm/issues/7483
func TestStrictTemplateParsingMapError(t *testing.T) { func TestStrictTemplateParsingMapError(t *testing.T) {
ch := chart.Chart{ ch := chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
Name: "regression7483", Name: "regression7483",
@ -371,7 +370,6 @@ func TestValidateTopIndentLevel(t *testing.T) {
t.Errorf("Expected %t for %q", shouldFail, doc) t.Errorf("Expected %t for %q", shouldFail, doc)
} }
} }
} }
// TestEmptyWithCommentsManifests checks the lint is not failing against empty manifests that contains only comments // TestEmptyWithCommentsManifests checks the lint is not failing against empty manifests that contains only comments
@ -463,5 +461,4 @@ func TestIsYamlFileExtension(t *testing.T) {
t.Errorf("isYamlFileExtension(%s) = %v; want %v", test.filename, result, test.expected) t.Errorf("isYamlFileExtension(%s) = %v; want %v", test.filename, result, test.expected)
} }
} }
} }

@ -337,7 +337,6 @@ icon: https://example.com/64x64.png
if text.String() != "" { if text.String() != "" {
t.Errorf("Expected no message to Stderr, got %s", text.String()) t.Errorf("Expected no message to Stderr, got %s", text.String())
} }
} }
// Packaging the chart on a Windows machine will produce an // Packaging the chart on a Windows machine will produce an
@ -607,7 +606,6 @@ func verifyChart(t *testing.T, c *chart.Chart) {
t.Errorf("Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version) t.Errorf("Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version)
} }
} }
} }
func verifyDependencies(t *testing.T, c *chart.Chart) { func verifyDependencies(t *testing.T, c *chart.Chart) {

@ -112,6 +112,9 @@ func (md *Metadata) Validate() error {
return ValidationError("chart.metadata.name is required") return ValidationError("chart.metadata.name is required")
} }
if md.Name == "." || md.Name == ".." {
return ValidationErrorf("chart.metadata.name %q is not allowed", md.Name)
}
if md.Name != filepath.Base(md.Name) { if md.Name != filepath.Base(md.Name) {
return ValidationErrorf("chart.metadata.name %q is invalid", md.Name) return ValidationErrorf("chart.metadata.name %q is invalid", md.Name)
} }

@ -41,6 +41,16 @@ func TestValidate(t *testing.T) {
&Metadata{APIVersion: "v3", Version: "1.0"}, &Metadata{APIVersion: "v3", Version: "1.0"},
ValidationError("chart.metadata.name is required"), ValidationError("chart.metadata.name is required"),
}, },
{
"chart with dot name",
&Metadata{Name: ".", APIVersion: "v3", Version: "1.0"},
ValidationError("chart.metadata.name \".\" is not allowed"),
},
{
"chart with dotdot name",
&Metadata{Name: "..", APIVersion: "v3", Version: "1.0"},
ValidationError("chart.metadata.name \"..\" is not allowed"),
},
{ {
"chart without name", "chart without name",
&Metadata{Name: "../../test", APIVersion: "v3", Version: "1.0"}, &Metadata{Name: "../../test", APIVersion: "v3", Version: "1.0"},

@ -702,7 +702,6 @@ func CreateFrom(chartfile *chart.Metadata, dest, src string) error {
// error. In such a case, this will attempt to clean up by removing the // error. In such a case, this will attempt to clean up by removing the
// new chart directory. // new chart directory.
func Create(name, dir string) (string, error) { func Create(name, dir string) (string, error) {
// Sanity-check the name of a chart so user doesn't create one that causes problems. // Sanity-check the name of a chart so user doesn't create one that causes problems.
if err := validateChartName(name); err != nil { if err := validateChartName(name); err != nil {
return "", err return "", err

@ -19,6 +19,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
"slices"
"strings" "strings"
chart "helm.sh/helm/v4/internal/chart/v3" chart "helm.sh/helm/v4/internal/chart/v3"
@ -242,8 +243,8 @@ func set(path []string, data map[string]any) map[string]any {
return nil return nil
} }
cur := data cur := data
for i := len(path) - 1; i >= 0; i-- { for _, v := range slices.Backward(path) {
cur = map[string]any{path[i]: cur} cur = map[string]any{v: cur}
} }
return cur return cur
} }

@ -459,7 +459,6 @@ func TestDependentChartAliases(t *testing.T) {
if aliasChart := getAliasDependency(c.Dependencies(), req[2]); aliasChart != nil { if aliasChart := getAliasDependency(c.Dependencies(), req[2]); aliasChart != nil {
t.Fatalf("expected no chart but got %s", aliasChart.Name()) t.Fatalf("expected no chart but got %s", aliasChart.Name())
} }
} }
func TestDependentChartWithSubChartsAbsentInDependency(t *testing.T) { func TestDependentChartWithSubChartsAbsentInDependency(t *testing.T) {

@ -42,4 +42,4 @@ into a Chart.
When creating charts in memory, use the 'helm.sh/helm/pkg/chart' When creating charts in memory, use the 'helm.sh/helm/pkg/chart'
package directly. package directly.
*/ */
package util // import chartutil "helm.sh/helm/v4/internal/chart/v3/util" package util // import "helm.sh/helm/v4/internal/chart/v3/util"

@ -52,6 +52,17 @@ func Expand(dir string, r io.Reader) error {
return errors.New("chart name not specified") return errors.New("chart name not specified")
} }
// Reject chart names that are POSIX path dot-segments or dot-dot segments or contain path separators.
// A dot-segment name (e.g. ".") causes SecureJoin to resolve to the root
// directory and extraction then to write files directly into that extraction root
// instead of a per-chart subdirectory.
if chartName == "." || chartName == ".." {
return fmt.Errorf("chart name %q is not allowed", chartName)
}
if chartName != filepath.Base(chartName) {
return fmt.Errorf("chart name %q must not contain path separators", chartName)
}
// Find the base directory // Find the base directory
// The directory needs to be cleaned prior to passing to SecureJoin or the location may end up // The directory needs to be cleaned prior to passing to SecureJoin or the location may end up
// being wrong or returning an error. This was introduced in v0.4.0. // being wrong or returning an error. This was introduced in v0.4.0.
@ -61,6 +72,12 @@ func Expand(dir string, r io.Reader) error {
return err return err
} }
// Defense-in-depth: the chart directory must be a subdirectory of dir,
// never dir itself.
if chartdir == dir {
return fmt.Errorf("chart name %q resolves to the extraction root", chartName)
}
// Copy all files verbatim. We don't parse these files because parsing can remove // Copy all files verbatim. We don't parse these files because parsing can remove
// comments. // comments.
for _, file := range files { for _, file := range files {

@ -17,11 +17,73 @@ limitations under the License.
package util package util
import ( import (
"archive/tar"
"bytes"
"compress/gzip"
"io/fs"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
// makeTestChartArchive builds a gzipped tar archive from the given sourceDir directory, file entries are prefixed with the given chartName
func makeTestChartArchive(t *testing.T, chartName, sourceDir string) *bytes.Buffer {
t.Helper()
var result bytes.Buffer
gw := gzip.NewWriter(&result)
tw := tar.NewWriter(gw)
dir := os.DirFS(sourceDir)
writeFile := func(relPath string) {
t.Helper()
f, err := dir.Open(relPath)
require.NoError(t, err)
fStat, err := f.Stat()
require.NoError(t, err)
err = tw.WriteHeader(&tar.Header{
Name: filepath.Join(chartName, relPath),
Mode: int64(fStat.Mode()),
Size: fStat.Size(),
})
require.NoError(t, err)
data, err := fs.ReadFile(dir, relPath)
require.NoError(t, err)
tw.Write(data)
}
err := fs.WalkDir(dir, ".", func(path string, d os.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if d.IsDir() {
return nil
}
writeFile(path)
return nil
})
if err != nil {
t.Fatal(err)
}
err = tw.Close()
require.NoError(t, err)
err = gw.Close()
require.NoError(t, err)
return &result
}
func TestExpand(t *testing.T) { func TestExpand(t *testing.T) {
dest := t.TempDir() dest := t.TempDir()
@ -75,6 +137,28 @@ func TestExpand(t *testing.T) {
} }
} }
func TestExpandError(t *testing.T) {
tests := map[string]struct {
chartName string
chartDir string
wantErr string
}{
"dot name": {"dotname", "testdata/dotname", "not allowed"},
"dotdot name": {"dotdotname", "testdata/dotdotname", "not allowed"},
"slash in name": {"slashinname", "testdata/slashinname", "must not contain path separators"},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
archive := makeTestChartArchive(t, tt.chartName, tt.chartDir)
dest := t.TempDir()
err := Expand(dest, archive)
assert.ErrorContains(t, err, tt.wantErr)
})
}
}
func TestExpandFile(t *testing.T) { func TestExpandFile(t *testing.T) {
dest := t.TempDir() dest := t.TempDir()

@ -0,0 +1,4 @@
apiVersion: v3
name: ..
description: A Helm chart for Kubernetes
version: 0.1.0

@ -0,0 +1,4 @@
apiVersion: v3
name: .
description: A Helm chart for Kubernetes
version: 0.1.0

@ -0,0 +1,4 @@
apiVersion: v3
name: a/../b
description: A Helm chart for Kubernetes
version: 0.1.0

@ -79,7 +79,6 @@ func ValidateReleaseName(name string) error {
// This case is preserved for backwards compatibility // This case is preserved for backwards compatibility
if name == "" { if name == "" {
return errMissingName return errMissingName
} }
if len(name) > maxReleaseNameLen || !validName.MatchString(name) { if len(name) > maxReleaseNameLen || !validName.MatchString(name) {
return errInvalidName return errInvalidName

@ -24,7 +24,6 @@ import (
) )
func TestColorizeStatus(t *testing.T) { func TestColorizeStatus(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
status common.Status status common.Status
@ -107,7 +106,6 @@ func TestColorizeStatus(t *testing.T) {
} }
func TestColorizeHeader(t *testing.T) { func TestColorizeHeader(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
header string header string
@ -149,7 +147,6 @@ func TestColorizeHeader(t *testing.T) {
} }
func TestColorizeNamespace(t *testing.T) { func TestColorizeNamespace(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
namespace string namespace string

@ -33,7 +33,6 @@ type Client struct {
// New creates a new client // New creates a new client
func New(u string) (*Client, error) { func New(u string) (*Client, error) {
// Validate we have a URL // Validate we have a URL
if err := validate(u); err != nil { if err := validate(u); err != nil {
return nil, err return nil, err
@ -46,7 +45,6 @@ func New(u string) (*Client, error) {
// Validate if the base URL for monocular is valid. // Validate if the base URL for monocular is valid.
func validate(u string) error { func validate(u string) error {
// Check if it is parsable // Check if it is parsable
p, err := url.Parse(u) p, err := url.Parse(u)
if err != nil { if err != nil {

@ -99,7 +99,6 @@ type ChartVersion struct {
// Search performs a search against the monocular search API // Search performs a search against the monocular search API
func (c *Client) Search(term string) ([]SearchResult, error) { func (c *Client) Search(term string) ([]SearchResult, error) {
// Create the URL to the search endpoint // Create the URL to the search endpoint
// Note, this is currently an internal API for the Hub. This should be // Note, this is currently an internal API for the Hub. This should be
// formatted without showing how monocular operates. // formatted without showing how monocular operates.

@ -27,7 +27,6 @@ import (
var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}` var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}`
func TestSearch(t *testing.T) { func TestSearch(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprintln(w, searchResult) fmt.Fprintln(w, searchResult)
})) }))

@ -35,7 +35,6 @@ func TestPath(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Setenv("HELM_PLUGINS", tt.helmPluginsDir) t.Setenv("HELM_PLUGINS", tt.helmPluginsDir)
baseIns := newBase(tt.source) baseIns := newBase(tt.source)
baseInsPath := baseIns.Path() baseInsPath := baseIns.Path()

@ -90,7 +90,6 @@ func NewExtractor(source string) (Extractor, error) {
// - Beginning a path with a path separator is illegal // - Beginning a path with a path separator is illegal
// - Rudimentary symlink protections are offered by SecureJoin. // - Rudimentary symlink protections are offered by SecureJoin.
func cleanJoin(root, dest string) (string, error) { func cleanJoin(root, dest string) (string, error) {
// On Windows, this is a drive separator. On UNIX-like, this is the path list separator. // On Windows, this is a drive separator. On UNIX-like, this is the path list separator.
// In neither case do we want to trust a TAR that contains these. // In neither case do we want to trust a TAR that contains these.
if strings.Contains(dest, ":") { if strings.Contains(dest, ":") {

@ -124,7 +124,6 @@ func TestHTTPInstaller(t *testing.T) {
} else if err.Error() != "plugin already exists" { } else if err.Error() != "plugin already exists" {
t.Fatalf("expected error for plugin exists, got (%v)", err) t.Fatalf("expected error for plugin exists, got (%v)", err)
} }
} }
func TestHTTPInstallerNonExistentVersion(t *testing.T) { func TestHTTPInstallerNonExistentVersion(t *testing.T) {
@ -157,7 +156,6 @@ func TestHTTPInstallerNonExistentVersion(t *testing.T) {
if err := Install(i); err == nil { if err := Install(i); err == nil {
t.Fatal("expected error from http client") t.Fatal("expected error from http client")
} }
} }
func TestHTTPInstallerUpdate(t *testing.T) { func TestHTTPInstallerUpdate(t *testing.T) {
@ -297,7 +295,6 @@ func TestExtract(t *testing.T) {
t.Fatalf("Expected %s to have %o mode but has %o (umask: %o)", t.Fatalf("Expected %s to have %o mode but has %o (umask: %o)",
readmeFullPath, expectedReadmePerm, info.Mode().Perm(), currentUmask) readmeFullPath, expectedReadmePerm, info.Mode().Perm(), currentUmask)
} }
} }
func TestCleanJoin(t *testing.T) { func TestCleanJoin(t *testing.T) {
@ -327,11 +324,9 @@ func TestCleanJoin(t *testing.T) {
t.Errorf("Test %d: Expected %q but got %q", i, fixture.expect, out) t.Errorf("Test %d: Expected %q but got %q", i, fixture.expect, out)
} }
} }
} }
func TestMediaTypeToExtension(t *testing.T) { func TestMediaTypeToExtension(t *testing.T) {
for mt, shouldPass := range map[string]bool{ for mt, shouldPass := range map[string]bool{
"": false, "": false,
"application/gzip": true, "application/gzip": true,

@ -72,7 +72,6 @@ type VerificationResult struct {
// InstallWithOptions installs a plugin with options. // InstallWithOptions installs a plugin with options.
func InstallWithOptions(i Installer, opts Options) (*VerificationResult, error) { func InstallWithOptions(i Installer, opts Options) (*VerificationResult, error) {
if err := os.MkdirAll(filepath.Dir(i.Path()), 0755); err != nil { if err := os.MkdirAll(filepath.Dir(i.Path()), 0755); err != nil {
return nil, err return nil, err
} }
@ -98,24 +97,23 @@ func InstallWithOptions(i Installer, opts Options) (*VerificationResult, error)
// Check if provenance data exists // Check if provenance data exists
if len(provData) == 0 { if len(provData) == 0 {
// No .prov file found - emit warning but continue installation return nil, errors.New("plugin verification failed: no provenance file (.prov) found")
fmt.Fprint(os.Stderr, "WARNING: No provenance file found for plugin. Plugin is not signed and cannot be verified.\n") }
} else {
// Provenance data exists - verify the plugin
verification, err := plugin.VerifyPlugin(archiveData, provData, filename, opts.Keyring)
if err != nil {
return nil, fmt.Errorf("plugin verification failed: %w", err)
}
// Collect verification info // Provenance data exists - verify the plugin
result = &VerificationResult{ verification, err := plugin.VerifyPlugin(archiveData, provData, filename, opts.Keyring)
SignedBy: make([]string, 0), if err != nil {
Fingerprint: fmt.Sprintf("%X", verification.SignedBy.PrimaryKey.Fingerprint), return nil, fmt.Errorf("plugin verification failed: %w", err)
FileHash: verification.FileHash, }
}
for name := range verification.SignedBy.Identities { // Collect verification info
result.SignedBy = append(result.SignedBy, name) result = &VerificationResult{
} SignedBy: make([]string, 0),
Fingerprint: fmt.Sprintf("%X", verification.SignedBy.PrimaryKey.Fingerprint),
FileHash: verification.FileHash,
}
for name := range verification.SignedBy.Identities {
result.SignedBy = append(result.SignedBy, name)
} }
} }

@ -185,5 +185,4 @@ func TestVCSInstallerUpdate(t *testing.T) {
} else if err.Error() != "plugin repo was modified" { } else if err.Error() != "plugin repo was modified" {
t.Fatalf("expected error for plugin modified, got (%v)", err) t.Fatalf("expected error for plugin modified, got (%v)", err)
} }
} }

@ -16,10 +16,8 @@ limitations under the License.
package installer package installer
import ( import (
"bytes"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"io"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -44,33 +42,49 @@ func TestInstallWithOptions_VerifyMissingProvenance(t *testing.T) {
} }
defer os.RemoveAll(installer.Path()) defer os.RemoveAll(installer.Path())
// Capture stderr to check warning message // Install with verification enabled should fail when .prov is missing
oldStderr := os.Stderr
r, w, _ := os.Pipe()
os.Stderr = w
// Install with verification enabled (should warn but succeed)
result, err := InstallWithOptions(installer, Options{Verify: true, Keyring: "dummy"}) result, err := InstallWithOptions(installer, Options{Verify: true, Keyring: "dummy"})
// Restore stderr and read captured output // Should fail with a missing provenance error
w.Close() if err == nil {
os.Stderr = oldStderr t.Fatal("Expected installation to fail when .prov file is missing and verification is enabled")
var buf bytes.Buffer }
io.Copy(&buf, r) if !strings.Contains(err.Error(), "no provenance file") {
output := buf.String() t.Errorf("Expected 'no provenance file' in error message, got: %v", err)
// Should succeed with nil result (no verification performed)
if err != nil {
t.Fatalf("Expected installation to succeed despite missing .prov file, got error: %v", err)
} }
if result != nil { if result != nil {
t.Errorf("Expected nil verification result when .prov file is missing, got: %+v", result) t.Errorf("Expected nil verification result when .prov file is missing, got: %+v", result)
} }
// Should contain warning message // Plugin should NOT be installed
expectedWarning := "WARNING: No provenance file found for plugin" if _, err := os.Stat(installer.Path()); !os.IsNotExist(err) {
if !strings.Contains(output, expectedWarning) { t.Error("Plugin should not be installed when verification fails due to missing .prov")
t.Errorf("Expected warning message '%s' in output, got: %s", expectedWarning, output) }
}
func TestInstallWithOptions_NoVerifyMissingProvenance(t *testing.T) {
ensure.HelmHome(t)
// Create a temporary plugin tarball without .prov file
pluginDir := createTestPluginDir(t)
pluginTgz := createTarballFromPluginDir(t, pluginDir)
defer os.Remove(pluginTgz)
// Create local installer
installer, err := NewLocalInstaller(pluginTgz)
if err != nil {
t.Fatalf("Failed to create installer: %v", err)
}
defer os.RemoveAll(installer.Path())
// Install with verification explicitly disabled should succeed without .prov
result, err := InstallWithOptions(installer, Options{Verify: false})
if err != nil {
t.Fatalf("Expected installation to succeed with --verify=false, got error: %v", err)
}
if result != nil {
t.Errorf("Expected nil verification result when verification is disabled, got: %+v", result)
} }
// Plugin should be installed // Plugin should be installed

@ -19,6 +19,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
@ -44,7 +45,6 @@ func peekAPIVersion(r io.Reader) (string, error) {
} }
func loadMetadataLegacy(metadataData []byte) (*Metadata, error) { func loadMetadataLegacy(metadataData []byte) (*Metadata, error) {
var ml MetadataLegacy var ml MetadataLegacy
d := yaml.NewDecoder(bytes.NewReader(metadataData)) d := yaml.NewDecoder(bytes.NewReader(metadataData))
// NOTE: No strict unmarshalling for legacy plugins - maintain backwards compatibility // NOTE: No strict unmarshalling for legacy plugins - maintain backwards compatibility
@ -64,7 +64,6 @@ func loadMetadataLegacy(metadataData []byte) (*Metadata, error) {
} }
func loadMetadataV1(metadataData []byte) (*Metadata, error) { func loadMetadataV1(metadataData []byte) (*Metadata, error) {
var mv1 MetadataV1 var mv1 MetadataV1
d := yaml.NewDecoder(bytes.NewReader(metadataData)) d := yaml.NewDecoder(bytes.NewReader(metadataData))
d.KnownFields(true) d.KnownFields(true)
@ -108,7 +107,6 @@ type prototypePluginManager struct {
} }
func newPrototypePluginManager() (*prototypePluginManager, error) { func newPrototypePluginManager() (*prototypePluginManager, error) {
cc, err := wazero.NewCompilationCacheWithDir(helmpath.CachePath("wazero-build")) cc, err := wazero.NewCompilationCacheWithDir(helmpath.CachePath("wazero-build"))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create wazero compilation cache: %w", err) return nil, fmt.Errorf("failed to create wazero compilation cache: %w", err)
@ -158,18 +156,27 @@ func LoadDir(dirname string) (Plugin, error) {
return pm.CreatePlugin(dirname, m) return pm.CreatePlugin(dirname, m)
} }
// LoadAll loads all plugins found beneath the base directory. func LogIgnorePluginLoadErrorFilterFunc(pluginYAML string, err error) error {
slog.Warn("failed to load plugin (ignoring)", slog.String("plugin_yaml", pluginYAML), slog.Any("error", err))
return nil
}
// errorFilterFunc is a function that can filter errors during plugin loading
type ErrorFilterFunc func(string, error) error
// LoadAllDir load all plugins found beneath the base directory, using the provided error filter to determine whether to fail on individual plugin load errors.
// //
// This scans only one directory level. // This scans only one directory level.
func LoadAll(basedir string) ([]Plugin, error) { func LoadAllDir(basedir string, errorFilter ErrorFilterFunc) ([]Plugin, error) {
var plugins []Plugin // We want <basedir>/*/plugin.yaml
// We want basedir/*/plugin.yaml
scanpath := filepath.Join(basedir, "*", PluginFileName) scanpath := filepath.Join(basedir, "*", PluginFileName)
matches, err := filepath.Glob(scanpath) matches, err := filepath.Glob(scanpath)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to search for plugins in %q: %w", scanpath, err) return nil, fmt.Errorf("failed to search for plugins in %q: %w", scanpath, err)
} }
plugins := make([]Plugin, 0, len(matches))
// empty dir should load // empty dir should load
if len(matches) == 0 { if len(matches) == 0 {
return plugins, nil return plugins, nil
@ -179,9 +186,12 @@ func LoadAll(basedir string) ([]Plugin, error) {
dir := filepath.Dir(yamlFile) dir := filepath.Dir(yamlFile)
p, err := LoadDir(dir) p, err := LoadDir(dir)
if err != nil { if err != nil {
return plugins, err if errNew := errorFilter(yamlFile, err); errNew != nil {
return plugins, errNew
}
} else {
plugins = append(plugins, p)
} }
plugins = append(plugins, p)
} }
return plugins, detectDuplicates(plugins) return plugins, detectDuplicates(plugins)
} }
@ -193,8 +203,12 @@ type findFunc func(pluginsDir string) ([]Plugin, error)
type filterFunc func(Plugin) bool type filterFunc func(Plugin) bool
// FindPlugins returns a list of plugins that match the descriptor // FindPlugins returns a list of plugins that match the descriptor
// Errors loading a plugin are ignored with a warning
func FindPlugins(pluginsDirs []string, descriptor Descriptor) ([]Plugin, error) { func FindPlugins(pluginsDirs []string, descriptor Descriptor) ([]Plugin, error) {
return findPlugins(pluginsDirs, LoadAll, makeDescriptorFilter(descriptor)) loadAllIgnoreErrors := func(pluginsDir string) ([]Plugin, error) {
return LoadAllDir(pluginsDir, LogIgnorePluginLoadErrorFilterFunc)
}
return findPlugins(pluginsDirs, loadAllIgnoreErrors, makeDescriptorFilter(descriptor))
} }
// findPlugins is the internal implementation that uses the find and filter functions // findPlugins is the internal implementation that uses the find and filter functions
@ -212,7 +226,6 @@ func findPlugins(pluginsDirs []string, findFn findFunc, filterFn filterFunc) ([]
found = append(found, p) found = append(found, p)
} }
} }
} }
return found, nil return found, nil
@ -225,7 +238,6 @@ func makeDescriptorFilter(descriptor Descriptor) filterFunc {
// If name is specified, it must match // If name is specified, it must match
if descriptor.Name != "" && p.Metadata().Name != descriptor.Name { if descriptor.Name != "" && p.Metadata().Name != descriptor.Name {
return false return false
} }
// If type is specified, it must match // If type is specified, it must match
if descriptor.Type != "" && p.Metadata().Type != descriptor.Type { if descriptor.Type != "" && p.Metadata().Type != descriptor.Type {
@ -237,7 +249,11 @@ func makeDescriptorFilter(descriptor Descriptor) filterFunc {
// FindPlugin returns a single plugin that matches the descriptor // FindPlugin returns a single plugin that matches the descriptor
func FindPlugin(dirs []string, descriptor Descriptor) (Plugin, error) { func FindPlugin(dirs []string, descriptor Descriptor) (Plugin, error) {
plugins, err := FindPlugins(dirs, descriptor) loadAllIgnoreErrors := func(pluginsDir string) ([]Plugin, error) {
return LoadAllDir(pluginsDir, LogIgnorePluginLoadErrorFilterFunc)
}
plugins, err := findPlugins(dirs, loadAllIgnoreErrors, makeDescriptorFilter(descriptor))
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -62,7 +62,6 @@ name: "test-plugin"
} }
func TestLoadDir(t *testing.T) { func TestLoadDir(t *testing.T) {
makeMetadata := func(apiVersion string) Metadata { makeMetadata := func(apiVersion string) Metadata {
usage := "hello [params]..." usage := "hello [params]..."
if apiVersion == "legacy" { if apiVersion == "legacy" {
@ -204,16 +203,16 @@ func TestDetectDuplicates(t *testing.T) {
} }
} }
func TestLoadAll(t *testing.T) { func TestLoadAllDir_Empty(t *testing.T) {
// Verify that empty dir loads: emptyDir := t.TempDir()
{ plugs, err := LoadAllDir(emptyDir, func(_ string, err error) error { return err })
plugs, err := LoadAll("testdata") require.NoError(t, err)
require.NoError(t, err) assert.Len(t, plugs, 0)
assert.Len(t, plugs, 0) }
}
func TestLoadAllPluginsDir(t *testing.T) {
basedir := "testdata/plugdir/good" basedir := "testdata/plugdir/good"
plugs, err := LoadAll(basedir) plugs, err := LoadAllDir(basedir, func(_ string, err error) error { return err })
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, plugs, "expected plugins to be loaded from %s", basedir) require.NotEmpty(t, plugs, "expected plugins to be loaded from %s", basedir)
@ -232,7 +231,7 @@ func TestLoadAll(t *testing.T) {
assert.Contains(t, plugsMap, "postrenderer-v1") assert.Contains(t, plugsMap, "postrenderer-v1")
} }
func TestFindPlugins(t *testing.T) { func TestLoadAllPluginsDir_Zero(t *testing.T) {
cases := []struct { cases := []struct {
name string name string
plugdirs string plugdirs string
@ -240,28 +239,20 @@ func TestFindPlugins(t *testing.T) {
}{ }{
{ {
name: "plugdirs is empty", name: "plugdirs is empty",
plugdirs: "", plugdirs: t.TempDir(),
expected: 0,
}, },
{ {
name: "plugdirs isn't dir", name: "plugdirs isn't dir",
plugdirs: "./plugin_test.go", plugdirs: "./plugin_test.go",
expected: 0,
}, },
{ {
name: "plugdirs doesn't have plugin", name: "plugdirs doesn't have plugin",
plugdirs: ".", plugdirs: ".",
expected: 0,
},
{
name: "normal",
plugdirs: "./testdata/plugdir/good",
expected: 7,
}, },
} }
for _, c := range cases { for _, c := range cases {
t.Run(t.Name(), func(t *testing.T) { t.Run(t.Name(), func(t *testing.T) {
plugin, err := LoadAll(c.plugdirs) plugin, err := LoadAllDir(c.plugdirs, func(_ string, err error) error { return err })
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, plugin, c.expected, "expected %d plugins, got %d", c.expected, len(plugin)) assert.Len(t, plugin, c.expected, "expected %d plugins, got %d", c.expected, len(plugin))
}) })
@ -337,6 +328,7 @@ runtime: subprocess
"correct name field": { "correct name field": {
yaml: `apiVersion: v1 yaml: `apiVersion: v1
name: my-plugin name: my-plugin
version: 1.0.0
type: cli/v1 type: cli/v1
runtime: subprocess runtime: subprocess
`, `,

@ -19,9 +19,17 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/Masterminds/semver/v3"
"helm.sh/helm/v4/internal/plugin/schema" "helm.sh/helm/v4/internal/plugin/schema"
) )
// isValidSemver checks if the given string is a valid semantic version
func isValidSemver(v string) bool {
_, err := semver.StrictNewVersion(v)
return err == nil
}
// Metadata of a plugin, converted from the "on-disk" legacy or v1 plugin.yaml // Metadata of a plugin, converted from the "on-disk" legacy or v1 plugin.yaml
// Specifically, Config and RuntimeConfig are converted to their respective types based on the plugin type and runtime // Specifically, Config and RuntimeConfig are converted to their respective types based on the plugin type and runtime
type Metadata struct { type Metadata struct {
@ -57,6 +65,11 @@ func (m Metadata) Validate() error {
errs = append(errs, fmt.Errorf("invalid plugin name %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name)) errs = append(errs, fmt.Errorf("invalid plugin name %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name))
} }
// Require version to be valid semver if specified
if m.Version != "" && !isValidSemver(m.Version) {
errs = append(errs, fmt.Errorf("invalid plugin version %q: must be valid semver", m.Version))
}
if m.APIVersion == "" { if m.APIVersion == "" {
errs = append(errs, errors.New("empty APIVersion")) errs = append(errs, errors.New("empty APIVersion"))
} }

@ -72,6 +72,11 @@ func (m *MetadataLegacy) Validate() error {
if !validPluginName.MatchString(m.Name) { if !validPluginName.MatchString(m.Name) {
return fmt.Errorf("invalid plugin name %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name) return fmt.Errorf("invalid plugin name %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name)
} }
if m.Version != "" && !isValidSemver(m.Version) {
return fmt.Errorf("invalid plugin version %q: must be valid semver", m.Version)
}
m.Usage = sanitizeString(m.Usage) m.Usage = sanitizeString(m.Usage)
if len(m.PlatformCommand) > 0 && len(m.Command) > 0 { if len(m.PlatformCommand) > 0 && len(m.Command) > 0 {

@ -26,6 +26,10 @@ func TestMetadataLegacyValidate(t *testing.T) {
"valid metadata": { "valid metadata": {
Name: "myplugin", Name: "myplugin",
}, },
"valid metadata (empty version)": {
Name: "myplugin",
Version: "",
},
"valid with command": { "valid with command": {
Name: "myplugin", Name: "myplugin",
Command: "echo hello", Command: "echo hello",
@ -59,6 +63,13 @@ func TestMetadataLegacyValidate(t *testing.T) {
}, },
}, },
}, },
"valid with version": {
Name: "myplugin",
Version: "1.0.0",
},
"valid with empty version": {
Name: "myplugin",
},
} }
for testName, metadata := range testsValid { for testName, metadata := range testsValid {
@ -116,6 +127,14 @@ func TestMetadataLegacyValidate(t *testing.T) {
}, },
}, },
}, },
"path traversal version": {
Name: "myplugin",
Version: "../../../../tmp/evil",
},
"invalid version": {
Name: "myplugin",
Version: "not-a-version",
},
} }
for testName, metadata := range testsInvalid { for testName, metadata := range testsInvalid {

@ -18,10 +18,11 @@ package plugin
import ( import (
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
func TestValidatePluginData(t *testing.T) { func TestValidatePluginData(t *testing.T) {
// A mock plugin with no commands // A mock plugin with no commands
mockNoCommand := mockSubprocessCLIPlugin(t, "foo") mockNoCommand := mockSubprocessCLIPlugin(t, "foo")
mockNoCommand.metadata.RuntimeConfig = &RuntimeConfigSubprocess{ mockNoCommand.metadata.RuntimeConfig = &RuntimeConfigSubprocess{
@ -72,6 +73,43 @@ func TestValidatePluginData(t *testing.T) {
} }
} }
func TestMetadataValidateVersion(t *testing.T) {
testValid := map[string]struct {
version string
}{
"valid semver": {version: "1.0.0"},
"valid semver with prerelease": {version: "1.2.3-alpha.1+build.123"},
"empty version": {version: ""},
}
testInvalid := map[string]struct {
version string
}{
"valid semver with v prefix": {version: "v1.0.0"},
"path traversal": {version: "../../../../tmp/evil"},
"path traversal in version": {version: "1.0.0/../../etc"},
"not a version": {version: "not-a-version"},
}
for name, tc := range testValid {
t.Run(name, func(t *testing.T) {
m := mockSubprocessCLIPlugin(t, "testplugin")
m.metadata.Version = tc.version
err := m.Metadata().Validate()
assert.NoError(t, err)
})
}
for name, tc := range testInvalid {
t.Run(name, func(t *testing.T) {
m := mockSubprocessCLIPlugin(t, "testplugin")
m.metadata.Version = tc.version
err := m.Metadata().Validate()
assert.ErrorContains(t, err, "invalid plugin version")
})
}
}
func TestMetadataValidateMultipleErrors(t *testing.T) { func TestMetadataValidateMultipleErrors(t *testing.T) {
// Create metadata with multiple validation issues // Create metadata with multiple validation issues
metadata := Metadata{ metadata := Metadata{

@ -52,6 +52,13 @@ func (m *MetadataV1) Validate() error {
return errors.New("invalid plugin `name`") return errors.New("invalid plugin `name`")
} }
if m.Version == "" {
return errors.New("plugin `version` is required")
}
if !isValidSemver(m.Version) {
return fmt.Errorf("invalid plugin `version` %q: must be valid semver", m.Version)
}
if m.APIVersion != "v1" { if m.APIVersion != "v1" {
return fmt.Errorf("invalid `apiVersion`: %q", m.APIVersion) return fmt.Errorf("invalid `apiVersion`: %q", m.APIVersion)
} }

@ -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 plugin
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestMetadataV1ValidateVersion(t *testing.T) {
base := func() MetadataV1 {
return MetadataV1{
APIVersion: "v1",
Name: "myplugin",
Type: "cli/v1",
Runtime: "subprocess",
Version: "1.0.0",
}
}
testsValid := map[string]string{
"simple version": "1.0.0",
"with prerelease": "1.2.3-alpha.1",
"with build meta": "1.2.3+build.123",
"full prerelease": "1.2.3-alpha.1+build.123",
}
for name, version := range testsValid {
t.Run("valid/"+name, func(t *testing.T) {
m := base()
m.Version = version
assert.NoError(t, m.Validate())
})
}
testsInvalid := map[string]struct {
version string
errMsg string
}{
"empty version": {
version: "",
errMsg: "plugin `version` is required",
},
"v prefix": {
version: "v1.0.0",
errMsg: "invalid plugin `version` \"v1.0.0\": must be valid semver",
},
"path traversal": {
version: "../../../../tmp/evil",
errMsg: "invalid plugin `version`",
},
"path traversal etc": {
version: "../../../etc/passwd",
errMsg: "invalid plugin `version`",
},
"not a version": {
version: "not-a-version",
errMsg: "invalid plugin `version`",
},
}
for name, tc := range testsInvalid {
t.Run("invalid/"+name, func(t *testing.T) {
m := base()
m.Version = tc.version
err := m.Validate()
assert.Error(t, err)
assert.Contains(t, err.Error(), tc.errMsg)
})
}
}

@ -77,5 +77,5 @@ type Output struct {
// validPluginName is a regular expression that validates plugin names. // validPluginName is a regular expression that validates plugin names.
// //
// Plugin names can only contain the ASCII characters a-z, A-Z, 0-9, ​_​ and ​-. // Plugin names can only contain the ASCII characters a-z, A-Z, 0-9, _ and -.
var validPluginName = regexp.MustCompile("^[A-Za-z0-9_-]+$") var validPluginName = regexp.MustCompile("^[A-Za-z0-9_-]+$")

@ -82,7 +82,7 @@ func mockSubprocessCLIPlugin(t *testing.T, pluginName string) *SubprocessPluginR
return &SubprocessPluginRuntime{ return &SubprocessPluginRuntime{
metadata: Metadata{ metadata: Metadata{
Name: pluginName, Name: pluginName,
Version: "v0.1.2", Version: "0.1.2",
Type: "cli/v1", Type: "cli/v1",
APIVersion: "v1", APIVersion: "v1",
Runtime: "subprocess", Runtime: "subprocess",

@ -28,7 +28,6 @@ func TestMakeOutputMessage(t *testing.T) {
ptm := pluginTypesIndex["getter/v1"] ptm := pluginTypesIndex["getter/v1"]
outputType := reflect.Zero(ptm.outputType).Interface() outputType := reflect.Zero(ptm.outputType).Interface()
assert.IsType(t, schema.OutputMessageGetterV1{}, outputType) assert.IsType(t, schema.OutputMessageGetterV1{}, outputType)
} }
func TestMakeConfig(t *testing.T) { func TestMakeConfig(t *testing.T) {

@ -99,7 +99,6 @@ type RuntimeExtismV1 struct {
var _ Runtime = (*RuntimeExtismV1)(nil) var _ Runtime = (*RuntimeExtismV1)(nil)
func (r *RuntimeExtismV1) CreatePlugin(pluginDir string, metadata *Metadata) (Plugin, error) { func (r *RuntimeExtismV1) CreatePlugin(pluginDir string, metadata *Metadata) (Plugin, error) {
rc, ok := metadata.RuntimeConfig.(*RuntimeConfigExtismV1) rc, ok := metadata.RuntimeConfig.(*RuntimeConfigExtismV1)
if !ok { if !ok {
return nil, fmt.Errorf("invalid extism/v1 plugin runtime config type: %T", metadata.RuntimeConfig) return nil, fmt.Errorf("invalid extism/v1 plugin runtime config type: %T", metadata.RuntimeConfig)
@ -139,7 +138,6 @@ func (p *ExtismV1PluginRuntime) Dir() string {
} }
func (p *ExtismV1PluginRuntime) Invoke(ctx context.Context, input *Input) (*Output, error) { func (p *ExtismV1PluginRuntime) Invoke(ctx context.Context, input *Input) (*Output, error) {
var tmpDir string var tmpDir string
if p.rc.FileSystem.CreateTempDir { if p.rc.FileSystem.CreateTempDir {
tmpDirInner, err := os.MkdirTemp(os.TempDir(), "helm-plugin-*") tmpDirInner, err := os.MkdirTemp(os.TempDir(), "helm-plugin-*")

@ -79,7 +79,7 @@ func TestRuntimeExtismV1InvokePlugin(t *testing.T) {
Name: "Phippy", Name: "Phippy",
}, },
}) })
require.Nil(t, err) require.NoError(t, err)
msg := output.Message.(schema.OutputMessageTestV1) msg := output.Message.(schema.OutputMessageTestV1)
assert.Equal(t, "Hello, Phippy! (6)", msg.Greeting) assert.Equal(t, "Hello, Phippy! (6)", msg.Greeting)

@ -42,7 +42,7 @@ func mockSubprocessCLIPluginErrorExit(t *testing.T, pluginName string, exitCode
md := Metadata{ md := Metadata{
Name: pluginName, Name: pluginName,
Version: "v0.1.2", Version: "0.1.2",
Type: "cli/v1", Type: "cli/v1",
APIVersion: "v1", APIVersion: "v1",
Runtime: "subprocess", Runtime: "subprocess",

@ -48,7 +48,6 @@ func TestPrepareCommand(t *testing.T) {
} }
func TestPrepareCommandExtraArgs(t *testing.T) { func TestPrepareCommandExtraArgs(t *testing.T) {
cmdMain := "sh" cmdMain := "sh"
cmdArgs := []string{"-c", "echo \"test\""} cmdArgs := []string{"-c", "echo \"test\""}
platformCommand := []PlatformCommand{ platformCommand := []PlatformCommand{

@ -21,6 +21,7 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"unicode"
) )
// SimpleHead defines what the structure of the head of a manifest file // SimpleHead defines what the structure of the head of a manifest file
@ -35,7 +36,16 @@ type SimpleHead struct {
var sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*") var sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*")
// SplitManifests takes a string of manifest and returns a map contains individual manifests // SplitManifests takes a manifest string and returns a map containing individual manifests.
//
// **Note for Chart API v3**: This function (due to the regex above) has allowed _WRONG_
// Go templates to be defined inside charts across the years. The generated text from Go
// templates may contain `---apiVersion: v1`, and this function magically splits this back
// to `---\napiVersion: v1`. This has caused issues recently after Helm 4 introduced
// kio.ParseAll to inject annotations when post-renderers are used. In Chart API v3,
// we should kill this regex with fire (or change it) and expose charts doing the wrong
// thing Go template-wise. Helm should say a big _NO_ to charts doing the wrong thing,
// with or without post-renderers.
func SplitManifests(bigFile string) map[string]string { func SplitManifests(bigFile string) map[string]string {
// Basically, we're quickly splitting a stream of YAML documents into an // Basically, we're quickly splitting a stream of YAML documents into an
// array of YAML docs. The file name is just a place holder, but should be // array of YAML docs. The file name is just a place holder, but should be
@ -44,15 +54,15 @@ func SplitManifests(bigFile string) map[string]string {
tpl := "manifest-%d" tpl := "manifest-%d"
res := map[string]string{} res := map[string]string{}
// Making sure that any extra whitespace in YAML stream doesn't interfere in splitting documents correctly. // Making sure that any extra whitespace in YAML stream doesn't interfere in splitting documents correctly.
bigFileTmp := strings.TrimSpace(bigFile) bigFileTmp := strings.TrimLeftFunc(bigFile, unicode.IsSpace)
docs := sep.Split(bigFileTmp, -1) docs := sep.Split(bigFileTmp, -1)
var count int var count int
for _, d := range docs { for _, d := range docs {
if d == "" { if strings.TrimSpace(d) == "" {
continue continue
} }
d = strings.TrimSpace(d) d = strings.TrimLeftFunc(d, unicode.IsSpace)
res[fmt.Sprintf(tpl, count)] = d res[fmt.Sprintf(tpl, count)] = d
count = count + 1 count = count + 1
} }

@ -26,7 +26,6 @@ import (
) )
func TestSortManifests(t *testing.T) { func TestSortManifests(t *testing.T) {
data := []struct { data := []struct {
name []string name []string
path string path string
@ -183,7 +182,6 @@ metadata:
if !reflect.DeepEqual(expectedHooks, out.Events) { if !reflect.DeepEqual(expectedHooks, out.Events) {
t.Errorf("expected events: %v but got: %v", expectedHooks, out.Events) t.Errorf("expected events: %v but got: %v", expectedHooks, out.Events)
} }
} }
} }
if !found { if !found {

@ -21,7 +21,15 @@ import (
"testing" "testing"
) )
const mockManifestFile = ` func TestSplitManifests(t *testing.T) {
tests := []struct {
name string
input string
expected map[string]string
}{
{
name: "single doc with leading separator and whitespace",
input: `
--- ---
apiVersion: v1 apiVersion: v1
@ -35,9 +43,9 @@ spec:
- name: nemo-test - name: nemo-test
image: fake-image image: fake-image
cmd: fake-command cmd: fake-command
` `,
expected: map[string]string{
const expectedManifest = `apiVersion: v1 "manifest-0": `apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
name: finding-nemo, name: finding-nemo,
@ -47,15 +55,463 @@ spec:
containers: containers:
- name: nemo-test - name: nemo-test
image: fake-image image: fake-image
cmd: fake-command` cmd: fake-command
`,
},
},
{
name: "empty input",
input: "",
expected: map[string]string{},
},
{
name: "whitespace only",
input: " \n\n \n",
expected: map[string]string{},
},
{
name: "whitespace-only doc after separator is skipped",
input: "---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1\n---\n \n",
expected: map[string]string{
"manifest-0": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1",
},
},
{
name: "single doc no separator",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
`,
},
},
{
name: "two docs with proper separator",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm1`,
"manifest-1": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
`,
},
},
func TestSplitManifest(t *testing.T) { // Block scalar chomping indicator tests using | (clip), |- (strip), and |+ (keep)
manifests := SplitManifests(mockManifestFile) // inputs with 0, 1, and 2 trailing newlines after the block content.
if len(manifests) != 1 { // Note: the emitter may normalize the output chomping indicator when the
t.Errorf("Expected 1 manifest, got %v", len(manifests)) // trailing newline count makes another indicator equivalent for the result.
// | (clip) input — clips trailing newlines to exactly one, though with
// 0 trailing newlines the emitted output may normalize to |-.
{
name: "block scalar clip (|) with 0 trailing newlines",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello`,
},
},
{
name: "block scalar clip (|) with 1 trailing newline",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello
`,
},
},
{
name: "block scalar clip (|) with 2 trailing newlines",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello
`,
},
},
// |- (strip)
{
name: "block scalar strip (|-) with 0 trailing newlines",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello`,
},
},
{
name: "block scalar strip (|-) with 1 trailing newline",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello
`,
},
},
{
name: "block scalar strip (|-) with 2 trailing newlines",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello
`,
},
},
// |+ (keep)
{
name: "block scalar keep (|+) with 0 trailing newlines",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello`,
},
},
{
name: "block scalar keep (|+) with 1 trailing newline",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello
`,
},
},
{
name: "block scalar keep (|+) with 2 trailing newlines",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello
`,
},
},
// Multi-doc with block scalars: the regex consumes \s*\n before ---,
// so trailing newlines from non-last docs are stripped.
{
name: "multi-doc block scalar clip (|) before separator",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello`,
"manifest-1": `apiVersion: v1
kind: ConfigMap
metadata:
name: test2
`,
},
},
{
name: "multi-doc block scalar keep (|+) with 2 trailing newlines before separator",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello`,
"manifest-1": `apiVersion: v1
kind: ConfigMap
metadata:
name: test2
`,
},
},
// **Note for Chart API v3**: The following tests exercise the lenient
// regex that splits `---apiVersion` back into separate documents.
// In Chart API v3, these inputs should return an _ERROR_ instead.
// See the comment on the SplitManifests function for more details.
{
name: "leading glued separator (---apiVersion)",
input: `
---apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
`,
},
},
{
name: "mid-content glued separator (---apiVersion)",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
---apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm1`,
"manifest-1": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
`,
},
},
{
name: "multiple glued separators",
input: `
---apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
---apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
---apiVersion: v1
kind: ConfigMap
metadata:
name: cm3
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm1`,
"manifest-1": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm2`,
"manifest-2": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm3
`,
},
},
{
name: "mixed glued and proper separators",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
---apiVersion: v1
kind: ConfigMap
metadata:
name: cm3
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm1`,
"manifest-1": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm2`,
"manifest-2": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm3
`,
},
},
} }
expected := map[string]string{"manifest-0": expectedManifest}
if !reflect.DeepEqual(manifests, expected) { for _, tt := range tests {
t.Errorf("Expected %v, got %v", expected, manifests) t.Run(tt.name, func(t *testing.T) {
result := SplitManifests(tt.input)
if !reflect.DeepEqual(result, tt.expected) {
t.Errorf("SplitManifests() =\n%v\nwant:\n%v", result, tt.expected)
}
})
} }
} }

@ -54,7 +54,6 @@ func New(chartpath, cachepath string, registryClient *registry.Client) *Resolver
// Resolve resolves dependencies and returns a lock file with the resolution. // Resolve resolves dependencies and returns a lock file with the resolution.
func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string) (*chart.Lock, error) { func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string) (*chart.Lock, error) {
// Now we clone the dependencies, locking as we go. // Now we clone the dependencies, locking as we go.
locked := make([]*chart.Dependency, len(reqs)) locked := make([]*chart.Dependency, len(reqs))
missing := []string{} missing := []string{}
@ -146,7 +145,6 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
Version: version, Version: version,
}, },
}} }}
} else { } else {
// Retrieve list of tags for repository // Retrieve list of tags for repository
ref := fmt.Sprintf("%s/%s", strings.TrimPrefix(d.Repository, registry.OCIScheme+"://"), d.Name) ref := fmt.Sprintf("%s/%s", strings.TrimPrefix(d.Repository, registry.OCIScheme+"://"), d.Name)

@ -238,7 +238,6 @@ func TestCopyDirFail_SrcIsNotDir(t *testing.T) {
if !errors.Is(err, errSrcNotDir) { if !errors.Is(err, errSrcNotDir) {
t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errSrcNotDir, srcdir, dstdir, err) t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errSrcNotDir, srcdir, dstdir, err)
} }
} }
func TestCopyDirFail_DstExists(t *testing.T) { func TestCopyDirFail_DstExists(t *testing.T) {
@ -491,7 +490,6 @@ func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() {
} }
func TestIsDir(t *testing.T) { func TestIsDir(t *testing.T) {
var currentUID = os.Getuid() var currentUID = os.Getuid()
if currentUID == 0 { if currentUID == 0 {
@ -544,7 +542,6 @@ func TestIsDir(t *testing.T) {
} }
func TestIsSymlink(t *testing.T) { func TestIsSymlink(t *testing.T) {
var currentUID = os.Getuid() var currentUID = os.Getuid()
if currentUID == 0 { if currentUID == 0 {

@ -34,7 +34,7 @@ var (
// //
// Increment major number for new feature additions and behavioral changes. // Increment major number for new feature additions and behavioral changes.
// Increment minor number for bug fixes and performance enhancements. // Increment minor number for bug fixes and performance enhancements.
version = "v4.1" version = "v4.2"
// metadata is extra build time data // metadata is extra build time data
metadata = "" metadata = ""
@ -77,7 +77,6 @@ func GetUserAgent() string {
// Get returns build info // Get returns build info
func Get() BuildInfo { func Get() BuildInfo {
makeKubeClientVersionString := func() string { makeKubeClientVersionString := func() string {
// Test builds don't include debug info / module info // Test builds don't include debug info / module info
// (And even if they did, we probably want a stable version during tests anyway) // (And even if they did, we probably want a stable version during tests anyway)

@ -27,6 +27,7 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"slices" "slices"
"sort"
"strings" "strings"
"sync" "sync"
"text/template" "text/template"
@ -87,6 +88,33 @@ const (
DryRunServer DryRunStrategy = "server" DryRunServer DryRunStrategy = "server"
) )
// PostRenderStrategy determines how hooks and regular templates are passed
// to the configured post-renderer.
type PostRenderStrategy string
const (
// PostRenderStrategyCombined sends hooks and regular templates together
// as a single stream to the post-renderer. This is the default in Helm 4.
PostRenderStrategyCombined PostRenderStrategy = "combined"
// PostRenderStrategySeparate sends hooks and regular templates to the
// post-renderer in independent invocations. This avoids duplicate-resource
// errors from post-renderers that de-duplicate by resource identity
// (for example Kustomize) when the same resource appears in both a hook
// and a regular template. Passing hooks to post-renderers was introduced
// in Helm 4; Helm 3 never did so, which is why the issue only surfaces
// with the Helm 4 combined default.
PostRenderStrategySeparate PostRenderStrategy = "separate"
// PostRenderStrategyNoHooks sends only regular templates to the
// post-renderer and leaves hooks untouched. This matches the Helm 3
// behavior and is useful for post-renderers that declare transforms
// targeting template-only resources (for example Kustomize patches
// against a Deployment that exists in templates but not in hooks),
// which would otherwise fail against the hook stream.
PostRenderStrategyNoHooks PostRenderStrategy = "nohooks"
)
// Configuration injects the dependencies that all actions share. // Configuration injects the dependencies that all actions share.
type Configuration struct { type Configuration struct {
// RESTClientGetter is an interface that loads Kubernetes clients. // RESTClientGetter is an interface that loads Kubernetes clients.
@ -144,39 +172,6 @@ const (
filenameAnnotation = "postrenderer.helm.sh/postrender-filename" filenameAnnotation = "postrenderer.helm.sh/postrender-filename"
) )
// fixDocSeparators ensures YAML document separators ("---") are always
// followed by a newline in rendered template content. Go template whitespace
// trimming ({{-) can remove the newline after "---", producing e.g.
// "---apiVersion: v1" which is not a valid YAML document separator.
// This function inserts a newline after any "---" at the start of a line
// that is immediately followed by non-whitespace content.
func fixDocSeparators(content string) string {
var b strings.Builder
remaining := content
for {
// Find "---" at the start of a line (or start of content).
idx := strings.Index(remaining, "---")
if idx == -1 {
b.WriteString(remaining)
break
}
// "---" must be at the start of a line: either idx==0 or preceded by '\n'.
if idx > 0 && remaining[idx-1] != '\n' {
b.WriteString(remaining[:idx+3])
remaining = remaining[idx+3:]
continue
}
b.WriteString(remaining[:idx+3])
remaining = remaining[idx+3:]
// If "---" is followed by non-whitespace (e.g. "---apiVersion"),
// insert a newline to make it a proper document separator.
if len(remaining) > 0 && remaining[0] != '\n' && remaining[0] != '\r' && remaining[0] != ' ' && remaining[0] != '\t' {
b.WriteByte('\n')
}
}
return b.String()
}
// annotateAndMerge combines multiple YAML files into a single stream of documents, // annotateAndMerge combines multiple YAML files into a single stream of documents,
// adding filename annotations to each document for later reconstruction. // adding filename annotations to each document for later reconstruction.
func annotateAndMerge(files map[string]string) (string, error) { func annotateAndMerge(files map[string]string) (string, error) {
@ -192,22 +187,32 @@ func annotateAndMerge(files map[string]string) (string, error) {
continue continue
} }
// Fix document separators where Go template whitespace trimming // For consistency with the non-post-renderers code path, we need
// ({{-) has removed the newline after "---", producing e.g. // to use releaseutil.SplitManifests here to split the file into
// "---apiVersion: v1" which is not a valid YAML document // individual documents before feeding them to kio.ParseAll. In
// separator. Insert the missing newline so kio.ParseAll can // Chart API before v3 this function had leniency for badly-written
// parse the content correctly. // Go templates, so this must be preserved for older charts.
content = fixDocSeparators(content) splitDocs := releaseutil.SplitManifests(content)
keys := make([]string, 0, len(splitDocs))
manifests, err := kio.ParseAll(content) for k := range splitDocs {
if err != nil { keys = append(keys, k)
return "", fmt.Errorf("parsing %s: %w", fname, err)
} }
for _, manifest := range manifests { sort.Sort(releaseutil.BySplitManifestsOrder(keys))
if err := manifest.PipeE(kyaml.SetAnnotation(filenameAnnotation, fname)); err != nil { for _, key := range keys {
return "", fmt.Errorf("annotating %s: %w", fname, err) doc := splitDocs[key]
if strings.TrimSpace(doc) == "" {
continue
}
manifests, err := kio.ParseAll(doc)
if err != nil {
return "", fmt.Errorf("parsing %s: %w", fname, err)
}
for _, manifest := range manifests {
if err := manifest.PipeE(kyaml.SetAnnotation(filenameAnnotation, fname)); err != nil {
return "", fmt.Errorf("annotating %s: %w", fname, err)
}
combinedManifests = append(combinedManifests, manifest)
} }
combinedManifests = append(combinedManifests, manifest)
} }
} }
@ -220,7 +225,14 @@ func annotateAndMerge(files map[string]string) (string, error) {
// splitAndDeannotate reconstructs individual files from a merged YAML stream, // splitAndDeannotate reconstructs individual files from a merged YAML stream,
// removing filename annotations and grouping documents by their original filenames. // removing filename annotations and grouping documents by their original filenames.
func splitAndDeannotate(postrendered string) (map[string]string, error) { // Documents without a filename annotation are assigned a synthesized name of the
// form "generated-by-postrender-<fallbackPrefix>-<i>.yaml" (or
// "generated-by-postrender-<i>.yaml" when fallbackPrefix is empty). The prefix
// disambiguates fallback filenames across multiple post-render invocations (for
// example when PostRenderStrategySeparate runs the post-renderer once per
// group), so that merging results from different invocations does not collide
// on the same synthetic key.
func splitAndDeannotate(postrendered, fallbackPrefix string) (map[string]string, error) {
manifests, err := kio.ParseAll(postrendered) manifests, err := kio.ParseAll(postrendered)
if err != nil { if err != nil {
return nil, fmt.Errorf("error parsing YAML: %w", err) return nil, fmt.Errorf("error parsing YAML: %w", err)
@ -234,7 +246,11 @@ func splitAndDeannotate(postrendered string) (map[string]string, error) {
} }
fname := meta.Annotations[filenameAnnotation] fname := meta.Annotations[filenameAnnotation]
if fname == "" { if fname == "" {
fname = fmt.Sprintf("generated-by-postrender-%d.yaml", i) if fallbackPrefix == "" {
fname = fmt.Sprintf("generated-by-postrender-%d.yaml", i)
} else {
fname = fmt.Sprintf("generated-by-postrender-%s-%d.yaml", fallbackPrefix, i)
}
} }
if err := manifest.PipeE(kyaml.ClearAnnotation(filenameAnnotation)); err != nil { if err := manifest.PipeE(kyaml.ClearAnnotation(filenameAnnotation)); err != nil {
return nil, fmt.Errorf("clearing filename annotation: %w", err) return nil, fmt.Errorf("clearing filename annotation: %w", err)
@ -259,7 +275,7 @@ func splitAndDeannotate(postrendered string) (map[string]string, error) {
// TODO: As part of the refactor the duplicate code in cmd/helm/template.go should be removed // TODO: As part of the refactor the duplicate code in cmd/helm/template.go should be removed
// //
// This code has to do with writing files to disk. // This code has to do with writing files to disk.
func (cfg *Configuration) renderResources(ch *chart.Chart, values common.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrenderer.PostRenderer, interactWithRemote, enableDNS, hideSecret bool) ([]*release.Hook, *bytes.Buffer, string, error) { func (cfg *Configuration) renderResources(ch *chart.Chart, values common.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrenderer.PostRenderer, interactWithRemote, enableDNS, hideSecret bool, postRenderStrategy PostRenderStrategy) ([]*release.Hook, *bytes.Buffer, string, error) {
var hs []*release.Hook var hs []*release.Hook
b := bytes.NewBuffer(nil) b := bytes.NewBuffer(nil)
@ -323,29 +339,122 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values common.Values,
notes := notesBuffer.String() notes := notesBuffer.String()
if pr != nil { if pr != nil {
// We need to send files to the post-renderer before sorting and splitting switch postRenderStrategy {
// hooks from manifests. The post-renderer interface expects a stream of case PostRenderStrategySeparate, PostRenderStrategyNoHooks:
// manifests (similar to what tools like Kustomize and kubectl expect), whereas // Split hooks from manifests before post-rendering. For "separate",
// the sorter uses filenames. // hooks and templates are sent to the post-renderer as independent
// Here, we merge the documents into a stream, post-render them, and then split // streams to avoid duplicate-resource errors when the same resource
// them back into a map of filename -> content. // appears in both (e.g. a ServiceAccount used by a pre-install hook
// that is also declared in the chart's regular templates). For
// Merge files as stream of documents for sending to post renderer // "nohooks", hooks skip the post-renderer entirely, matching the
merged, err := annotateAndMerge(files) // Helm 3 behavior.
if err != nil { sortedHooks, sortedManifests, err := releaseutil.SortManifests(files, nil, releaseutil.InstallOrder)
return hs, b, notes, fmt.Errorf("error merging manifests: %w", err) if err != nil {
} for name, content := range files {
if strings.TrimSpace(content) == "" {
continue
}
fmt.Fprintf(b, "---\n# Source: %s\n%s\n", name, content)
}
return hs, b, "", err
}
// Run the post renderer // Build separate files maps for hooks and manifests.
postRendered, err := pr.Run(bytes.NewBufferString(merged)) hookFiles := make(map[string]string)
if err != nil { for _, h := range sortedHooks {
return hs, b, notes, fmt.Errorf("error while running post render on files: %w", err) if existing, ok := hookFiles[h.Path]; ok {
} hookFiles[h.Path] = existing + "\n---\n" + h.Manifest
} else {
hookFiles[h.Path] = h.Manifest
}
}
manifestFiles := make(map[string]string)
for _, m := range sortedManifests {
if existing, ok := manifestFiles[m.Name]; ok {
manifestFiles[m.Name] = existing + "\n---\n" + m.Content
} else {
manifestFiles[m.Name] = m.Content
}
}
// Use the file list and contents received from the post renderer // Decide which groups to post-render. "nohooks" passes hooks
files, err = splitAndDeannotate(postRendered.String()) // through untouched and only post-renders manifests.
if err != nil { groups := []struct {
return hs, b, notes, fmt.Errorf("error while parsing post rendered output: %w", err) name string
files map[string]string
postRender bool
}{
{"hooks", hookFiles, postRenderStrategy == PostRenderStrategySeparate},
{"manifests", manifestFiles, true},
}
files = make(map[string]string)
for _, group := range groups {
if len(group.files) == 0 {
continue
}
if !group.postRender {
for k, v := range group.files {
if existing, ok := files[k]; ok {
files[k] = existing + "\n---\n" + v
} else {
files[k] = v
}
}
continue
}
merged, err := annotateAndMerge(group.files)
if err != nil {
return hs, b, notes, fmt.Errorf("error merging %s: %w", group.name, err)
}
postRendered, err := pr.Run(bytes.NewBufferString(merged))
if err != nil {
return hs, b, notes, fmt.Errorf("error while running post render on %s: %w", group.name, err)
}
rendered, err := splitAndDeannotate(postRendered.String(), group.name)
if err != nil {
return hs, b, notes, fmt.Errorf("error while parsing post rendered output for %s: %w", group.name, err)
}
for k, v := range rendered {
if existing, ok := files[k]; ok {
files[k] = existing + "\n---\n" + v
} else {
files[k] = v
}
}
}
case PostRenderStrategyCombined, "":
// We need to send files to the post-renderer before sorting and splitting
// hooks from manifests. The post-renderer interface expects a stream of
// manifests (similar to what tools like Kustomize and kubectl expect), whereas
// the sorter uses filenames.
// Here, we merge the documents into a stream, post-render them, and then split
// them back into a map of filename -> content.
// Merge files as stream of documents for sending to post renderer
merged, err := annotateAndMerge(files)
if err != nil {
return hs, b, notes, fmt.Errorf("error merging manifests: %w", err)
}
// Run the post renderer
postRendered, err := pr.Run(bytes.NewBufferString(merged))
if err != nil {
return hs, b, notes, fmt.Errorf("error while running post render on files: %w", err)
}
// Use the file list and contents received from the post renderer
files, err = splitAndDeannotate(postRendered.String(), "")
if err != nil {
return hs, b, notes, fmt.Errorf("error while parsing post rendered output: %w", err)
}
default:
return hs, b, notes, fmt.Errorf("unknown post-render strategy: '%s'", postRenderStrategy)
} }
} }
@ -521,7 +630,6 @@ func GetVersionSet(client discovery.ServerResourcesInterface) (common.VersionSet
var ok bool var ok bool
for _, r := range resources { for _, r := range resources {
for _, rl := range r.APIResources { for _, rl := range r.APIResources {
// A Kind at a GroupVersion can show up more than once. We only want // A Kind at a GroupVersion can show up more than once. We only want
// it displayed once in the final output. // it displayed once in the final output.
id = path.Join(r.GroupVersion, rl.Kind) id = path.Join(r.GroupVersion, rl.Kind)

File diff suppressed because it is too large Load Diff

@ -120,7 +120,6 @@ func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, p
if r := statArchiveForStatus(archive, dep); r != "" { if r := statArchiveForStatus(archive, dep); r != "" {
return r return r
} }
} }
// End unnecessary code. // End unnecessary code.

@ -119,7 +119,6 @@ func (m *Metadata) FormattedDepNames() string {
continue continue
} }
depsNames = append(depsNames, ac.Name()) depsNames = append(depsNames, ac.Name())
} }
sort.StringSlice(depsNames).Sort() sort.StringSlice(depsNames).Sort()

@ -37,7 +37,7 @@ func TestNewGetValues(t *testing.T) {
assert.NotNil(t, client) assert.NotNil(t, client)
assert.Equal(t, cfg, client.cfg) assert.Equal(t, cfg, client.cfg)
assert.Equal(t, 0, client.Version) assert.Equal(t, 0, client.Version)
assert.Equal(t, false, client.AllValues) assert.False(t, client.AllValues)
} }
func TestGetValues_Run_UserConfigOnly(t *testing.T) { func TestGetValues_Run_UserConfigOnly(t *testing.T) {

@ -35,7 +35,6 @@ import (
func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption,
timeout time.Duration, serverSideApply bool) error { timeout time.Duration, serverSideApply bool) error {
shutdown, err := cfg.execHookWithDelayedShutdown(rl, hook, waitStrategy, waitOptions, timeout, serverSideApply) shutdown, err := cfg.execHookWithDelayedShutdown(rl, hook, waitStrategy, waitOptions, timeout, serverSideApply)
if shutdown == nil { if shutdown == nil {
return err return err
@ -59,7 +58,6 @@ func shutdownNoOp() error {
func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook release.HookEvent, func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook release.HookEvent,
waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration, waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration,
serverSideApply bool) (ExecuteShutdownFunc, error) { serverSideApply bool) (ExecuteShutdownFunc, error) {
executingHooks := []*release.Hook{} executingHooks := []*release.Hook{}
for _, h := range rl.Hooks { for _, h := range rl.Hooks {
@ -150,8 +148,8 @@ func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook
return func() error { return func() error {
// If all hooks are successful, check the annotation of each hook to determine whether the hook should be deleted // If all hooks are successful, check the annotation of each hook to determine whether the hook should be deleted
// or output should be logged under succeeded condition. If so, then clear the corresponding resource object in each hook // or output should be logged under succeeded condition. If so, then clear the corresponding resource object in each hook
for i := len(executingHooks) - 1; i >= 0; i-- { for _, v := range slices.Backward(executingHooks) {
h := executingHooks[i] h := v
if err := cfg.outputLogsByPolicy(h, rl.Namespace, release.HookOutputOnSucceeded); err != nil { if err := cfg.outputLogsByPolicy(h, rl.Namespace, release.HookOutputOnSucceeded); err != nil {
// We log here as we still want to attempt hook resource deletion even if output logging fails. // We log here as we still want to attempt hook resource deletion even if output logging fails.
log.Printf("error outputting logs for hook failure: %v", err) log.Printf("error outputting logs for hook failure: %v", err)
@ -179,7 +177,6 @@ func (x hookByWeight) Less(i, j int) bool {
// deleteHookByPolicy deletes a hook if the hook policy instructs it to // deleteHookByPolicy deletes a hook if the hook policy instructs it to
func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy, func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy,
waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration) error { waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration) error {
// Never delete CustomResourceDefinitions; this could cause lots of // Never delete CustomResourceDefinitions; this could cause lots of
// cascading garbage collection. // cascading garbage collection.
if h.Kind == "CustomResourceDefinition" { if h.Kind == "CustomResourceDefinition" {
@ -214,7 +211,6 @@ func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.Hoo
// deleteHooksByPolicy deletes all hooks if the hook policy instructs it to // deleteHooksByPolicy deletes all hooks if the hook policy instructs it to
func (cfg *Configuration) deleteHooksByPolicy(hooks []*release.Hook, policy release.HookDeletePolicy, func (cfg *Configuration) deleteHooksByPolicy(hooks []*release.Hook, policy release.HookDeletePolicy,
waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration) error { waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration) error {
for _, h := range hooks { for _, h := range hooks {
if err := cfg.deleteHookByPolicy(h, policy, waitStrategy, waitOptions, timeout); err != nil { if err := cfg.deleteHookByPolicy(h, policy, waitStrategy, waitOptions, timeout); err != nil {
return err return err

@ -130,6 +130,10 @@ type Install struct {
// TakeOwnership will ignore the check for helm annotations and take ownership of the resources. // TakeOwnership will ignore the check for helm annotations and take ownership of the resources.
TakeOwnership bool TakeOwnership bool
PostRenderer postrenderer.PostRenderer PostRenderer postrenderer.PostRenderer
// PostRenderStrategy controls how hooks and regular templates are passed
// to the configured post-renderer. See PostRenderStrategy for the
// available modes. Defaults to PostRenderStrategyCombined.
PostRenderStrategy PostRenderStrategy
// Lock to control raceconditions when the process receives a SIGTERM // Lock to control raceconditions when the process receives a SIGTERM
Lock sync.Mutex Lock sync.Mutex
goroutineCount atomic.Int32 goroutineCount atomic.Int32
@ -158,9 +162,10 @@ type ChartPathOptions struct {
// NewInstall creates a new Install object with the given configuration. // NewInstall creates a new Install object with the given configuration.
func NewInstall(cfg *Configuration) *Install { func NewInstall(cfg *Configuration) *Install {
in := &Install{ in := &Install{
cfg: cfg, cfg: cfg,
ServerSideApply: true, // Must always match the CLI default. ServerSideApply: true, // Must always match the CLI default.
DryRunStrategy: DryRunNone, DryRunStrategy: DryRunNone,
PostRenderStrategy: PostRenderStrategyCombined,
} }
in.registryClient = cfg.RegistryClient in.registryClient = cfg.RegistryClient
@ -370,7 +375,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
rel := i.createRelease(chrt, vals, i.Labels) rel := i.createRelease(chrt, vals, i.Labels)
var manifestDoc *bytes.Buffer var manifestDoc *bytes.Buffer
rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, interactWithServer(i.DryRunStrategy), i.EnableDNS, i.HideSecret) rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, interactWithServer(i.DryRunStrategy), i.EnableDNS, i.HideSecret, i.PostRenderStrategy)
// Even for errors, attach this if available // Even for errors, attach this if available
if manifestDoc != nil { if manifestDoc != nil {
rel.Manifest = manifestDoc.String() rel.Manifest = manifestDoc.String()

@ -737,7 +737,6 @@ func TestInstallRelease_RollbackOnFailure(t *testing.T) {
}) })
} }
func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) { func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) {
is := assert.New(t) is := assert.New(t)
instAction := installAction(t) instAction := installAction(t)
instAction.ReleaseName = "interrupted-release" instAction.ReleaseName = "interrupted-release"
@ -767,7 +766,6 @@ func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) {
is.Equal(goroutines+1, instAction.getGoroutineCount()) // installation goroutine still is in background is.Equal(goroutines+1, instAction.getGoroutineCount()) // installation goroutine still is in background
time.Sleep(10 * time.Second) // wait for goroutine to finish time.Sleep(10 * time.Second) // wait for goroutine to finish
is.Equal(goroutines, instAction.getGoroutineCount()) is.Equal(goroutines, instAction.getGoroutineCount())
} }
func TestNameTemplate(t *testing.T) { func TestNameTemplate(t *testing.T) {
testCases := []nameTemplateTestCase{ testCases := []nameTemplateTestCase{
@ -804,7 +802,6 @@ func TestNameTemplate(t *testing.T) {
} }
for _, tc := range testCases { for _, tc := range testCases {
n, err := TemplateName(tc.tpl) n, err := TemplateName(tc.tpl)
if err != nil { if err != nil {
if tc.expectedErrorStr == "" { if tc.expectedErrorStr == "" {
@ -867,7 +864,7 @@ func TestInstallReleaseOutputDir(t *testing.T) {
test.AssertGoldenFile(t, filepath.Join(dir, "hello/templates/rbac"), "rbac.txt") test.AssertGoldenFile(t, filepath.Join(dir, "hello/templates/rbac"), "rbac.txt")
_, err = os.Stat(filepath.Join(dir, "hello/templates/empty")) _, err = os.Stat(filepath.Join(dir, "hello/templates/empty"))
is.True(errors.Is(err, fs.ErrNotExist)) is.ErrorIs(err, fs.ErrNotExist)
} }
func TestInstallOutputDirWithReleaseName(t *testing.T) { func TestInstallOutputDirWithReleaseName(t *testing.T) {
@ -903,7 +900,7 @@ func TestInstallOutputDirWithReleaseName(t *testing.T) {
test.AssertGoldenFile(t, filepath.Join(newDir, "hello/templates/rbac"), "rbac.txt") test.AssertGoldenFile(t, filepath.Join(newDir, "hello/templates/rbac"), "rbac.txt")
_, err = os.Stat(filepath.Join(newDir, "hello/templates/empty")) _, err = os.Stat(filepath.Join(newDir, "hello/templates/empty"))
is.True(errors.Is(err, fs.ErrNotExist)) is.ErrorIs(err, fs.ErrNotExist)
} }
func TestNameAndChart(t *testing.T) { func TestNameAndChart(t *testing.T) {
@ -1168,7 +1165,7 @@ func TestInstallCRDs_AlreadyExist(t *testing.T) {
mockChart := buildChart(withFile(mockFile)) mockChart := buildChart(withFile(mockFile))
crdsToInstall := mockChart.CRDObjects() crdsToInstall := mockChart.CRDObjects()
assert.Nil(t, instAction.installCRDs(crdsToInstall)) assert.NoError(t, instAction.installCRDs(crdsToInstall))
} }
func TestInstallCRDs_KubeClient_BuildError(t *testing.T) { func TestInstallCRDs_KubeClient_BuildError(t *testing.T) {
@ -1227,7 +1224,7 @@ func TestCheckDependencies(t *testing.T) {
dependency := chart.Dependency{Name: "hello"} dependency := chart.Dependency{Name: "hello"}
mockChart := buildChart(withDependency()) mockChart := buildChart(withDependency())
assert.Nil(t, CheckDependencies(mockChart, []ci.Dependency{&dependency})) assert.NoError(t, CheckDependencies(mockChart, []ci.Dependency{&dependency}))
} }
func TestCheckDependencies_MissingDependency(t *testing.T) { func TestCheckDependencies_MissingDependency(t *testing.T) {

@ -150,7 +150,6 @@ func TestValidateVersion(t *testing.T) {
if !errors.Is(err, tt.wantErr) { if !errors.Is(err, tt.wantErr) {
t.Errorf("Expected {%v}, got {%v}", tt.wantErr, err) t.Errorf("Expected {%v}, got {%v}", tt.wantErr, err)
} }
} }
}) })
} }

@ -47,14 +47,14 @@ func TestNewPushWithInsecureSkipTLSVerify(t *testing.T) {
client := NewPushWithOpts(WithInsecureSkipTLSVerify(true)) client := NewPushWithOpts(WithInsecureSkipTLSVerify(true))
assert.NotNil(t, client) assert.NotNil(t, client)
assert.Equal(t, true, client.insecureSkipTLSVerify) assert.True(t, client.insecureSkipTLSVerify)
} }
func TestNewPushWithPlainHTTP(t *testing.T) { func TestNewPushWithPlainHTTP(t *testing.T) {
client := NewPushWithOpts(WithPlainHTTP(true)) client := NewPushWithOpts(WithPlainHTTP(true))
assert.NotNil(t, client) assert.NotNil(t, client)
assert.Equal(t, true, client.plainHTTP) assert.True(t, client.plainHTTP)
} }
func TestNewPushWithPushOptWriter(t *testing.T) { func TestNewPushWithPushOptWriter(t *testing.T) {

@ -37,7 +37,7 @@ func TestWithCertFile(t *testing.T) {
certFile := "testdata/cert.pem" certFile := "testdata/cert.pem"
opt := WithCertFile(certFile) opt := WithCertFile(certFile)
assert.Nil(t, opt(client)) assert.NoError(t, opt(client))
assert.Equal(t, certFile, client.certFile) assert.Equal(t, certFile, client.certFile)
} }
@ -47,8 +47,8 @@ func TestWithInsecure(t *testing.T) {
opt := WithInsecure(true) opt := WithInsecure(true)
assert.Nil(t, opt(client)) assert.NoError(t, opt(client))
assert.Equal(t, true, client.insecure) assert.True(t, client.insecure)
} }
func TestWithKeyFile(t *testing.T) { func TestWithKeyFile(t *testing.T) {
@ -58,7 +58,7 @@ func TestWithKeyFile(t *testing.T) {
keyFile := "testdata/key.pem" keyFile := "testdata/key.pem"
opt := WithKeyFile(keyFile) opt := WithKeyFile(keyFile)
assert.Nil(t, opt(client)) assert.NoError(t, opt(client))
assert.Equal(t, keyFile, client.keyFile) assert.Equal(t, keyFile, client.keyFile)
} }
@ -69,7 +69,7 @@ func TestWithCAFile(t *testing.T) {
caFile := "testdata/ca.pem" caFile := "testdata/ca.pem"
opt := WithCAFile(caFile) opt := WithCAFile(caFile)
assert.Nil(t, opt(client)) assert.NoError(t, opt(client))
assert.Equal(t, caFile, client.caFile) assert.Equal(t, caFile, client.caFile)
} }
@ -79,6 +79,6 @@ func TestWithPlainHTTPLogin(t *testing.T) {
opt := WithPlainHTTPLogin(true) opt := WithPlainHTTPLogin(true)
assert.Nil(t, opt(client)) assert.NoError(t, opt(client))
assert.Equal(t, true, client.plainHTTP) assert.True(t, client.plainHTTP)
} }

@ -18,6 +18,7 @@ package action
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"slices" "slices"
@ -25,6 +26,8 @@ import (
"time" "time"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
chartutil "helm.sh/helm/v4/pkg/chart/v2/util" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/kube" "helm.sh/helm/v4/pkg/kube"
@ -124,9 +127,9 @@ func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error {
return fmt.Errorf("unable to get kubernetes client to fetch pod logs: %w", err) return fmt.Errorf("unable to get kubernetes client to fetch pod logs: %w", err)
} }
hooksByWight := append([]*release.Hook{}, rel.Hooks...) hooksByWeight := append([]*release.Hook{}, rel.Hooks...)
sort.Stable(hookByWeight(hooksByWight)) sort.Stable(hookByWeight(hooksByWeight))
for _, h := range hooksByWight { for _, h := range hooksByWeight {
for _, e := range h.Events { for _, e := range h.Events {
if e == release.HookTest { if e == release.HookTest {
if slices.Contains(r.Filters[ExcludeNameFilter], h.Name) { if slices.Contains(r.Filters[ExcludeNameFilter], h.Name) {
@ -135,20 +138,43 @@ func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error {
if len(r.Filters[IncludeNameFilter]) > 0 && !slices.Contains(r.Filters[IncludeNameFilter], h.Name) { if len(r.Filters[IncludeNameFilter]) > 0 && !slices.Contains(r.Filters[IncludeNameFilter], h.Name) {
continue continue
} }
req := client.CoreV1().Pods(r.Namespace).GetLogs(h.Name, &v1.PodLogOptions{}) if err := r.getContainerLogs(out, client, h.Name); err != nil {
logReader, err := req.Stream(context.Background()) return err
if err != nil {
return fmt.Errorf("unable to get pod logs for %s: %w", h.Name, err)
}
fmt.Fprintf(out, "POD LOGS: %s\n", h.Name)
_, err = io.Copy(out, logReader)
fmt.Fprintln(out)
if err != nil {
return fmt.Errorf("unable to write pod logs for %s: %w", h.Name, err)
} }
} }
} }
} }
return nil return nil
} }
// getContainerLogs fetches logs from all containers (init and regular) in the
// named pod and writes them to out. It continues on per-container errors and
// returns all of them joined at the end.
func (r *ReleaseTesting) getContainerLogs(out io.Writer, client kubernetes.Interface, podName string) error {
pod, err := client.CoreV1().Pods(r.Namespace).Get(context.Background(), podName, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("unable to get pod %s: %w", podName, err)
}
allContainers := append(pod.Spec.InitContainers, pod.Spec.Containers...)
var errs []error
for _, c := range allContainers {
opts := &v1.PodLogOptions{Container: c.Name}
req := client.CoreV1().Pods(r.Namespace).GetLogs(podName, opts)
logReader, err := req.Stream(context.Background())
if err != nil {
errs = append(errs, fmt.Errorf("unable to get logs for pod %s, container %s: %w", podName, c.Name, err))
continue
}
fmt.Fprintf(out, "POD LOGS: %s (%s)\n", podName, c.Name)
_, err = io.Copy(out, logReader)
logReader.Close()
fmt.Fprintln(out)
if err != nil {
errs = append(errs, fmt.Errorf("unable to write logs for pod %s, container %s: %w", podName, c.Name, err))
}
}
return errors.Join(errs...)
}

@ -26,6 +26,9 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
fakeclientset "k8s.io/client-go/kubernetes/fake"
"helm.sh/helm/v4/pkg/cli" "helm.sh/helm/v4/pkg/cli"
"helm.sh/helm/v4/pkg/kube" "helm.sh/helm/v4/pkg/kube"
@ -89,7 +92,7 @@ func TestReleaseTestingGetPodLogs_PodRetrievalError(t *testing.T) {
}, },
} }
require.ErrorContains(t, client.GetPodLogs(&bytes.Buffer{}, &release.Release{Hooks: hooks}), "unable to get pod logs") require.ErrorContains(t, client.GetPodLogs(&bytes.Buffer{}, &release.Release{Hooks: hooks}), "unable to get pod")
} }
func TestReleaseTesting_WaitOptionsPassedDownstream(t *testing.T) { func TestReleaseTesting_WaitOptionsPassedDownstream(t *testing.T) {
@ -117,3 +120,91 @@ func TestReleaseTesting_WaitOptionsPassedDownstream(t *testing.T) {
// Verify that WaitOptions were passed to GetWaiter // Verify that WaitOptions were passed to GetWaiter
is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter") is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter")
} }
func TestGetContainerLogs_MultipleContainers(t *testing.T) {
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Namespace: "default",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{Name: "main"},
{Name: "sidecar"},
},
},
}
client := fakeclientset.NewClientset(pod)
rt := &ReleaseTesting{Namespace: "default"}
var buf bytes.Buffer
err := rt.getContainerLogs(&buf, client, "test-pod")
require.NoError(t, err)
output := buf.String()
assert.Contains(t, output, "POD LOGS: test-pod (main)")
assert.Contains(t, output, "POD LOGS: test-pod (sidecar)")
}
func TestGetContainerLogs_WithInitContainers(t *testing.T) {
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Namespace: "default",
},
Spec: v1.PodSpec{
InitContainers: []v1.Container{
{Name: "init-setup"},
},
Containers: []v1.Container{
{Name: "main"},
},
},
}
client := fakeclientset.NewClientset(pod)
rt := &ReleaseTesting{Namespace: "default"}
var buf bytes.Buffer
err := rt.getContainerLogs(&buf, client, "test-pod")
require.NoError(t, err)
output := buf.String()
// Init containers should appear before regular containers
assert.Contains(t, output, "POD LOGS: test-pod (init-setup)")
assert.Contains(t, output, "POD LOGS: test-pod (main)")
}
func TestGetContainerLogs_PodNotFound(t *testing.T) {
client := fakeclientset.NewClientset()
rt := &ReleaseTesting{Namespace: "default"}
var buf bytes.Buffer
err := rt.getContainerLogs(&buf, client, "nonexistent-pod")
require.Error(t, err)
assert.Contains(t, err.Error(), "unable to get pod nonexistent-pod")
}
func TestGetContainerLogs_OutputHeaderFormat(t *testing.T) {
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "multi-test",
Namespace: "default",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{Name: "container-a"},
{Name: "container-b"},
},
},
}
client := fakeclientset.NewClientset(pod)
rt := &ReleaseTesting{Namespace: "default"}
var buf bytes.Buffer
err := rt.getContainerLogs(&buf, client, "multi-test")
require.NoError(t, err)
output := buf.String()
assert.Contains(t, output, "POD LOGS: multi-test (container-a)")
assert.Contains(t, output, "POD LOGS: multi-test (container-b)")
}

@ -40,7 +40,6 @@ func filterManifestsToKeep(manifests []releaseutil.Manifest) (keep, remaining []
if resourcePolicyType == kube.KeepPolicy { if resourcePolicyType == kube.KeepPolicy {
keep = append(keep, m) keep = append(keep, m)
} }
} }
return keep, remaining return keep, remaining
} }

@ -23,3 +23,4 @@ subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: schedule-agents name: schedule-agents
namespace: spaced namespace: spaced

@ -121,6 +121,10 @@ type Upgrade struct {
// If this is non-nil, then after templates are rendered, they will be sent to the // If this is non-nil, then after templates are rendered, they will be sent to the
// post renderer before sending to the Kubernetes API server. // post renderer before sending to the Kubernetes API server.
PostRenderer postrenderer.PostRenderer PostRenderer postrenderer.PostRenderer
// PostRenderStrategy controls how hooks and regular templates are passed
// to the configured post-renderer. See PostRenderStrategy for the
// available modes. Defaults to PostRenderStrategyCombined.
PostRenderStrategy PostRenderStrategy
// DisableOpenAPIValidation controls whether OpenAPI validation is enforced. // DisableOpenAPIValidation controls whether OpenAPI validation is enforced.
DisableOpenAPIValidation bool DisableOpenAPIValidation bool
// Get missing dependencies // Get missing dependencies
@ -141,9 +145,10 @@ type resultMessage struct {
// NewUpgrade creates a new Upgrade object with the given configuration. // NewUpgrade creates a new Upgrade object with the given configuration.
func NewUpgrade(cfg *Configuration) *Upgrade { func NewUpgrade(cfg *Configuration) *Upgrade {
up := &Upgrade{ up := &Upgrade{
cfg: cfg, cfg: cfg,
ServerSideApply: "auto", // Must always match the CLI default. ServerSideApply: "auto", // Must always match the CLI default.
DryRunStrategy: DryRunNone, DryRunStrategy: DryRunNone,
PostRenderStrategy: PostRenderStrategyCombined,
} }
up.registryClient = cfg.RegistryClient up.registryClient = cfg.RegistryClient
@ -253,7 +258,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[str
var cerr error var cerr error
currentRelease, cerr = releaserToV1Release(currentReleasei) currentRelease, cerr = releaserToV1Release(currentReleasei)
if cerr != nil { if cerr != nil {
return nil, nil, false, err return nil, nil, false, cerr
} }
if err != nil { if err != nil {
if errors.Is(err, driver.ErrNoDeployedReleases) && if errors.Is(err, driver.ErrNoDeployedReleases) &&
@ -263,7 +268,6 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[str
return nil, nil, false, err return nil, nil, false, err
} }
} }
} }
// determine if values will be reused // determine if values will be reused
@ -296,7 +300,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[str
return nil, nil, false, err return nil, nil, false, err
} }
hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, interactWithServer(u.DryRunStrategy), u.EnableDNS, u.HideSecret) hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, interactWithServer(u.DryRunStrategy), u.EnableDNS, u.HideSecret, u.PostRenderStrategy)
if err != nil { if err != nil {
return nil, nil, false, err return nil, nil, false, err
} }

@ -446,7 +446,6 @@ func TestUpgradeRelease_Interrupted_Wait(t *testing.T) {
} }
func TestUpgradeRelease_Interrupted_RollbackOnFailure(t *testing.T) { func TestUpgradeRelease_Interrupted_RollbackOnFailure(t *testing.T) {
is := assert.New(t) is := assert.New(t)
req := require.New(t) req := require.New(t)
@ -716,7 +715,7 @@ func TestGetUpgradeServerSideValue(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
serverSideApply, err := getUpgradeServerSideValue(tt.actionServerSideOption, tt.releaseApplyMethod) serverSideApply, err := getUpgradeServerSideValue(tt.actionServerSideOption, tt.releaseApplyMethod)
assert.Nil(t, err) assert.NoError(t, err)
assert.Equal(t, tt.expectedServerSideApply, serverSideApply) assert.Equal(t, tt.expectedServerSideApply, serverSideApply)
}) })
} }
@ -741,7 +740,6 @@ func TestGetUpgradeServerSideValue(t *testing.T) {
assert.ErrorContains(t, err, tt.expectedErrorMsg) assert.ErrorContains(t, err, tt.expectedErrorMsg)
}) })
} }
} }
func TestUpgradeRun_UnreachableKubeClient(t *testing.T) { func TestUpgradeRun_UnreachableKubeClient(t *testing.T) {

@ -273,7 +273,6 @@ func TestValidateNameAndGenerateName(t *testing.T) {
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
skip, err := validateNameAndGenerateName(tc.info) skip, err := validateNameAndGenerateName(tc.info)
if tc.wantErr { if tc.wantErr {

@ -167,7 +167,6 @@ func makeDefaultCapabilities() (*Capabilities, error) {
} }
func newCapabilities(kubeVersionMajor, kubeVersionMinor uint64) (*Capabilities, error) { func newCapabilities(kubeVersionMajor, kubeVersionMinor uint64) (*Capabilities, error) {
version := fmt.Sprintf("v%d.%d.0", kubeVersionMajor, kubeVersionMinor) version := fmt.Sprintf("v%d.%d.0", kubeVersionMajor, kubeVersionMinor)
return &Capabilities{ return &Capabilities{
KubeVersion: KubeVersion{ KubeVersion: KubeVersion{

@ -60,8 +60,8 @@ func TestDefaultCapabilities(t *testing.T) {
} }
hv := caps.HelmVersion hv := caps.HelmVersion
if hv.Version != "v4.1" { if hv.Version != "v4.2" {
t.Errorf("Expected default HelmVersion to be v4.1, got %q", hv.Version) t.Errorf("Expected default HelmVersion to be v4.2, got %q", hv.Version)
} }
} }

@ -251,6 +251,12 @@ func coalesceValues(printf printFn, c chart.Charter, v map[string]any, prefix st
// If the key is a child chart, coalesce tables with Merge set to true // If the key is a child chart, coalesce tables with Merge set to true
merge := childChartMergeTrue(c, key, merge) merge := childChartMergeTrue(c, key, merge)
// When coalescing, clean nils from chart defaults before merging
// so they don't leak into the result.
if !merge {
cleanNilValues(src)
}
// Because v has higher precedence than nv, dest values override src // Because v has higher precedence than nv, dest values override src
// values. // values.
coalesceTablesFullKey(printf, dest, src, concatPrefix(subPrefix, key), merge) coalesceTablesFullKey(printf, dest, src, concatPrefix(subPrefix, key), merge)
@ -258,6 +264,16 @@ func coalesceValues(printf printFn, c chart.Charter, v map[string]any, prefix st
} }
} else { } else {
// If the key is not in v, copy it from nv. // If the key is not in v, copy it from nv.
// When coalescing, skip chart default nils and clean nils from
// nested maps so they don't shadow globals or produce %!s(<nil>).
if !merge {
if val == nil {
continue
}
if sub, ok := val.(map[string]any); ok {
cleanNilValues(sub)
}
}
v[key] = val v[key] = val
} }
} }
@ -326,7 +342,6 @@ func coalesceTablesFullKey(printf printFn, dst, src map[string]any, prefix strin
// But if src also has nil (or key not in src), preserve the nil // But if src also has nil (or key not in src), preserve the nil
delete(dst, key) delete(dst, key)
} else if !ok { } else if !ok {
// key not in user values, preserve src value (including nil)
dst[key] = val dst[key] = val
} else if istable(val) { } else if istable(val) {
if istable(dv) { if istable(dv) {
@ -341,6 +356,18 @@ func coalesceTablesFullKey(printf printFn, dst, src map[string]any, prefix strin
return dst return dst
} }
// cleanNilValues recursively removes nil entries in-place from a map so that chart
// default nils don't leak into the coalesced result.
func cleanNilValues(m map[string]any) {
for key, val := range m {
if val == nil {
delete(m, key)
} else if sub, ok := val.(map[string]any); ok {
cleanNilValues(sub)
}
}
}
// istable is a special-purpose function to see if the present thing matches the definition of a YAML table. // istable is a special-purpose function to see if the present thing matches the definition of a YAML table.
func istable(v any) bool { func istable(v any) bool {
_, ok := v.(map[string]any) _, ok := v.(map[string]any)

@ -666,7 +666,6 @@ func TestMergeTables(t *testing.T) {
} }
func TestCoalesceValuesWarnings(t *testing.T) { func TestCoalesceValuesWarnings(t *testing.T) {
c := withDeps(&chart.Chart{ c := withDeps(&chart.Chart{
Metadata: &chart.Metadata{Name: "level1"}, Metadata: &chart.Metadata{Name: "level1"},
Values: map[string]any{ Values: map[string]any{
@ -724,7 +723,6 @@ func TestCoalesceValuesWarnings(t *testing.T) {
assert.Contains(t, warnings, "warning: skipped value for level1.level2.level3.boat: Not a table.") assert.Contains(t, warnings, "warning: skipped value for level1.level2.level3.boat: Not a table.")
assert.Contains(t, warnings, "warning: destination for level1.level2.level3.spear.tip is a table. Ignoring non-table value (true)") assert.Contains(t, warnings, "warning: destination for level1.level2.level3.spear.tip is a table. Ignoring non-table value (true)")
assert.Contains(t, warnings, "warning: cannot overwrite table with non table for level1.level2.level3.spear.sail (map[cotton:true])") assert.Contains(t, warnings, "warning: cannot overwrite table with non table for level1.level2.level3.spear.sail (map[cotton:true])")
} }
func TestConcatPrefix(t *testing.T) { func TestConcatPrefix(t *testing.T) {
@ -765,3 +763,166 @@ func TestCoalesceValuesEmptyMapWithNils(t *testing.T) {
is.True(ok, "Expected data.baz key to be present but it was removed") is.True(ok, "Expected data.baz key to be present but it was removed")
is.Nil(data["baz"], "Expected data.baz key to be nil but it is not") is.Nil(data["baz"], "Expected data.baz key to be nil but it is not")
} }
// TestCoalesceValuesSubchartDefaultNilsCleaned tests that nil values in subchart defaults
// are cleaned up during coalescing when the parent doesn't set those keys.
// Regression test for issue #31919.
func TestCoalesceValuesSubchartDefaultNilsCleaned(t *testing.T) {
is := assert.New(t)
// Subchart has a default with nil values (e.g. keyMapping: {password: null})
subchart := &chart.Chart{
Metadata: &chart.Metadata{Name: "child"},
Values: map[string]any{
"keyMapping": map[string]any{
"password": nil,
},
},
}
parent := withDeps(&chart.Chart{
Metadata: &chart.Metadata{Name: "parent"},
Values: map[string]any{},
}, subchart)
// Parent user values don't mention keyMapping at all
vals := map[string]any{}
v, err := CoalesceValues(parent, vals)
is.NoError(err)
childVals, ok := v["child"].(map[string]any)
is.True(ok, "child values should be a map")
keyMapping, ok := childVals["keyMapping"].(map[string]any)
is.True(ok, "keyMapping should be a map")
// The nil "password" key from chart defaults should be cleaned up
_, ok = keyMapping["password"]
is.False(ok, "Expected keyMapping.password (nil from chart defaults) to be removed, but it is still present")
}
// TestCoalesceValuesUserNullErasesSubchartDefault tests that a user-supplied null
// value erases a subchart's default value during coalescing.
// Regression test for issue #31919.
func TestCoalesceValuesUserNullErasesSubchartDefault(t *testing.T) {
is := assert.New(t)
subchart := &chart.Chart{
Metadata: &chart.Metadata{Name: "child"},
Values: map[string]any{
"someKey": "default",
},
}
parent := withDeps(&chart.Chart{
Metadata: &chart.Metadata{Name: "parent"},
Values: map[string]any{},
}, subchart)
// User explicitly nullifies the subchart key via parent values
vals := map[string]any{
"child": map[string]any{
"someKey": nil,
},
}
v, err := CoalesceValues(parent, vals)
is.NoError(err)
childVals, ok := v["child"].(map[string]any)
is.True(ok, "child values should be a map")
// someKey should be erased — user null overrides subchart default
_, ok = childVals["someKey"]
is.False(ok, "Expected someKey to be removed by user null override, but it is still present")
}
// TestCoalesceValuesSubchartNilDoesNotShadowGlobal tests that a nil value in
// subchart defaults doesn't shadow a global value accessible via pluck-like access.
// Regression test for issue #31971.
func TestCoalesceValuesSubchartNilDoesNotShadowGlobal(t *testing.T) {
is := assert.New(t)
subchart := &chart.Chart{
Metadata: &chart.Metadata{Name: "child"},
Values: map[string]any{
"ingress": map[string]any{
"feature": nil, // nil in subchart defaults
},
},
}
parent := withDeps(&chart.Chart{
Metadata: &chart.Metadata{Name: "parent"},
Values: map[string]any{},
}, subchart)
// Parent sets the global value
vals := map[string]any{
"global": map[string]any{
"ingress": map[string]any{
"feature": true,
},
},
}
v, err := CoalesceValues(parent, vals)
is.NoError(err)
childVals, ok := v["child"].(map[string]any)
is.True(ok, "child values should be a map")
ingress, ok := childVals["ingress"].(map[string]any)
is.True(ok, "ingress should be a map")
// The nil "feature" from subchart defaults should be cleaned up,
// so that pluck can fall through to the global value
_, ok = ingress["feature"]
is.False(ok, "Expected ingress.feature (nil from chart defaults) to be removed so global can be used via pluck, but it is still present")
}
// TestCoalesceValuesSubchartNilCleanedWhenUserPartiallyOverrides tests that nil
// values in subchart defaults are cleaned even when the user partially overrides
// the same map. Regression test for the coalesceTablesFullKey merge path.
func TestCoalesceValuesSubchartNilCleanedWhenUserPartiallyOverrides(t *testing.T) {
is := assert.New(t)
subchart := &chart.Chart{
Metadata: &chart.Metadata{Name: "child"},
Values: map[string]any{
"keyMapping": map[string]any{
"password": nil,
"format": "bcrypt",
},
},
}
parent := withDeps(&chart.Chart{
Metadata: &chart.Metadata{Name: "parent"},
Values: map[string]any{},
}, subchart)
// User overrides format but doesn't mention password
vals := map[string]any{
"child": map[string]any{
"keyMapping": map[string]any{
"format": "sha256",
},
},
}
v, err := CoalesceValues(parent, vals)
is.NoError(err)
childVals, ok := v["child"].(map[string]any)
is.True(ok, "child values should be a map")
keyMapping, ok := childVals["keyMapping"].(map[string]any)
is.True(ok, "keyMapping should be a map")
is.Equal("sha256", keyMapping["format"], "User override should be preserved")
_, ok = keyMapping["password"]
is.False(ok, "Expected keyMapping.password (nil from chart defaults) to be removed even when user partially overrides the map")
}

@ -25,7 +25,6 @@ import (
) )
func TestToRenderValues(t *testing.T) { func TestToRenderValues(t *testing.T) {
chartValues := map[string]any{ chartValues := map[string]any{
"name": "al Rashid", "name": "al Rashid",
"where": map[string]any{ "where": map[string]any{

@ -100,7 +100,6 @@ func LoadDir(dir string) (chart.Charter, error) {
default: default:
return nil, errors.New("unsupported chart version") return nil, errors.New("unsupported chart version")
} }
} }
// FileLoader loads a chart from a file // FileLoader loads a chart from a file

@ -104,7 +104,7 @@ func TestMetadata(t *testing.T) {
is.Equal("foo.yaml", chrt.Name()) is.Equal("foo.yaml", chrt.Name())
is.Equal("1.0.0", chrt.AppVersion()) is.Equal("1.0.0", chrt.AppVersion())
is.Equal(nil, chrt.Validate()) is.NoError(chrt.Validate())
} }
func TestIsRoot(t *testing.T) { func TestIsRoot(t *testing.T) {
@ -124,8 +124,8 @@ func TestIsRoot(t *testing.T) {
is := assert.New(t) is := assert.New(t)
is.Equal(false, chrt1.IsRoot()) is.False(chrt1.IsRoot())
is.Equal(true, chrt2.IsRoot()) is.True(chrt2.IsRoot())
} }
func TestChartPath(t *testing.T) { func TestChartPath(t *testing.T) {

@ -44,7 +44,6 @@ func WithSkipSchemaValidation(skipSchemaValidation bool) LinterOption {
} }
func RunAll(baseDir string, values map[string]any, namespace string, options ...LinterOption) support.Linter { func RunAll(baseDir string, values map[string]any, namespace string, options ...LinterOption) support.Linter {
chartDir, _ := filepath.Abs(baseDir) chartDir, _ := filepath.Abs(baseDir)
lo := linterOptions{} lo := linterOptions{}

@ -28,7 +28,7 @@ import (
kscheme "k8s.io/client-go/kubernetes/scheme" kscheme "k8s.io/client-go/kubernetes/scheme"
) )
// deprecatedAPIError indicates than an API is deprecated in Kubernetes // deprecatedAPIError indicates that an API is deprecated in Kubernetes
type deprecatedAPIError struct { type deprecatedAPIError struct {
Deprecated string Deprecated string
Message string Message string

@ -28,8 +28,8 @@ import (
"slices" "slices"
"strings" "strings"
"k8s.io/apimachinery/pkg/api/validate/content"
"k8s.io/apimachinery/pkg/api/validation" "k8s.io/apimachinery/pkg/api/validation"
apipath "k8s.io/apimachinery/pkg/api/validation/path"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apimachinery/pkg/util/yaml" "k8s.io/apimachinery/pkg/util/yaml"
@ -62,7 +62,6 @@ func TemplateLinterSkipSchemaValidation(skipSchemaValidation bool) TemplateLinte
} }
func newTemplateLinter(linter *support.Linter, namespace string, values map[string]any, options ...TemplateLinterOption) templateLinter { func newTemplateLinter(linter *support.Linter, namespace string, values map[string]any, options ...TemplateLinterOption) templateLinter {
result := templateLinter{ result := templateLinter{
linter: linter, linter: linter,
values: values, values: values,
@ -323,7 +322,7 @@ func validateMetadataNameFunc(obj *k8sYamlStruct) validation.ValidateNameFunc {
case "role", "clusterrole", "rolebinding", "clusterrolebinding": case "role", "clusterrole", "rolebinding", "clusterrolebinding":
// https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/rbac/validation/validation.go#L32-L34 // https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/rbac/validation/validation.go#L32-L34
return func(name string, _ bool) []string { return func(name string, _ bool) []string {
return apipath.IsValidPathSegmentName(name) return content.IsPathSegmentName(name)
} }
default: default:
return validation.NameIsDNSSubdomain return validation.NameIsDNSSubdomain

@ -257,7 +257,6 @@ data:
// //
// See https://github.com/helm/helm/issues/7483 // See https://github.com/helm/helm/issues/7483
func TestStrictTemplateParsingMapError(t *testing.T) { func TestStrictTemplateParsingMapError(t *testing.T) {
ch := chart.Chart{ ch := chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
Name: "regression7483", Name: "regression7483",
@ -390,7 +389,6 @@ func TestValidateTopIndentLevel(t *testing.T) {
t.Errorf("Expected %t for %q", shouldFail, doc) t.Errorf("Expected %t for %q", shouldFail, doc)
} }
} }
} }
// TestEmptyWithCommentsManifests checks the lint is not failing against empty manifests that contains only comments // TestEmptyWithCommentsManifests checks the lint is not failing against empty manifests that contains only comments
@ -486,5 +484,4 @@ func TestIsYamlFileExtension(t *testing.T) {
t.Errorf("isYamlFileExtension(%s) = %v; want %v", test.filename, result, test.expected) t.Errorf("isYamlFileExtension(%s) = %v; want %v", test.filename, result, test.expected)
} }
} }
} }

@ -390,7 +390,6 @@ icon: https://example.com/64x64.png
if text.String() != "" { if text.String() != "" {
t.Errorf("Expected no message to Stderr, got %s", text.String()) t.Errorf("Expected no message to Stderr, got %s", text.String())
} }
} }
// Packaging the chart on a Windows machine will produce an // Packaging the chart on a Windows machine will produce an
@ -660,7 +659,6 @@ func verifyChart(t *testing.T, c *chart.Chart) {
t.Errorf("Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version) t.Errorf("Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version)
} }
} }
} }
func verifyDependencies(t *testing.T, c *chart.Chart) { func verifyDependencies(t *testing.T, c *chart.Chart) {

@ -112,6 +112,9 @@ func (md *Metadata) Validate() error {
return ValidationError("chart.metadata.name is required") return ValidationError("chart.metadata.name is required")
} }
if md.Name == "." || md.Name == ".." {
return ValidationErrorf("chart.metadata.name %q is not allowed", md.Name)
}
if md.Name != filepath.Base(md.Name) { if md.Name != filepath.Base(md.Name) {
return ValidationErrorf("chart.metadata.name %q is invalid", md.Name) return ValidationErrorf("chart.metadata.name %q is invalid", md.Name)
} }

@ -41,6 +41,16 @@ func TestValidate(t *testing.T) {
&Metadata{APIVersion: "v2", Version: "1.0"}, &Metadata{APIVersion: "v2", Version: "1.0"},
ValidationError("chart.metadata.name is required"), ValidationError("chart.metadata.name is required"),
}, },
{
"chart with dot name",
&Metadata{Name: ".", APIVersion: "v2", Version: "1.0"},
ValidationError("chart.metadata.name \".\" is not allowed"),
},
{
"chart with dotdot name",
&Metadata{Name: "..", APIVersion: "v2", Version: "1.0"},
ValidationError("chart.metadata.name \"..\" is not allowed"),
},
{ {
"chart without name", "chart without name",
&Metadata{Name: "../../test", APIVersion: "v2", Version: "1.0"}, &Metadata{Name: "../../test", APIVersion: "v2", Version: "1.0"},

@ -701,7 +701,6 @@ func CreateFrom(chartfile *chart.Metadata, dest, src string) error {
// error. In such a case, this will attempt to clean up by removing the // error. In such a case, this will attempt to clean up by removing the
// new chart directory. // new chart directory.
func Create(name, dir string) (string, error) { func Create(name, dir string) (string, error) {
// Sanity-check the name of a chart so user doesn't create one that causes problems. // Sanity-check the name of a chart so user doesn't create one that causes problems.
if err := validateChartName(name); err != nil { if err := validateChartName(name); err != nil {
return "", err return "", err

@ -19,6 +19,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
"slices"
"strings" "strings"
"helm.sh/helm/v4/internal/copystructure" "helm.sh/helm/v4/internal/copystructure"
@ -242,8 +243,8 @@ func set(path []string, data map[string]any) map[string]any {
return nil return nil
} }
cur := data cur := data
for i := len(path) - 1; i >= 0; i-- { for _, v := range slices.Backward(path) {
cur = map[string]any{path[i]: cur} cur = map[string]any{v: cur}
} }
return cur return cur
} }

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save