Merge branch 'main' of https://github.com/helm/helm into optional-version

Signed-off-by: Felipe Santos <felipecassiors@gmail.com>
pull/10096/head
Felipe Santos 3 months ago
commit 8a9b854daa

@ -0,0 +1,69 @@
name: Bug Report
description: Report a bug encountered in Helm
labels: kind/bug
body:
- type: textarea
id: problem
attributes:
label: What happened?
description: |
Please provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner.
validations:
required: true
- type: textarea
id: expected
attributes:
label: What did you expect to happen?
validations:
required: true
- type: textarea
id: repro
attributes:
label: How can we reproduce it (as minimally and precisely as possible)?
description: |
Please list steps someone can follow to trigger the issue.
For example:
1. Run `helm install mychart ./path-to-chart -f values.yaml --debug`
2. Observe the following error: ...
You can include:
- a sample `values.yaml` block
- a link to a chart
- specific `helm` commands used
This helps others reproduce and debug your issue more effectively.
validations:
required: true
- type: textarea
id: helmVersion
attributes:
label: Helm version
value: |
<details>
```console
$ helm version
# paste output here
```
</details>
validations:
required: true
- type: textarea
id: kubeVersion
attributes:
label: Kubernetes version
value: |
<details>
```console
$ kubectl version
# paste output here
```
</details>
validations:
required: true

@ -0,0 +1,27 @@
name: Documentation
description: Report any mistakes or missing information from the documentation or the examples
labels: kind/documentation
body:
- type: markdown
attributes:
value: |
⚠️ **Note**: Most documentation lives in [helm/helm-www](https://github.com/helm/helm-www).
If your issue is about Helm website documentation or examples, please [open an issue there](https://github.com/helm/helm-www/issues/new/choose).
- type: textarea
id: feature
attributes:
label: What would you like to be added?
description: |
Link to the issue (please include a link to the specific documentation or example).
Link to the issue raised in [Helm Documentation Improvement Proposal](https://github.com/helm/helm-www)
validations:
required: true
- type: textarea
id: rationale
attributes:
label: Why is this needed?
validations:
required: true

@ -0,0 +1,21 @@
name: Enhancement/feature
description: Provide supporting details for a feature in development
labels: kind/feature
body:
- type: textarea
id: feature
attributes:
label: What would you like to be added?
description: |
Feature requests are unlikely to make progress as issues.
Initial discussion and ideas can happen on an issue.
But significant changes or features must be proposed as a [Helm Improvement Proposal](https://github.com/helm/community/blob/main/hips/hip-0001.md) (HIP)
validations:
required: true
- type: textarea
id: rationale
attributes:
label: Why is this needed?
validations:
required: true

2
.github/env vendored

@ -0,0 +1,2 @@
GOLANG_VERSION=1.24
GOLANGCI_LINT_VERSION=v2.1.0

@ -1,9 +0,0 @@
<!-- If you need help or think you have found a bug, please help us with your issue by entering the following information (otherwise you can delete this text): -->
Output of `helm version`:
Output of `kubectl version`:
Cloud Provider/Platform (AKS, GKE, Minikube etc.):

@ -19,10 +19,12 @@ jobs:
steps: steps:
- name: Checkout source code - name: Checkout source code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2
- name: Add variables to environment file
run: cat ".github/env" >> "$GITHUB_ENV"
- name: Setup Go - name: Setup Go
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # pin@5.3.0 uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@5.5.0
with: with:
go-version: '1.23' go-version: '${{ env.GOLANG_VERSION }}'
check-latest: true check-latest: true
- name: Test source headers are present - name: Test source headers are present
run: make test-source-headers run: make test-source-headers

@ -14,13 +14,14 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2
- name: Add variables to environment file
run: cat ".github/env" >> "$GITHUB_ENV"
- name: Setup Go - name: Setup Go
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # pin@5.3.0 uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@5.5.0
with: with:
go-version: '1.23' go-version: '${{ env.GOLANG_VERSION }}'
check-latest: true check-latest: true
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@2226d7cb06a077cd73e56eedd38eecad18e5d837 #pin@6.5.0 uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 #pin@8.0.0
with: with:
version: v1.62 version: ${{ env.GOLANGCI_LINT_VERSION }}

@ -13,10 +13,14 @@ jobs:
name: govulncheck name: govulncheck
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2
- name: Add variables to environment file
run: cat ".github/env" >> "$GITHUB_ENV"
- name: Setup Go - name: Setup Go
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # pin@5.3.0 uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@5.5.0
with: with:
go-version: '1.23' go-version: '${{ env.GOLANG_VERSION }}'
check-latest: true check-latest: true
- name: govulncheck - name: govulncheck
uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # pin@1.0.4 uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # pin@1.0.4

@ -24,14 +24,15 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Add variables to environment file
run: cat ".github/env" >> "$GITHUB_ENV"
- name: Setup Go - name: Setup Go
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # pin@5.3.0 uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@5.5.0
with: with:
go-version: '1.23' go-version: '${{ env.GOLANG_VERSION }}'
- name: Run unit tests - name: Run unit tests
run: make test-coverage run: make test-coverage
- name: Build Helm Binaries - name: Build Helm Binaries
run: | run: |
set -eu -o pipefail set -eu -o pipefail
@ -80,10 +81,13 @@ jobs:
- name: Checkout source code - name: Checkout source code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2
- name: Add variables to environment file
run: cat ".github/env" >> "$GITHUB_ENV"
- name: Setup Go - name: Setup Go
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # pin@5.3.0 uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@5.5.0
with: with:
go-version: '1.23' go-version: '${{ env.GOLANG_VERSION }}'
check-latest: true check-latest: true
- name: Run unit tests - name: Run unit tests

@ -33,7 +33,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: "Run analysis" - name: "Run analysis"
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
with: with:
results_file: results.sarif results_file: results.sarif
results_format: sarif results_format: sarif
@ -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@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: SARIF file name: SARIF file
path: results.sarif path: results.sarif

1
.gitignore vendored

@ -12,4 +12,5 @@ bin/
vendor/ vendor/
# Ignores charts pulled for dependency build tests # Ignores charts pulled for dependency build tests
cmd/helm/testdata/testcharts/issue-7233/charts/* cmd/helm/testdata/testcharts/issue-7233/charts/*
pkg/cmd/testdata/testcharts/issue-7233/charts/*
.pre-commit-config.yaml .pre-commit-config.yaml

@ -1,45 +1,71 @@
run: formatters:
timeout: 10m enable:
- gofmt
- goimports
exclusions:
generated: lax
settings:
gofmt:
simplify: true
goimports:
local-prefixes:
- helm.sh/helm/v4
linters: linters:
disable-all: true default: none
enable: enable:
- depguard
- dupl - dupl
- gofmt - gomodguard
- goimports
- gosimple
- govet - govet
- ineffassign - ineffassign
- misspell - misspell
- nakedret - nakedret
- revive - revive
- unused
- staticcheck - staticcheck
- thelper
- unused
- usestdlibvars
- usetesting
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules: []
warn-unused: true
settings:
depguard:
rules:
Main:
deny:
- pkg: github.com/hashicorp/go-multierror
desc: "use errors instead"
- pkg: github.com/pkg/errors
desc: "use errors instead"
dupl:
threshold: 400
gomodguard:
blocked:
modules:
- github.com/evanphx/json-patch:
recommendations:
- github.com/evanphx/json-patch/v5
run:
timeout: 10m
linters-settings: version: "2"
gofmt:
simplify: true
goimports:
local-prefixes: helm.sh/helm/v4
dupl:
threshold: 400
issues:
exclude-rules:
# Helm, and the Go source code itself, sometimes uses these names outside their built-in
# functions. As the Go source code has re-used these names it's ok for Helm to do the same.
# Linting will look for redefinition of built-in id's but we opt-in to the ones we choose to use.
- linters:
- revive
text: "redefines-builtin-id: redefinition of the built-in function append"
- linters:
- revive
text: "redefines-builtin-id: redefinition of the built-in function clear"
- linters:
- revive
text: "redefines-builtin-id: redefinition of the built-in function max"
- linters:
- revive
text: "redefines-builtin-id: redefinition of the built-in function min"
- linters:
- revive
text: "redefines-builtin-id: redefinition of the built-in function new"

@ -65,8 +65,8 @@ K8S_MODULES_MINOR_VER=$(word 2,$(K8S_MODULES_VER))
LDFLAGS += -X helm.sh/helm/v4/pkg/lint/rules.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER) LDFLAGS += -X helm.sh/helm/v4/pkg/lint/rules.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER)
LDFLAGS += -X helm.sh/helm/v4/pkg/lint/rules.k8sVersionMinor=$(K8S_MODULES_MINOR_VER) LDFLAGS += -X helm.sh/helm/v4/pkg/lint/rules.k8sVersionMinor=$(K8S_MODULES_MINOR_VER)
LDFLAGS += -X helm.sh/helm/v4/pkg/chart/util.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER) LDFLAGS += -X helm.sh/helm/v4/pkg/chart/v2/util.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER)
LDFLAGS += -X helm.sh/helm/v4/pkg/chart/util.k8sVersionMinor=$(K8S_MODULES_MINOR_VER) LDFLAGS += -X helm.sh/helm/v4/pkg/chart/v2/util.k8sVersionMinor=$(K8S_MODULES_MINOR_VER)
.PHONY: all .PHONY: all
all: build all: build
@ -156,7 +156,7 @@ format: $(GOIMPORTS)
# Generate golden files used in unit tests # Generate golden files used in unit tests
.PHONY: gen-test-golden .PHONY: gen-test-golden
gen-test-golden: gen-test-golden:
gen-test-golden: PKG = ./cmd/helm ./pkg/action gen-test-golden: PKG = ./pkg/cmd ./pkg/action
gen-test-golden: TESTFLAGS = -update gen-test-golden: TESTFLAGS = -update
gen-test-golden: test-unit gen-test-golden: test-unit

@ -9,6 +9,7 @@ maintainers:
- technosophos - technosophos
triage: triage:
- banjoh - banjoh
- TerryHowe
- yxxhero - yxxhero
- zonggen - zonggen
- z4ce - z4ce

@ -1,10 +1,11 @@
# Helm # Helm
[![Build Status](https://github.com/helm/helm/workflows/release/badge.svg)](https://github.com/helm/helm/actions?workflow=release) [![Build Status](https://github.com/helm/helm/workflows/release/badge.svg)](https://github.com/helm/helm/actions?workflow=release)
[![Go Report Card](https://goreportcard.com/badge/github.com/helm/helm)](https://goreportcard.com/report/github.com/helm/helm) [![Go Report Card](https://goreportcard.com/badge/helm.sh/helm/v4)](https://goreportcard.com/report/helm.sh/helm/v4)
[![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/helm.sh/helm/v4) [![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/helm.sh/helm/v4)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3131/badge)](https://bestpractices.coreinfrastructure.org/projects/3131) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3131/badge)](https://bestpractices.coreinfrastructure.org/projects/3131)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/helm/helm/badge)](https://scorecard.dev/viewer/?uri=github.com/helm/helm) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/helm/helm/badge)](https://scorecard.dev/viewer/?uri=github.com/helm/helm)
[![LFX Health Score](https://img.shields.io/static/v1?label=Health%20Score&message=Healthy&color=A7F3D0&logo=linuxfoundation&logoColor=white&style=flat)](https://insights.linuxfoundation.org/project/helm)
Helm is a tool for managing Charts. Charts are packages of pre-configured Kubernetes resources. Helm is a tool for managing Charts. Charts are packages of pre-configured Kubernetes resources.
@ -56,7 +57,7 @@ including installing pre-releases.
## Docs ## Docs
Get started with the [Quick Start guide](https://helm.sh/docs/intro/quickstart/) or plunge into the [complete documentation](https://helm.sh/docs) Get started with the [Quick Start guide](https://helm.sh/docs/intro/quickstart/) or plunge into the [complete documentation](https://helm.sh/docs).
## Roadmap ## Roadmap

@ -17,51 +17,16 @@ limitations under the License.
package main // import "helm.sh/helm/v4/cmd/helm" package main // import "helm.sh/helm/v4/cmd/helm"
import ( import (
"fmt" "log/slog"
"io"
"log"
"os" "os"
"strings"
"time"
"github.com/spf13/cobra"
"sigs.k8s.io/yaml"
// Import to initialize client auth plugins. // Import to initialize client auth plugins.
_ "k8s.io/client-go/plugin/pkg/client/auth" _ "k8s.io/client-go/plugin/pkg/client/auth"
"helm.sh/helm/v4/pkg/action" helmcmd "helm.sh/helm/v4/pkg/cmd"
"helm.sh/helm/v4/pkg/cli"
"helm.sh/helm/v4/pkg/kube" "helm.sh/helm/v4/pkg/kube"
kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v4/pkg/storage/driver"
) )
var settings = cli.New()
func init() {
log.SetFlags(log.Lshortfile)
}
func debug(format string, v ...interface{}) {
if settings.Debug {
timeNow := time.Now().String()
format = fmt.Sprintf("%s [debug] %s\n", timeNow, format)
log.Output(2, fmt.Sprintf(format, v...))
}
}
func warning(format string, v ...interface{}) {
format = fmt.Sprintf("WARNING: %s\n", format)
fmt.Fprintf(os.Stderr, format, v...)
}
// hookOutputWriter provides the writer for writing hook logs.
func hookOutputWriter(_, _, _ string) io.Writer {
return log.Writer()
}
func main() { func main() {
// Setting the name of the app for managedFields in the Kubernetes client. // Setting the name of the app for managedFields in the Kubernetes client.
// It is set here to the full name of "helm" so that renaming of helm to // It is set here to the full name of "helm" so that renaming of helm to
@ -69,70 +34,18 @@ func main() {
// manager as picked up by the automated name detection. // manager as picked up by the automated name detection.
kube.ManagedFieldsManager = "helm" kube.ManagedFieldsManager = "helm"
actionConfig := new(action.Configuration) cmd, err := helmcmd.NewRootCmd(os.Stdout, os.Args[1:], helmcmd.SetupLogging)
cmd, err := newRootCmd(actionConfig, os.Stdout, os.Args[1:])
if err != nil { if err != nil {
warning("%+v", err) slog.Warn("command failed", slog.Any("error", err))
os.Exit(1) os.Exit(1)
} }
// run when each command's execute method is called
cobra.OnInitialize(func() {
helmDriver := os.Getenv("HELM_DRIVER")
if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), helmDriver, debug); err != nil {
log.Fatal(err)
}
if helmDriver == "memory" {
loadReleasesInMemory(actionConfig)
}
actionConfig.SetHookOutputFunc(hookOutputWriter)
})
if err := cmd.Execute(); err != nil { if err := cmd.Execute(); err != nil {
debug("%+v", err)
switch e := err.(type) { switch e := err.(type) {
case pluginError: case helmcmd.PluginError:
os.Exit(e.code) os.Exit(e.Code)
default: default:
os.Exit(1) os.Exit(1)
} }
} }
} }
// This function loads releases into the memory storage if the
// environment variable is properly set.
func loadReleasesInMemory(actionConfig *action.Configuration) {
filePaths := strings.Split(os.Getenv("HELM_MEMORY_DRIVER_DATA"), ":")
if len(filePaths) == 0 {
return
}
store := actionConfig.Releases
mem, ok := store.Driver.(*driver.Memory)
if !ok {
// For an unexpected reason we are not dealing with the memory storage driver.
return
}
actionConfig.KubeClient = &kubefake.PrintingKubeClient{Out: io.Discard}
for _, path := range filePaths {
b, err := os.ReadFile(path)
if err != nil {
log.Fatal("Unable to read memory driver data", err)
}
releases := []*release.Release{}
if err := yaml.Unmarshal(b, &releases); err != nil {
log.Fatal("Unable to unmarshal memory driver data: ", err)
}
for _, rel := range releases {
if err := store.Create(rel); err != nil {
log.Fatal(err)
}
}
}
// Must reset namespace to the proper one
mem.SetNamespace(settings.Namespace())
}

@ -18,153 +18,12 @@ package main
import ( import (
"bytes" "bytes"
"io"
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
"strings"
"testing" "testing"
shellwords "github.com/mattn/go-shellwords"
"github.com/spf13/cobra"
"helm.sh/helm/v4/internal/test"
"helm.sh/helm/v4/pkg/action"
chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/cli"
kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v4/pkg/storage"
"helm.sh/helm/v4/pkg/storage/driver"
"helm.sh/helm/v4/pkg/time"
) )
func testTimestamper() time.Time { return time.Unix(242085845, 0).UTC() }
func init() {
action.Timestamper = testTimestamper
}
func runTestCmd(t *testing.T, tests []cmdTestCase) {
t.Helper()
for _, tt := range tests {
for i := 0; i <= tt.repeat; i++ {
t.Run(tt.name, func(t *testing.T) {
defer resetEnv()()
storage := storageFixture()
for _, rel := range tt.rels {
if err := storage.Create(rel); err != nil {
t.Fatal(err)
}
}
t.Logf("running cmd (attempt %d): %s", i+1, tt.cmd)
_, out, err := executeActionCommandC(storage, tt.cmd)
if tt.wantError && err == nil {
t.Errorf("expected error, got success with the following output:\n%s", out)
}
if !tt.wantError && err != nil {
t.Errorf("expected no error, got: '%v'", err)
}
if tt.golden != "" {
test.AssertGoldenString(t, out, tt.golden)
}
})
}
}
}
func storageFixture() *storage.Storage {
return storage.Init(driver.NewMemory())
}
func executeActionCommandC(store *storage.Storage, cmd string) (*cobra.Command, string, error) {
return executeActionCommandStdinC(store, nil, cmd)
}
func executeActionCommandStdinC(store *storage.Storage, in *os.File, cmd string) (*cobra.Command, string, error) {
args, err := shellwords.Parse(cmd)
if err != nil {
return nil, "", err
}
buf := new(bytes.Buffer)
actionConfig := &action.Configuration{
Releases: store,
KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard},
Capabilities: chartutil.DefaultCapabilities,
Log: func(_ string, _ ...interface{}) {},
}
root, err := newRootCmd(actionConfig, buf, args)
if err != nil {
return nil, "", err
}
root.SetOut(buf)
root.SetErr(buf)
root.SetArgs(args)
oldStdin := os.Stdin
if in != nil {
root.SetIn(in)
os.Stdin = in
}
if mem, ok := store.Driver.(*driver.Memory); ok {
mem.SetNamespace(settings.Namespace())
}
c, err := root.ExecuteC()
result := buf.String()
os.Stdin = oldStdin
return c, result, err
}
// cmdTestCase describes a test case that works with releases.
type cmdTestCase struct {
name string
cmd string
golden string
wantError bool
// Rels are the available releases at the start of the test.
rels []*release.Release
// Number of repeats (in case a feature was previously flaky and the test checks
// it's now stably producing identical results). 0 means test is run exactly once.
repeat int
}
func executeActionCommand(cmd string) (*cobra.Command, string, error) {
return executeActionCommandC(storageFixture(), cmd)
}
func resetEnv() func() {
origEnv := os.Environ()
return func() {
os.Clearenv()
for _, pair := range origEnv {
kv := strings.SplitN(pair, "=", 2)
os.Setenv(kv[0], kv[1])
}
settings = cli.New()
}
}
func testChdir(t *testing.T, dir string) func() {
t.Helper()
old, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
if err := os.Chdir(dir); err != nil {
t.Fatal(err)
}
return func() { os.Chdir(old) }
}
func TestPluginExitCode(t *testing.T) { func TestPluginExitCode(t *testing.T) {
if os.Getenv("RUN_MAIN_FOR_TESTING") == "1" { if os.Getenv("RUN_MAIN_FOR_TESTING") == "1" {
os.Args = []string{"helm", "exitwith", "2"} os.Args = []string{"helm", "exitwith", "2"}
@ -190,10 +49,8 @@ func TestPluginExitCode(t *testing.T) {
"RUN_MAIN_FOR_TESTING=1", "RUN_MAIN_FOR_TESTING=1",
// See pkg/cli/environment.go for which envvars can be used for configuring these passes // See pkg/cli/environment.go for which envvars can be used for configuring these passes
// and also see plugin_test.go for how a plugin env can be set up. // and also see plugin_test.go for how a plugin env can be set up.
// We just does the same setup as plugin_test.go via envvars // This mimics the "exitwith" test case in TestLoadPlugins using envvars
"HELM_PLUGINS=testdata/helmhome/helm/plugins", "HELM_PLUGINS=../../pkg/cmd/testdata/helmhome/helm/plugins",
"HELM_REPOSITORY_CONFIG=testdata/helmhome/helm/repositories.yaml",
"HELM_REPOSITORY_CACHE=testdata/helmhome/helm/repository",
) )
stdout := &bytes.Buffer{} stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{} stderr := &bytes.Buffer{}

@ -1 +0,0 @@
Error: INSTALLATION FAILED: Hiding Kubernetes secrets requires a dry-run mode

@ -1,8 +0,0 @@
---
# Source: issue-totoml/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: issue-totoml
data: |
key = 13

@ -1,3 +0,0 @@
apiVersion: v2
name: issue-totoml
version: 0.1.0

@ -1,6 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: issue-totoml
data: |
{{ .Values.global | toToml }}

139
go.mod

@ -1,51 +1,51 @@
module helm.sh/helm/v4 module helm.sh/helm/v4
go 1.23.0 go 1.24.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.4.0 github.com/BurntSushi/toml v1.5.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.3.0 github.com/Masterminds/semver/v3 v3.4.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/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/containerd/containerd v1.7.25
github.com/cyphar/filepath-securejoin v0.4.1 github.com/cyphar/filepath-securejoin v0.4.1
github.com/distribution/distribution/v3 v3.0.0-rc.3 github.com/distribution/distribution/v3 v3.0.0
github.com/evanphx/json-patch v5.9.11+incompatible github.com/evanphx/json-patch/v5 v5.9.11
github.com/fluxcd/cli-utils v0.36.0-flux.13
github.com/foxcpp/go-mockdns v1.1.0 github.com/foxcpp/go-mockdns v1.1.0
github.com/gobwas/glob v0.2.3 github.com/gobwas/glob v0.2.3
github.com/gofrs/flock v0.12.1 github.com/gofrs/flock v0.12.1
github.com/gosuri/uitable v0.0.4 github.com/gosuri/uitable v0.0.4
github.com/hashicorp/go-multierror v1.1.1
github.com/jmoiron/sqlx v1.4.0 github.com/jmoiron/sqlx v1.4.0
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/mattn/go-shellwords v1.0.12 github.com/mattn/go-shellwords v1.0.12
github.com/mitchellh/copystructure v1.2.0 github.com/mitchellh/copystructure v1.2.0
github.com/moby/term v0.5.2 github.com/moby/term v0.5.2
github.com/opencontainers/image-spec v1.1.0 github.com/opencontainers/image-spec v1.1.1
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
github.com/pkg/errors v0.9.1 github.com/rubenv/sql-migrate v1.8.0
github.com/rubenv/sql-migrate v1.7.1 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.6 github.com/spf13/pflag v1.0.6
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/xeipuuv/gojsonschema v1.2.0 golang.org/x/crypto v0.39.0
golang.org/x/crypto v0.33.0 golang.org/x/term v0.32.0
golang.org/x/term v0.29.0 golang.org/x/text v0.26.0
golang.org/x/text v0.22.0 gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.32.2 k8s.io/api v0.33.2
k8s.io/apiextensions-apiserver v0.32.2 k8s.io/apiextensions-apiserver v0.33.2
k8s.io/apimachinery v0.32.2 k8s.io/apimachinery v0.33.2
k8s.io/apiserver v0.32.2 k8s.io/apiserver v0.33.2
k8s.io/cli-runtime v0.32.2 k8s.io/cli-runtime v0.33.2
k8s.io/client-go v0.32.2 k8s.io/client-go v0.33.2
k8s.io/klog/v2 v2.130.1 k8s.io/klog/v2 v2.130.1
k8s.io/kubectl v0.32.2 k8s.io/kubectl v0.33.2
oras.land/oras-go/v2 v2.5.0 oras.land/oras-go/v2 v2.6.0
sigs.k8s.io/yaml v1.4.0 sigs.k8s.io/controller-runtime v0.21.0
sigs.k8s.io/yaml v1.5.0
) )
require ( require (
@ -59,9 +59,6 @@ require (
github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // 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/containerd/errdefs v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/coreos/go-systemd/v22 v22.5.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
@ -70,43 +67,40 @@ require (
github.com/docker/docker-credential-helpers v0.8.2 // indirect github.com/docker/docker-credential-helpers v0.8.2 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-metrics v0.0.1 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emicklei/go-restful/v3 v3.12.1 // 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/fatih/color v1.13.0 // indirect github.com/fatih/color v1.13.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect github.com/go-errors/errors v1.5.1 // indirect
github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect
github.com/google/btree v1.0.1 // indirect github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
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/gorilla/websocket v1.5.0 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // 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
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.17.11 // indirect github.com/klauspost/compress v1.18.0 // 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
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect
@ -119,66 +113,69 @@ require (
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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/onsi/gomega v1.37.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // 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.20.5 // indirect github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.60.1 // indirect github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/procfs v0.15.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.1.0 // 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.3 // indirect
github.com/spf13/cast v1.7.0 // indirect github.com/spf13/cast v1.7.0 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xlab/treeprint v1.2.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 // indirect go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 // indirect
go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 // indirect go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
go.opentelemetry.io/otel v1.32.0 // indirect go.opentelemetry.io/otel v1.34.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 // indirect
go.opentelemetry.io/otel/exporters/prometheus v0.54.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.54.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 // indirect
go.opentelemetry.io/otel/log v0.8.0 // indirect go.opentelemetry.io/otel/log v0.8.0 // indirect
go.opentelemetry.io/otel/metric v1.32.0 // indirect go.opentelemetry.io/otel/metric v1.34.0 // indirect
go.opentelemetry.io/otel/sdk v1.32.0 // indirect go.opentelemetry.io/otel/sdk v1.33.0 // indirect
go.opentelemetry.io/otel/sdk/log v0.8.0 // indirect go.opentelemetry.io/otel/sdk/log v0.8.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect
go.opentelemetry.io/otel/trace v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.34.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.opentelemetry.io/proto/otlp v1.4.0 // indirect
golang.org/x/mod v0.21.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/net v0.33.0 // indirect go.yaml.in/yaml/v3 v3.0.3 // indirect
golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/mod v0.25.0 // indirect
golang.org/x/sync v0.11.0 // indirect golang.org/x/net v0.40.0 // indirect
golang.org/x/sys v0.30.0 // indirect golang.org/x/oauth2 v0.29.0 // indirect
golang.org/x/time v0.7.0 // indirect golang.org/x/sync v0.15.0 // indirect
golang.org/x/tools v0.26.0 // indirect golang.org/x/sys v0.33.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect golang.org/x/time v0.11.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect golang.org/x/tools v0.33.0 // indirect
google.golang.org/grpc v1.68.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect
google.golang.org/protobuf v1.35.2 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect
google.golang.org/grpc v1.68.1 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.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
gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/component-base v0.33.2 // indirect
k8s.io/component-base v0.32.2 // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect sigs.k8s.io/kustomize/api v0.19.0 // indirect
sigs.k8s.io/kustomize/api v0.18.0 // indirect sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect
sigs.k8s.io/kustomize/kyaml v0.18.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
) )

304
go.sum

@ -6,16 +6,16 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
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.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/semver/v3 v3.4.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=
@ -37,10 +37,11 @@ github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2y
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70=
github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
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.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.9.5/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@ -48,19 +49,10 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk=
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/containerd/containerd v1.7.25 h1:khEQOAXOEJalRO228yzVsuASLH42vT7DIo9Ss+9SMFQ=
github.com/containerd/containerd v1.7.25/go.mod h1:tWfHzVI0azhw4CT2vaIjsb2CoV4LJ9PrMPaULAr21Ok=
github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4=
github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
@ -71,34 +63,38 @@ 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-rc.3 h1:JRJso9IVLoooKX76oWR+DWCCdZlK5m4nRtDWvzB1ITg= github.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN6UX90KJc4HjyM=
github.com/distribution/distribution/v3 v3.0.0-rc.3/go.mod h1:offoOgrnYs+CFwis8nE0hyzYZqRCZj5EFc5kgfszwiE= github.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU=
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/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/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/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 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=
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
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.36.0-flux.13 h1:2X5yjz/rk9mg7+bMFBDZKGKzeZpAmY2s6iwbNZz7OzM=
github.com/fluxcd/cli-utils v0.36.0-flux.13/go.mod h1:b2iSoIeDTtjfCB0IKtGgqlhhvWa1oux3e90CjOf81oA=
github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI=
github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/foxcpp/go-mockdns v1.1.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=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@ -109,12 +105,12 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
@ -135,19 +131,17 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@ -156,19 +150,14 @@ github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyE
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1 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/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
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/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/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=
@ -189,15 +178,12 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
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.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
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.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
@ -210,8 +196,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
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.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
@ -253,14 +239,14 @@ 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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
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.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
@ -276,16 +262,16 @@ github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjz
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
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=
@ -296,14 +282,16 @@ github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJu
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=
github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ=
github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY= github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rubenv/sql-migrate v1.7.1 h1:f/o0WgfO/GqNuVg+6801K/KW3WdDSupzSjDYODmiUq4= github.com/rubenv/sql-migrate v1.8.0 h1:dXnYiJk9k3wetp7GfQbKJcPHjVJL6YK19tKj8t2Ns0o=
github.com/rubenv/sql-migrate v1.7.1/go.mod h1:Ob2Psprc0/3ggbM6wCzyYVFFuc6FyZrb2AS+ezLDFb4= github.com/rubenv/sql-migrate v1.8.0/go.mod h1:F2bGFBwCU+pnmbtNYDeKvSuvL6lBVtXDXUUv5t+u1qw=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
@ -319,41 +307,31 @@ github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
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.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
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=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w= go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w=
go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk= go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk=
go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4= go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4=
go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g= go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8=
@ -362,10 +340,10 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7Z
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI=
go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU= go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU=
@ -378,20 +356,30 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsu
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s=
go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk=
go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8=
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM=
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM=
go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs=
go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo=
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
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/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -400,16 +388,16 @@ 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.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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=
@ -423,10 +411,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.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
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=
@ -437,8 +425,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-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=
@ -460,8 +448,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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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=
@ -469,8 +457,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.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
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=
@ -478,10 +466,10 @@ 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.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
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.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@ -490,20 +478,20 @@ 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.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
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=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q=
google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0=
google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw=
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
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=
@ -518,37 +506,43 @@ 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.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw= k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY=
k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y= k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs=
k8s.io/apiextensions-apiserver v0.32.2 h1:2YMk285jWMk2188V2AERy5yDwBYrjgWYggscghPCvV4= k8s.io/apiextensions-apiserver v0.33.2 h1:6gnkIbngnaUflR3XwE1mCefN3YS8yTD631JXQhsU6M8=
k8s.io/apiextensions-apiserver v0.32.2/go.mod h1:GPwf8sph7YlJT3H6aKUWtd0E+oyShk/YHWQHf/OOgCA= k8s.io/apiextensions-apiserver v0.33.2/go.mod h1:IvVanieYsEHJImTKXGP6XCOjTwv2LUMos0YWc9O+QP8=
k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ= k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=
k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/apiserver v0.32.2 h1:WzyxAu4mvLkQxwD9hGa4ZfExo3yZZaYzoYvvVDlM6vw= k8s.io/apiserver v0.33.2 h1:KGTRbxn2wJagJowo29kKBp4TchpO1DRO3g+dB/KOJN4=
k8s.io/apiserver v0.32.2/go.mod h1:PEwREHiHNU2oFdte7BjzA1ZyjWjuckORLIK/wLV5goM= k8s.io/apiserver v0.33.2/go.mod h1:9qday04wEAMLPWWo9AwqCZSiIn3OYSZacDyu/AcoM/M=
k8s.io/cli-runtime v0.32.2 h1:aKQR4foh9qeyckKRkNXUccP9moxzffyndZAvr+IXMks= k8s.io/cli-runtime v0.33.2 h1:koNYQKSDdq5AExa/RDudXMhhtFasEg48KLS2KSAU74Y=
k8s.io/cli-runtime v0.32.2/go.mod h1:a/JpeMztz3xDa7GCyyShcwe55p8pbcCVQxvqZnIwXN8= k8s.io/cli-runtime v0.33.2/go.mod h1:gnhsAWpovqf1Zj5YRRBBU7PFsRc6NkEkwYNQE+mXL88=
k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA= k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E=
k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94= k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo=
k8s.io/component-base v0.32.2 h1:1aUL5Vdmu7qNo4ZsE+569PV5zFatM9hl+lb3dEea2zU= k8s.io/component-base v0.33.2 h1:sCCsn9s/dG3ZrQTX/Us0/Sx2R0G5kwa0wbZFYoVp/+0=
k8s.io/component-base v0.32.2/go.mod h1:PXJ61Vx9Lg+P5mS8TLd7bCIr+eMJRQTyXe8KvkrvJq0= k8s.io/component-base v0.33.2/go.mod h1:/41uw9wKzuelhN+u+/C59ixxf4tYQKW7p32ddkYNe2k=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
k8s.io/kubectl v0.32.2 h1:TAkag6+XfSBgkqK9I7ZvwtF0WVtUAvK8ZqTt+5zi1Us= k8s.io/kubectl v0.33.2 h1:7XKZ6DYCklu5MZQzJe+CkCjoGZwD1wWl7t/FxzhMz7Y=
k8s.io/kubectl v0.32.2/go.mod h1:+h/NQFSPxiDZYX/WZaWw9fwYezGLISP0ud8nQKg+3g8= k8s.io/kubectl v0.33.2/go.mod h1:8rC67FB8tVTYraovAGNi/idWIK90z2CHFNMmGJZJ3KI=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
oras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc=
oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM=
sigs.k8s.io/kustomize/api v0.18.0 h1:hTzp67k+3NEVInwz5BHyzc9rGxIauoXferXyjv5lWPo= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/kustomize/api v0.18.0/go.mod h1:f8isXnX+8b+SGLHQ6yO4JG1rdkZlvhaCf/uZbLVMb0U= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/kustomize/kyaml v0.18.1 h1:WvBo56Wzw3fjS+7vBjN6TeivvpbW9GmRaWZ9CIVmt4E= sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ=
sigs.k8s.io/kustomize/kyaml v0.18.1/go.mod h1:C3L2BFVU1jgcddNBE1TxuVLgS46TjObMwW5FT9FcjYo= sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=

@ -0,0 +1,87 @@
/*
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 logging
import (
"context"
"log/slog"
"os"
)
// DebugEnabledFunc is a function type that determines if debug logging is enabled
// We use a function because we want to check the setting at log time, not when the logger is created
type DebugEnabledFunc func() bool
// DebugCheckHandler checks settings.Debug at log time
type DebugCheckHandler struct {
handler slog.Handler
debugEnabled DebugEnabledFunc
}
// Enabled implements slog.Handler.Enabled
func (h *DebugCheckHandler) Enabled(_ context.Context, level slog.Level) bool {
if level == slog.LevelDebug {
return h.debugEnabled()
}
return true // Always log other levels
}
// Handle implements slog.Handler.Handle
func (h *DebugCheckHandler) Handle(ctx context.Context, r slog.Record) error {
return h.handler.Handle(ctx, r)
}
// WithAttrs implements slog.Handler.WithAttrs
func (h *DebugCheckHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return &DebugCheckHandler{
handler: h.handler.WithAttrs(attrs),
debugEnabled: h.debugEnabled,
}
}
// WithGroup implements slog.Handler.WithGroup
func (h *DebugCheckHandler) WithGroup(name string) slog.Handler {
return &DebugCheckHandler{
handler: h.handler.WithGroup(name),
debugEnabled: h.debugEnabled,
}
}
// NewLogger creates a new logger with dynamic debug checking
func NewLogger(debugEnabled DebugEnabledFunc) *slog.Logger {
// Create base handler that removes timestamps
baseHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
// Always use LevelDebug here to allow all messages through
// Our custom handler will do the filtering
Level: slog.LevelDebug,
ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr {
// Remove the time attribute
if a.Key == slog.TimeKey {
return slog.Attr{}
}
return a
},
})
// Wrap with our dynamic debug-checking handler
dynamicHandler := &DebugCheckHandler{
handler: baseHandler,
debugEnabled: debugEnabled,
}
return slog.New(dynamicHandler)
}

@ -29,9 +29,6 @@ type Client struct {
// The base URL for requests // The base URL for requests
BaseURL string BaseURL string
// The internal logger to use
Log func(string, ...interface{})
} }
// New creates a new client // New creates a new client
@ -44,12 +41,9 @@ func New(u string) (*Client, error) {
return &Client{ return &Client{
BaseURL: u, BaseURL: u,
Log: nopLogger,
}, nil }, nil
} }
var nopLogger = func(_ string, _ ...interface{}) {}
// 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 {

@ -25,7 +25,7 @@ import (
"time" "time"
"helm.sh/helm/v4/internal/version" "helm.sh/helm/v4/internal/version"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
) )
// SearchPath is the url path to the search API in monocular. // SearchPath is the url path to the search API in monocular.
@ -129,7 +129,7 @@ func (c *Client) Search(term string) ([]SearchResult, error) {
} }
defer res.Body.Close() defer res.Body.Close()
if res.StatusCode != 200 { if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to fetch %s : %s", p.String(), res.Status) return nil, fmt.Errorf("failed to fetch %s : %s", p.String(), res.Status)
} }

@ -18,17 +18,18 @@ package resolver
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io/fs"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/pkg/errors"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/chart/loader" "helm.sh/helm/v4/pkg/chart/v2/loader"
"helm.sh/helm/v4/pkg/helmpath" "helm.sh/helm/v4/pkg/helmpath"
"helm.sh/helm/v4/pkg/provenance" "helm.sh/helm/v4/pkg/provenance"
"helm.sh/helm/v4/pkg/registry" "helm.sh/helm/v4/pkg/registry"
@ -66,7 +67,7 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
if d.Version != "" { if d.Version != "" {
constraint, err = semver.NewConstraint(d.Version) constraint, err = semver.NewConstraint(d.Version)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "dependency %q has an invalid version/constraint format", d.Name) return nil, fmt.Errorf("dependency %q has an invalid version/constraint format: %w", d.Name, err)
} }
} }
@ -132,12 +133,12 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
if !registry.IsOCI(d.Repository) { if !registry.IsOCI(d.Repository) {
repoIndex, err := repo.LoadIndexFile(filepath.Join(r.cachepath, helmpath.CacheIndexFile(repoName))) repoIndex, err := repo.LoadIndexFile(filepath.Join(r.cachepath, helmpath.CacheIndexFile(repoName)))
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "no cached repository for %s found. (try 'helm repo update')", repoName) return nil, fmt.Errorf("no cached repository for %s found. (try 'helm repo update'): %w", repoName, err)
} }
vs, ok = repoIndex.Entries[d.Name] vs, ok = repoIndex.Entries[d.Name]
if !ok { if !ok {
return nil, errors.Errorf("%s chart not found in repo %s", d.Name, d.Repository) return nil, fmt.Errorf("%s chart not found in repo %s", d.Name, d.Repository)
} }
found = false found = false
} else { } else {
@ -159,7 +160,7 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
ref := fmt.Sprintf("%s/%s", strings.TrimPrefix(d.Repository, fmt.Sprintf("%s://", registry.OCIScheme)), d.Name) ref := fmt.Sprintf("%s/%s", strings.TrimPrefix(d.Repository, fmt.Sprintf("%s://", registry.OCIScheme)), d.Name)
tags, err := r.registryClient.Tags(ref) tags, err := r.registryClient.Tags(ref)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "could not retrieve list of tags for repository %s", d.Repository) return nil, fmt.Errorf("could not retrieve list of tags for repository %s: %w", d.Repository, err)
} }
vs = make(repo.ChartVersions, len(tags)) vs = make(repo.ChartVersions, len(tags))
@ -200,7 +201,7 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
} }
} }
if len(missing) > 0 { if len(missing) > 0 {
return nil, errors.Errorf("can't get a valid version for %d subchart(s): %s. Make sure a matching chart version exists in the repo, or change the version constraint in Chart.yaml", len(missing), strings.Join(missing, ", ")) return nil, fmt.Errorf("can't get a valid version for %d subchart(s): %s. Make sure a matching chart version exists in the repo, or change the version constraint in Chart.yaml", len(missing), strings.Join(missing, ", "))
} }
digest, err := HashReq(reqs, locked) digest, err := HashReq(reqs, locked)
@ -260,8 +261,8 @@ func GetLocalPath(repo, chartpath string) (string, error) {
depPath = filepath.Join(chartpath, p) depPath = filepath.Join(chartpath, p)
} }
if _, err = os.Stat(depPath); os.IsNotExist(err) { if _, err = os.Stat(depPath); errors.Is(err, fs.ErrNotExist) {
return "", errors.Errorf("directory %s not found", depPath) return "", fmt.Errorf("directory %s not found", depPath)
} else if err != nil { } else if err != nil {
return "", err return "", err
} }

@ -19,7 +19,7 @@ import (
"runtime" "runtime"
"testing" "testing"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/registry" "helm.sh/helm/v4/pkg/registry"
) )

@ -0,0 +1,121 @@
/*
Copyright The Helm Authors.
This file was initially copied and modified from
https://github.com/fluxcd/kustomize-controller/blob/main/internal/statusreaders/job.go
Copyright 2022 The Flux 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 statusreaders
import (
"context"
"fmt"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine"
"github.com/fluxcd/cli-utils/pkg/kstatus/polling/event"
"github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders"
"github.com/fluxcd/cli-utils/pkg/kstatus/status"
"github.com/fluxcd/cli-utils/pkg/object"
)
type customJobStatusReader struct {
genericStatusReader engine.StatusReader
}
func NewCustomJobStatusReader(mapper meta.RESTMapper) engine.StatusReader {
genericStatusReader := statusreaders.NewGenericStatusReader(mapper, jobConditions)
return &customJobStatusReader{
genericStatusReader: genericStatusReader,
}
}
func (j *customJobStatusReader) Supports(gk schema.GroupKind) bool {
return gk == batchv1.SchemeGroupVersion.WithKind("Job").GroupKind()
}
func (j *customJobStatusReader) ReadStatus(ctx context.Context, reader engine.ClusterReader, resource object.ObjMetadata) (*event.ResourceStatus, error) {
return j.genericStatusReader.ReadStatus(ctx, reader, resource)
}
func (j *customJobStatusReader) ReadStatusForObject(ctx context.Context, reader engine.ClusterReader, resource *unstructured.Unstructured) (*event.ResourceStatus, error) {
return j.genericStatusReader.ReadStatusForObject(ctx, reader, resource)
}
// Ref: https://github.com/kubernetes-sigs/cli-utils/blob/v0.29.4/pkg/kstatus/status/core.go
// Modified to return Current status only when the Job has completed as opposed to when it's in progress.
func jobConditions(u *unstructured.Unstructured) (*status.Result, error) {
obj := u.UnstructuredContent()
parallelism := status.GetIntField(obj, ".spec.parallelism", 1)
completions := status.GetIntField(obj, ".spec.completions", parallelism)
succeeded := status.GetIntField(obj, ".status.succeeded", 0)
failed := status.GetIntField(obj, ".status.failed", 0)
// Conditions
// https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/job/utils.go#L24
objc, err := status.GetObjectWithConditions(obj)
if err != nil {
return nil, err
}
for _, c := range objc.Status.Conditions {
switch c.Type {
case "Complete":
if c.Status == corev1.ConditionTrue {
message := fmt.Sprintf("Job Completed. succeeded: %d/%d", succeeded, completions)
return &status.Result{
Status: status.CurrentStatus,
Message: message,
Conditions: []status.Condition{},
}, nil
}
case "Failed":
message := fmt.Sprintf("Job Failed. failed: %d/%d", failed, completions)
if c.Status == corev1.ConditionTrue {
return &status.Result{
Status: status.FailedStatus,
Message: message,
Conditions: []status.Condition{
{
Type: status.ConditionStalled,
Status: corev1.ConditionTrue,
Reason: "JobFailed",
Message: message,
},
},
}, nil
}
}
}
message := "Job in progress"
return &status.Result{
Status: status.InProgressStatus,
Message: message,
Conditions: []status.Condition{
{
Type: status.ConditionReconciling,
Status: corev1.ConditionTrue,
Reason: "JobInProgress",
Message: message,
},
},
}, nil
}

@ -0,0 +1,116 @@
/*
Copyright The Helm Authors.
This file was initially copied and modified from
https://github.com/fluxcd/kustomize-controller/blob/main/internal/statusreaders/job_test.go
Copyright 2022 The Flux 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 statusreaders
import (
"testing"
"github.com/stretchr/testify/assert"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"github.com/fluxcd/cli-utils/pkg/kstatus/status"
)
func toUnstructured(t *testing.T, obj runtime.Object) (*unstructured.Unstructured, error) {
t.Helper()
// If the incoming object is already unstructured, perform a deep copy first
// otherwise DefaultUnstructuredConverter ends up returning the inner map without
// making a copy.
if _, ok := obj.(runtime.Unstructured); ok {
obj = obj.DeepCopyObject()
}
rawMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
return nil, err
}
return &unstructured.Unstructured{Object: rawMap}, nil
}
func TestJobConditions(t *testing.T) {
t.Parallel()
tests := []struct {
name string
job *batchv1.Job
expectedStatus status.Status
}{
{
name: "job without Complete condition returns InProgress status",
job: &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "job-no-condition",
},
Spec: batchv1.JobSpec{},
Status: batchv1.JobStatus{},
},
expectedStatus: status.InProgressStatus,
},
{
name: "job with Complete condition as True returns Current status",
job: &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "job-complete",
},
Spec: batchv1.JobSpec{},
Status: batchv1.JobStatus{
Conditions: []batchv1.JobCondition{
{
Type: batchv1.JobComplete,
Status: corev1.ConditionTrue,
},
},
},
},
expectedStatus: status.CurrentStatus,
},
{
name: "job with Failed condition as True returns Failed status",
job: &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "job-failed",
},
Spec: batchv1.JobSpec{},
Status: batchv1.JobStatus{
Conditions: []batchv1.JobCondition{
{
Type: batchv1.JobFailed,
Status: corev1.ConditionTrue,
},
},
},
},
expectedStatus: status.FailedStatus,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
us, err := toUnstructured(t, tc.job)
assert.NoError(t, err)
result, err := jobConditions(us)
assert.NoError(t, err)
assert.Equal(t, tc.expectedStatus, result.Status)
})
}
}

@ -0,0 +1,104 @@
/*
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 statusreaders
import (
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine"
"github.com/fluxcd/cli-utils/pkg/kstatus/polling/event"
"github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders"
"github.com/fluxcd/cli-utils/pkg/kstatus/status"
"github.com/fluxcd/cli-utils/pkg/object"
)
type customPodStatusReader struct {
genericStatusReader engine.StatusReader
}
func NewCustomPodStatusReader(mapper meta.RESTMapper) engine.StatusReader {
genericStatusReader := statusreaders.NewGenericStatusReader(mapper, podConditions)
return &customPodStatusReader{
genericStatusReader: genericStatusReader,
}
}
func (j *customPodStatusReader) Supports(gk schema.GroupKind) bool {
return gk == corev1.SchemeGroupVersion.WithKind("Pod").GroupKind()
}
func (j *customPodStatusReader) ReadStatus(ctx context.Context, reader engine.ClusterReader, resource object.ObjMetadata) (*event.ResourceStatus, error) {
return j.genericStatusReader.ReadStatus(ctx, reader, resource)
}
func (j *customPodStatusReader) ReadStatusForObject(ctx context.Context, reader engine.ClusterReader, resource *unstructured.Unstructured) (*event.ResourceStatus, error) {
return j.genericStatusReader.ReadStatusForObject(ctx, reader, resource)
}
func podConditions(u *unstructured.Unstructured) (*status.Result, error) {
obj := u.UnstructuredContent()
phase := status.GetStringField(obj, ".status.phase", "")
switch corev1.PodPhase(phase) {
case corev1.PodSucceeded:
message := fmt.Sprintf("pod %s succeeded", u.GetName())
return &status.Result{
Status: status.CurrentStatus,
Message: message,
Conditions: []status.Condition{
{
Type: status.ConditionStalled,
Status: corev1.ConditionTrue,
Message: message,
},
},
}, nil
case corev1.PodFailed:
message := fmt.Sprintf("pod %s failed", u.GetName())
return &status.Result{
Status: status.FailedStatus,
Message: message,
Conditions: []status.Condition{
{
Type: status.ConditionStalled,
Status: corev1.ConditionTrue,
Reason: "PodFailed",
Message: message,
},
},
}, nil
}
message := "Pod in progress"
return &status.Result{
Status: status.InProgressStatus,
Message: message,
Conditions: []status.Condition{
{
Type: status.ConditionReconciling,
Status: corev1.ConditionTrue,
Reason: "PodInProgress",
Message: message,
},
},
}, nil
}

@ -0,0 +1,111 @@
/*
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 statusreaders
import (
"testing"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fluxcd/cli-utils/pkg/kstatus/status"
)
func TestPodConditions(t *testing.T) {
tests := []struct {
name string
pod *v1.Pod
expectedStatus status.Status
}{
{
name: "pod without status returns in progress",
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "pod-no-status"},
Spec: v1.PodSpec{},
Status: v1.PodStatus{},
},
expectedStatus: status.InProgressStatus,
},
{
name: "pod succeeded returns current status",
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "pod-succeeded"},
Spec: v1.PodSpec{},
Status: v1.PodStatus{
Phase: v1.PodSucceeded,
},
},
expectedStatus: status.CurrentStatus,
},
{
name: "pod failed returns failed status",
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "pod-failed"},
Spec: v1.PodSpec{},
Status: v1.PodStatus{
Phase: v1.PodFailed,
},
},
expectedStatus: status.FailedStatus,
},
{
name: "pod pending returns in progress status",
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "pod-pending"},
Spec: v1.PodSpec{},
Status: v1.PodStatus{
Phase: v1.PodPending,
},
},
expectedStatus: status.InProgressStatus,
},
{
name: "pod running returns in progress status",
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "pod-running"},
Spec: v1.PodSpec{},
Status: v1.PodStatus{
Phase: v1.PodRunning,
},
},
expectedStatus: status.InProgressStatus,
},
{
name: "pod with unknown phase returns in progress status",
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "pod-unknown"},
Spec: v1.PodSpec{},
Status: v1.PodStatus{
Phase: v1.PodUnknown,
},
},
expectedStatus: status.InProgressStatus,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
us, err := toUnstructured(t, tc.pod)
assert.NoError(t, err)
result, err := podConditions(us)
assert.NoError(t, err)
assert.Equal(t, tc.expectedStatus, result.Status)
})
}
}

@ -21,12 +21,11 @@ limitations under the License.
package sympath package sympath
import ( import (
"log" "fmt"
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
"github.com/pkg/errors"
) )
// Walk walks the file tree rooted at root, calling walkFn for each file or directory // Walk walks the file tree rooted at root, calling walkFn for each file or directory
@ -69,10 +68,10 @@ func symwalk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
if IsSymlink(info) { if IsSymlink(info) {
resolved, err := filepath.EvalSymlinks(path) resolved, err := filepath.EvalSymlinks(path)
if err != nil { if err != nil {
return errors.Wrapf(err, "error evaluating symlink %s", path) return fmt.Errorf("error evaluating symlink %s: %w", path, err)
} }
//This log message is to highlight a symlink that is being used within a chart, symlinks can be used for nefarious reasons. //This log message is to highlight a symlink that is being used within a chart, symlinks can be used for nefarious reasons.
log.Printf("found symbolic link in path: %s resolves to %s. Contents of linked file included and used", path, resolved) slog.Info("found symbolic link in path. Contents of linked file included and used", "path", path, "resolved", resolved)
if info, err = os.Lstat(resolved); err != nil { if info, err = os.Lstat(resolved); err != nil {
return err return err
} }

@ -76,6 +76,7 @@ func walkTree(n *Node, path string, f func(path string, n *Node)) {
} }
func makeTree(t *testing.T) { func makeTree(t *testing.T) {
t.Helper()
walkTree(tree, tree.name, func(path string, n *Node) { walkTree(tree, tree.name, func(path string, n *Node) {
if n.entries == nil { if n.entries == nil {
if n.symLinkedTo != "" { if n.symLinkedTo != "" {
@ -99,6 +100,7 @@ func makeTree(t *testing.T) {
} }
func checkMarks(t *testing.T, report bool) { func checkMarks(t *testing.T, report bool) {
t.Helper()
walkTree(tree, tree.name, func(path string, n *Node) { walkTree(tree, tree.name, func(path string, n *Node) {
if n.marks != n.expectedMarks && report { if n.marks != n.expectedMarks && report {
t.Errorf("node %s mark = %d; expected %d", path, n.marks, n.expectedMarks) t.Errorf("node %s mark = %d; expected %d", path, n.marks, n.expectedMarks)

@ -29,12 +29,12 @@ import (
func HelmHome(t *testing.T) { func HelmHome(t *testing.T) {
t.Helper() t.Helper()
base := t.TempDir() base := t.TempDir()
os.Setenv(xdg.CacheHomeEnvVar, base) t.Setenv(xdg.CacheHomeEnvVar, base)
os.Setenv(xdg.ConfigHomeEnvVar, base) t.Setenv(xdg.ConfigHomeEnvVar, base)
os.Setenv(xdg.DataHomeEnvVar, base) t.Setenv(xdg.DataHomeEnvVar, base)
os.Setenv(helmpath.CacheHomeEnvVar, "") t.Setenv(helmpath.CacheHomeEnvVar, "")
os.Setenv(helmpath.ConfigHomeEnvVar, "") t.Setenv(helmpath.ConfigHomeEnvVar, "")
os.Setenv(helmpath.DataHomeEnvVar, "") t.Setenv(helmpath.DataHomeEnvVar, "")
} }
// TempFile ensures a temp file for unit testing purposes. // TempFile ensures a temp file for unit testing purposes.
@ -46,9 +46,10 @@ func HelmHome(t *testing.T) {
// tempdir := TempFile(t, "foo", []byte("bar")) // tempdir := TempFile(t, "foo", []byte("bar"))
// filename := filepath.Join(tempdir, "foo") // filename := filepath.Join(tempdir, "foo")
func TempFile(t *testing.T, name string, data []byte) string { func TempFile(t *testing.T, name string, data []byte) string {
t.Helper()
path := t.TempDir() path := t.TempDir()
filename := filepath.Join(path, name) filename := filepath.Join(path, name)
if err := os.WriteFile(filename, data, 0755); err != nil { if err := os.WriteFile(filename, data, 0o755); err != nil {
t.Fatal(err) t.Fatal(err)
} }
return path return path

@ -19,10 +19,9 @@ package test
import ( import (
"bytes" "bytes"
"flag" "flag"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"github.com/pkg/errors"
) )
// UpdateGolden writes out the golden files with the latest values, rather than failing the test. // UpdateGolden writes out the golden files with the latest values, rather than failing the test.
@ -75,11 +74,11 @@ func compare(actual []byte, filename string) error {
expected, err := os.ReadFile(filename) expected, err := os.ReadFile(filename)
if err != nil { if err != nil {
return errors.Wrapf(err, "unable to read testdata %s", filename) return fmt.Errorf("unable to read testdata %s: %w", filename, err)
} }
expected = normalize(expected) expected = normalize(expected)
if !bytes.Equal(expected, actual) { if !bytes.Equal(expected, actual) {
return errors.Errorf("does not match golden file %s\n\nWANT:\n'%s'\n\nGOT:\n'%s'", filename, expected, actual) return fmt.Errorf("does not match golden file %s\n\nWANT:\n'%s'\n\nGOT:\n'%s'", filename, expected, actual)
} }
return nil return nil
} }
@ -92,5 +91,5 @@ func update(filename string, in []byte) error {
} }
func normalize(in []byte) []byte { func normalize(in []byte) []byte {
return bytes.Replace(in, []byte("\r\n"), []byte("\n"), -1) return bytes.ReplaceAll(in, []byte("\r\n"), []byte("\n"))
} }

@ -32,13 +32,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package fs package fs
import ( import (
"errors"
"fmt"
"io" "io"
"io/fs"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"syscall" "syscall"
"github.com/pkg/errors"
) )
// fs contains a copy of a few functions from dep tool code to avoid a dependency on golang/dep. // fs contains a copy of a few functions from dep tool code to avoid a dependency on golang/dep.
@ -51,7 +52,7 @@ import (
func RenameWithFallback(src, dst string) error { func RenameWithFallback(src, dst string) error {
_, err := os.Stat(src) _, err := os.Stat(src)
if err != nil { if err != nil {
return errors.Wrapf(err, "cannot stat %s", src) return fmt.Errorf("cannot stat %s: %w", src, err)
} }
err = os.Rename(src, dst) err = os.Rename(src, dst)
@ -69,20 +70,24 @@ func renameByCopy(src, dst string) error {
if dir, _ := IsDir(src); dir { if dir, _ := IsDir(src); dir {
cerr = CopyDir(src, dst) cerr = CopyDir(src, dst)
if cerr != nil { if cerr != nil {
cerr = errors.Wrap(cerr, "copying directory failed") cerr = fmt.Errorf("copying directory failed: %w", cerr)
} }
} else { } else {
cerr = copyFile(src, dst) cerr = copyFile(src, dst)
if cerr != nil { if cerr != nil {
cerr = errors.Wrap(cerr, "copying file failed") cerr = fmt.Errorf("copying file failed: %w", cerr)
} }
} }
if cerr != nil { if cerr != nil {
return errors.Wrapf(cerr, "rename fallback failed: cannot rename %s to %s", src, dst) return fmt.Errorf("rename fallback failed: cannot rename %s to %s: %w", src, dst, cerr)
}
if err := os.RemoveAll(src); err != nil {
return fmt.Errorf("cannot delete %s: %w", src, err)
} }
return errors.Wrapf(os.RemoveAll(src), "cannot delete %s", src) return nil
} }
var ( var (
@ -107,7 +112,7 @@ func CopyDir(src, dst string) error {
} }
_, err = os.Stat(dst) _, err = os.Stat(dst)
if err != nil && !os.IsNotExist(err) { if err != nil && !errors.Is(err, fs.ErrNotExist) {
return err return err
} }
if err == nil { if err == nil {
@ -115,12 +120,12 @@ func CopyDir(src, dst string) error {
} }
if err = os.MkdirAll(dst, fi.Mode()); err != nil { if err = os.MkdirAll(dst, fi.Mode()); err != nil {
return errors.Wrapf(err, "cannot mkdir %s", dst) return fmt.Errorf("cannot mkdir %s: %w", dst, err)
} }
entries, err := os.ReadDir(src) entries, err := os.ReadDir(src)
if err != nil { if err != nil {
return errors.Wrapf(err, "cannot read directory %s", dst) return fmt.Errorf("cannot read directory %s: %w", dst, err)
} }
for _, entry := range entries { for _, entry := range entries {
@ -129,13 +134,13 @@ func CopyDir(src, dst string) error {
if entry.IsDir() { if entry.IsDir() {
if err = CopyDir(srcPath, dstPath); err != nil { if err = CopyDir(srcPath, dstPath); err != nil {
return errors.Wrap(err, "copying directory failed") return fmt.Errorf("copying directory failed: %w", err)
} }
} else { } else {
// This will include symlinks, which is what we want when // This will include symlinks, which is what we want when
// copying things. // copying things.
if err = copyFile(srcPath, dstPath); err != nil { if err = copyFile(srcPath, dstPath); err != nil {
return errors.Wrap(err, "copying file failed") return fmt.Errorf("copying file failed: %w", err)
} }
} }
} }
@ -149,7 +154,7 @@ func CopyDir(src, dst string) error {
// of the source file. The file mode will be copied from the source. // of the source file. The file mode will be copied from the source.
func copyFile(src, dst string) (err error) { func copyFile(src, dst string) (err error) {
if sym, err := IsSymlink(src); err != nil { if sym, err := IsSymlink(src); err != nil {
return errors.Wrap(err, "symlink check failed") return fmt.Errorf("symlink check failed: %w", err)
} else if sym { } else if sym {
if err := cloneSymlink(src, dst); err != nil { if err := cloneSymlink(src, dst); err != nil {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
@ -172,28 +177,28 @@ func copyFile(src, dst string) (err error) {
in, err := os.Open(src) in, err := os.Open(src)
if err != nil { if err != nil {
return return err
} }
defer in.Close() defer in.Close()
out, err := os.Create(dst) out, err := os.Create(dst)
if err != nil { if err != nil {
return return err
} }
if _, err = io.Copy(out, in); err != nil { if _, err = io.Copy(out, in); err != nil {
out.Close() out.Close()
return return err
} }
// Check for write errors on Close // Check for write errors on Close
if err = out.Close(); err != nil { if err = out.Close(); err != nil {
return return err
} }
si, err := os.Stat(src) si, err := os.Stat(src)
if err != nil { if err != nil {
return return err
} }
// Temporary fix for Go < 1.9 // Temporary fix for Go < 1.9
@ -205,7 +210,7 @@ func copyFile(src, dst string) (err error) {
} }
err = os.Chmod(dst, si.Mode()) err = os.Chmod(dst, si.Mode())
return return err
} }
// cloneSymlink will create a new symlink that points to the resolved path of sl. // cloneSymlink will create a new symlink that points to the resolved path of sl.
@ -226,7 +231,7 @@ func IsDir(name string) (bool, error) {
return false, err return false, err
} }
if !fi.IsDir() { if !fi.IsDir() {
return false, errors.Errorf("%q is not a directory", name) return false, fmt.Errorf("%q is not a directory", name)
} }
return true, nil return true, nil
} }

@ -33,17 +33,11 @@ package fs
import ( import (
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sync"
"testing" "testing"
) )
var (
mu sync.Mutex
)
func TestRenameWithFallback(t *testing.T) { func TestRenameWithFallback(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
@ -360,19 +354,6 @@ func TestCopyFile(t *testing.T) {
} }
} }
func cleanUpDir(dir string) {
// NOTE(mattn): It seems that sometimes git.exe is not dead
// when cleanUpDir() is called. But we do not know any way to wait for it.
if runtime.GOOS == "windows" {
mu.Lock()
exec.Command(`taskkill`, `/F`, `/IM`, `git.exe`).Run()
mu.Unlock()
}
if dir != "" {
os.RemoveAll(dir)
}
}
func TestCopyFileSymlink(t *testing.T) { func TestCopyFileSymlink(t *testing.T) {
tempdir := t.TempDir() tempdir := t.TempDir()
@ -476,6 +457,7 @@ func TestCopyFileFail(t *testing.T) {
// files this function creates. It is the caller's responsibility to call // files this function creates. It is the caller's responsibility to call
// this function before the test is done running, whether there's an error or not. // this function before the test is done running, whether there's an error or not.
func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() { func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() {
t.Helper()
dir := t.TempDir() dir := t.TempDir()
subdir := filepath.Join(dir, "dir") subdir := filepath.Join(dir, "dir")

@ -34,10 +34,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package fs package fs
import ( import (
"fmt"
"os" "os"
"syscall" "syscall"
"github.com/pkg/errors"
) )
// renameFallback attempts to determine the appropriate fallback to failed rename // renameFallback attempts to determine the appropriate fallback to failed rename
@ -51,7 +50,7 @@ func renameFallback(err error, src, dst string) error {
if !ok { if !ok {
return err return err
} else if terr.Err != syscall.EXDEV { } else if terr.Err != syscall.EXDEV {
return errors.Wrapf(terr, "link error: cannot rename %s to %s", src, dst) return fmt.Errorf("link error: cannot rename %s to %s: %w", src, dst, terr)
} }
return renameByCopy(src, dst) return renameByCopy(src, dst)

@ -34,10 +34,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package fs package fs
import ( import (
"fmt"
"os" "os"
"syscall" "syscall"
"github.com/pkg/errors"
) )
// renameFallback attempts to determine the appropriate fallback to failed rename // renameFallback attempts to determine the appropriate fallback to failed rename
@ -61,7 +60,7 @@ func renameFallback(err error, src, dst string) error {
// 0x11 (ERROR_NOT_SAME_DEVICE) is the windows error. // 0x11 (ERROR_NOT_SAME_DEVICE) is the windows error.
// See https://msdn.microsoft.com/en-us/library/cc231199.aspx // See https://msdn.microsoft.com/en-us/library/cc231199.aspx
if ok && noerr != 0x11 { if ok && noerr != 0x11 {
return errors.Wrapf(terr, "link error: cannot rename %s to %s", src, dst) return fmt.Errorf("link error: cannot rename %s to %s: %w", src, dst, terr)
} }
} }

@ -30,8 +30,9 @@ const (
) )
func testfile(t *testing.T, file string) (path string) { func testfile(t *testing.T, file string) (path string) {
var err error t.Helper()
if path, err = filepath.Abs(filepath.Join(tlsTestDir, file)); err != nil { path, err := filepath.Abs(filepath.Join(tlsTestDir, file))
if err != nil {
t.Fatalf("error getting absolute path to test file %q: %v", file, err) t.Fatalf("error getting absolute path to test file %q: %v", file, err)
} }
return path return path

@ -18,29 +18,31 @@ package action
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"io" "io"
"log/slog"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"sync"
"text/template"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/discovery" "k8s.io/client-go/discovery"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
chartutil "helm.sh/helm/v4/pkg/chart/util" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/engine" "helm.sh/helm/v4/pkg/engine"
"helm.sh/helm/v4/pkg/kube" "helm.sh/helm/v4/pkg/kube"
"helm.sh/helm/v4/pkg/postrender" "helm.sh/helm/v4/pkg/postrender"
"helm.sh/helm/v4/pkg/registry" "helm.sh/helm/v4/pkg/registry"
"helm.sh/helm/v4/pkg/release" releaseutil "helm.sh/helm/v4/pkg/release/util"
"helm.sh/helm/v4/pkg/releaseutil" release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/storage" "helm.sh/helm/v4/pkg/storage"
"helm.sh/helm/v4/pkg/storage/driver" "helm.sh/helm/v4/pkg/storage/driver"
"helm.sh/helm/v4/pkg/time" "helm.sh/helm/v4/pkg/time"
@ -63,21 +65,6 @@ var (
errPending = errors.New("another operation (install/upgrade/rollback) is in progress") errPending = errors.New("another operation (install/upgrade/rollback) is in progress")
) )
// ValidName is a regular expression for resource names.
//
// DEPRECATED: This will be removed in Helm 4, and is no longer used here. See
// pkg/lint/rules.validateMetadataNameFunc for the replacement.
//
// According to the Kubernetes help text, the regular expression it uses is:
//
// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*
//
// This follows the above regular expression (but requires a full string match, not partial).
//
// The Kubernetes documentation is here, though it is not entirely correct:
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
var ValidName = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`)
// 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.
@ -95,10 +82,13 @@ type Configuration struct {
// Capabilities describes the capabilities of the Kubernetes cluster. // Capabilities describes the capabilities of the Kubernetes cluster.
Capabilities *chartutil.Capabilities Capabilities *chartutil.Capabilities
Log func(string, ...interface{}) // CustomTemplateFuncs is defined by users to provide custom template funcs
CustomTemplateFuncs template.FuncMap
// HookOutputFunc called with container name and returns and expects writer that will receive the log output. // HookOutputFunc called with container name and returns and expects writer that will receive the log output.
HookOutputFunc func(namespace, pod, container string) io.Writer HookOutputFunc func(namespace, pod, container string) io.Writer
mutex sync.Mutex
} }
// renderResources renders the templates in a chart // renderResources renders the templates in a chart
@ -118,7 +108,7 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu
if ch.Metadata.KubeVersion != "" { if ch.Metadata.KubeVersion != "" {
if !chartutil.IsCompatibleRange(ch.Metadata.KubeVersion, caps.KubeVersion.String()) { if !chartutil.IsCompatibleRange(ch.Metadata.KubeVersion, caps.KubeVersion.String()) {
return hs, b, "", errors.Errorf("chart requires kubeVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.String()) return hs, b, "", fmt.Errorf("chart requires kubeVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.String())
} }
} }
@ -135,10 +125,14 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu
} }
e := engine.New(restConfig) e := engine.New(restConfig)
e.EnableDNS = enableDNS e.EnableDNS = enableDNS
e.CustomTemplateFuncs = cfg.CustomTemplateFuncs
files, err2 = e.Render(ch, values) files, err2 = e.Render(ch, values)
} else { } else {
var e engine.Engine var e engine.Engine
e.EnableDNS = enableDNS e.EnableDNS = enableDNS
e.CustomTemplateFuncs = cfg.CustomTemplateFuncs
files, err2 = e.Render(ch, values) files, err2 = e.Render(ch, values)
} }
@ -229,7 +223,7 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu
if pr != nil { if pr != nil {
b, err = pr.Run(b) b, err = pr.Run(b)
if err != nil { if err != nil {
return hs, b, notes, errors.Wrap(err, "error while running post render on files") return hs, b, notes, fmt.Errorf("error while running post render on files: %w", err)
} }
} }
@ -243,9 +237,6 @@ type RESTClientGetter interface {
ToRESTMapper() (meta.RESTMapper, error) ToRESTMapper() (meta.RESTMapper, error)
} }
// DebugLog sets the logger that writes debug strings
type DebugLog func(format string, v ...interface{})
// capabilities builds a Capabilities from discovery information. // capabilities builds a Capabilities from discovery information.
func (cfg *Configuration) getCapabilities() (*chartutil.Capabilities, error) { func (cfg *Configuration) getCapabilities() (*chartutil.Capabilities, error) {
if cfg.Capabilities != nil { if cfg.Capabilities != nil {
@ -253,13 +244,13 @@ func (cfg *Configuration) getCapabilities() (*chartutil.Capabilities, error) {
} }
dc, err := cfg.RESTClientGetter.ToDiscoveryClient() dc, err := cfg.RESTClientGetter.ToDiscoveryClient()
if err != nil { if err != nil {
return nil, errors.Wrap(err, "could not get Kubernetes discovery client") return nil, fmt.Errorf("could not get Kubernetes discovery client: %w", err)
} }
// force a discovery cache invalidation to always fetch the latest server version/capabilities. // force a discovery cache invalidation to always fetch the latest server version/capabilities.
dc.Invalidate() dc.Invalidate()
kubeVersion, err := dc.ServerVersion() kubeVersion, err := dc.ServerVersion()
if err != nil { if err != nil {
return nil, errors.Wrap(err, "could not get server version from Kubernetes") return nil, fmt.Errorf("could not get server version from Kubernetes: %w", err)
} }
// Issue #6361: // Issue #6361:
// Client-Go emits an error when an API service is registered but unimplemented. // Client-Go emits an error when an API service is registered but unimplemented.
@ -269,10 +260,10 @@ func (cfg *Configuration) getCapabilities() (*chartutil.Capabilities, error) {
apiVersions, err := GetVersionSet(dc) apiVersions, err := GetVersionSet(dc)
if err != nil { if err != nil {
if discovery.IsGroupDiscoveryFailedError(err) { if discovery.IsGroupDiscoveryFailedError(err) {
cfg.Log("WARNING: The Kubernetes server has an orphaned API service. Server reports: %s", err) slog.Warn("the kubernetes server has an orphaned API service", slog.Any("error", err))
cfg.Log("WARNING: To fix this, kubectl delete apiservice <service-name>") slog.Warn("to fix this, kubectl delete apiservice <service-name>")
} else { } else {
return nil, errors.Wrap(err, "could not get apiVersions from Kubernetes") return nil, fmt.Errorf("could not get apiVersions from Kubernetes: %w", err)
} }
} }
@ -292,7 +283,7 @@ func (cfg *Configuration) getCapabilities() (*chartutil.Capabilities, error) {
func (cfg *Configuration) KubernetesClientSet() (kubernetes.Interface, error) { func (cfg *Configuration) KubernetesClientSet() (kubernetes.Interface, error) {
conf, err := cfg.RESTClientGetter.ToRESTConfig() conf, err := cfg.RESTClientGetter.ToRESTConfig()
if err != nil { if err != nil {
return nil, errors.Wrap(err, "unable to generate config for kubernetes client") return nil, fmt.Errorf("unable to generate config for kubernetes client: %w", err)
} }
return kubernetes.NewForConfig(conf) return kubernetes.NewForConfig(conf)
@ -308,7 +299,7 @@ func (cfg *Configuration) Now() time.Time {
func (cfg *Configuration) releaseContent(name string, version int) (*release.Release, error) { func (cfg *Configuration) releaseContent(name string, version int) (*release.Release, error) {
if err := chartutil.ValidateReleaseName(name); err != nil { if err := chartutil.ValidateReleaseName(name); err != nil {
return nil, errors.Errorf("releaseContent: Release name is invalid: %s", name) return nil, fmt.Errorf("releaseContent: Release name is invalid: %s", name)
} }
if version <= 0 { if version <= 0 {
@ -322,7 +313,7 @@ func (cfg *Configuration) releaseContent(name string, version int) (*release.Rel
func GetVersionSet(client discovery.ServerResourcesInterface) (chartutil.VersionSet, error) { func GetVersionSet(client discovery.ServerResourcesInterface) (chartutil.VersionSet, error) {
groups, resources, err := client.ServerGroupsAndResources() groups, resources, err := client.ServerGroupsAndResources()
if err != nil && !discovery.IsGroupDiscoveryFailedError(err) { if err != nil && !discovery.IsGroupDiscoveryFailedError(err) {
return chartutil.DefaultVersionSet, errors.Wrap(err, "could not get apiVersions from Kubernetes") return chartutil.DefaultVersionSet, fmt.Errorf("could not get apiVersions from Kubernetes: %w", err)
} }
// FIXME: The Kubernetes test fixture for cli appears to always return nil // FIXME: The Kubernetes test fixture for cli appears to always return nil
@ -369,14 +360,13 @@ func GetVersionSet(client discovery.ServerResourcesInterface) (chartutil.Version
// recordRelease with an update operation in case reuse has been set. // recordRelease with an update operation in case reuse has been set.
func (cfg *Configuration) recordRelease(r *release.Release) { func (cfg *Configuration) recordRelease(r *release.Release) {
if err := cfg.Releases.Update(r); err != nil { if err := cfg.Releases.Update(r); err != nil {
cfg.Log("warning: Failed to update release %s: %s", r.Name, err) slog.Warn("failed to update release", "name", r.Name, "revision", r.Version, slog.Any("error", err))
} }
} }
// Init initializes the action configuration // Init initializes the action configuration
func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace, helmDriver string, log DebugLog) error { func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace, helmDriver string) error {
kc := kube.New(getter) kc := kube.New(getter)
kc.Log = log
lazyClient := &lazyClient{ lazyClient := &lazyClient{
namespace: namespace, namespace: namespace,
@ -387,11 +377,9 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp
switch helmDriver { switch helmDriver {
case "secret", "secrets", "": case "secret", "secrets", "":
d := driver.NewSecrets(newSecretClient(lazyClient)) d := driver.NewSecrets(newSecretClient(lazyClient))
d.Log = log
store = storage.Init(d) store = storage.Init(d)
case "configmap", "configmaps": case "configmap", "configmaps":
d := driver.NewConfigMaps(newConfigMapClient(lazyClient)) d := driver.NewConfigMaps(newConfigMapClient(lazyClient))
d.Log = log
store = storage.Init(d) store = storage.Init(d)
case "memory": case "memory":
var d *driver.Memory var d *driver.Memory
@ -411,21 +399,19 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp
case "sql": case "sql":
d, err := driver.NewSQL( d, err := driver.NewSQL(
os.Getenv("HELM_DRIVER_SQL_CONNECTION_STRING"), os.Getenv("HELM_DRIVER_SQL_CONNECTION_STRING"),
log,
namespace, namespace,
) )
if err != nil { if err != nil {
return errors.Wrap(err, "unable to instantiate SQL driver") return fmt.Errorf("unable to instantiate SQL driver: %w", err)
} }
store = storage.Init(d) store = storage.Init(d)
default: default:
return errors.Errorf("unknown driver %q", helmDriver) return fmt.Errorf("unknown driver %q", helmDriver)
} }
cfg.RESTClientGetter = getter cfg.RESTClientGetter = getter
cfg.KubeClient = kc cfg.KubeClient = kc
cfg.Releases = store cfg.Releases = store
cfg.Log = log
cfg.HookOutputFunc = func(_, _, _ string) io.Writer { return io.Discard } cfg.HookOutputFunc = func(_, _, _ string) io.Writer { return io.Discard }
return nil return nil

@ -19,25 +19,38 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"log/slog"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
fakeclientset "k8s.io/client-go/kubernetes/fake" fakeclientset "k8s.io/client-go/kubernetes/fake"
"helm.sh/helm/v4/pkg/chart" "helm.sh/helm/v4/internal/logging"
chartutil "helm.sh/helm/v4/pkg/chart/util" chart "helm.sh/helm/v4/pkg/chart/v2"
chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/kube"
kubefake "helm.sh/helm/v4/pkg/kube/fake" kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v4/pkg/registry" "helm.sh/helm/v4/pkg/registry"
"helm.sh/helm/v4/pkg/release" release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/storage" "helm.sh/helm/v4/pkg/storage"
"helm.sh/helm/v4/pkg/storage/driver" "helm.sh/helm/v4/pkg/storage/driver"
"helm.sh/helm/v4/pkg/time" "helm.sh/helm/v4/pkg/time"
) )
var verbose = flag.Bool("test.log", false, "enable test logging") var verbose = flag.Bool("test.log", false, "enable test logging (debug by default)")
func actionConfigFixture(t *testing.T) *Configuration { func actionConfigFixture(t *testing.T) *Configuration {
t.Helper() t.Helper()
return actionConfigFixtureWithDummyResources(t, nil)
}
func actionConfigFixtureWithDummyResources(t *testing.T, dummyResources kube.ResourceList) *Configuration {
t.Helper()
logger := logging.NewLogger(func() bool {
return *verbose
})
slog.SetDefault(logger)
registryClient, err := registry.NewClient() registryClient, err := registry.NewClient()
if err != nil { if err != nil {
@ -46,15 +59,9 @@ func actionConfigFixture(t *testing.T) *Configuration {
return &Configuration{ return &Configuration{
Releases: storage.Init(driver.NewMemory()), Releases: storage.Init(driver.NewMemory()),
KubeClient: &kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}}, KubeClient: &kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: dummyResources},
Capabilities: chartutil.DefaultCapabilities, Capabilities: chartutil.DefaultCapabilities,
RegistryClient: registryClient, RegistryClient: registryClient,
Log: func(format string, v ...interface{}) {
t.Helper()
if *verbose {
t.Logf(format, v...)
}
},
} }
} }
@ -334,7 +341,7 @@ func TestConfiguration_Init(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
cfg := &Configuration{} cfg := &Configuration{}
actualErr := cfg.Init(nil, "default", tt.helmDriver, nil) actualErr := cfg.Init(nil, "default", tt.helmDriver)
if tt.expectErr { if tt.expectErr {
assert.Error(t, actualErr) assert.Error(t, actualErr)
assert.Contains(t, actualErr.Error(), tt.errMsg) assert.Contains(t, actualErr.Error(), tt.errMsg)
@ -347,7 +354,7 @@ func TestConfiguration_Init(t *testing.T) {
} }
func TestGetVersionSet(t *testing.T) { func TestGetVersionSet(t *testing.T) {
client := fakeclientset.NewSimpleClientset() client := fakeclientset.NewClientset()
vs, err := GetVersionSet(client.Discovery()) vs, err := GetVersionSet(client.Discovery())
if err != nil { if err != nil {

@ -26,8 +26,8 @@ import (
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/gosuri/uitable" "github.com/gosuri/uitable"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/chart/loader" "helm.sh/helm/v4/pkg/chart/v2/loader"
) )
// Dependency is the action for building a given chart's dependency tree. // Dependency is the action for building a given chart's dependency tree.

@ -25,8 +25,8 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"helm.sh/helm/v4/internal/test" "helm.sh/helm/v4/internal/test"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
chartutil "helm.sh/helm/v4/pkg/chart/util" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
) )
func TestList(t *testing.T) { func TestList(t *testing.T) {

@ -17,7 +17,7 @@ limitations under the License.
package action package action
import ( import (
"helm.sh/helm/v4/pkg/release" release "helm.sh/helm/v4/pkg/release/v1"
) )
// Get is the action for checking a given release's information. // Get is the action for checking a given release's information.

@ -21,7 +21,7 @@ import (
"strings" "strings"
"time" "time"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
) )
// GetMetadata is the action for checking a given release's metadata. // GetMetadata is the action for checking a given release's metadata.

@ -17,7 +17,7 @@ limitations under the License.
package action package action
import ( import (
chartutil "helm.sh/helm/v4/pkg/chart/util" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
) )
// GetValues is the action for checking a given release's values. // GetValues is the action for checking a given release's values.

@ -17,10 +17,12 @@ limitations under the License.
package action package action
import ( import (
"github.com/pkg/errors" "log/slog"
chartutil "helm.sh/helm/v4/pkg/chart/util" "fmt"
"helm.sh/helm/v4/pkg/release"
chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
release "helm.sh/helm/v4/pkg/release/v1"
) )
// History is the action for checking the release's ledger. // History is the action for checking the release's ledger.
@ -50,9 +52,9 @@ func (h *History) Run(name string) ([]*release.Release, error) {
} }
if err := chartutil.ValidateReleaseName(name); err != nil { if err := chartutil.ValidateReleaseName(name); err != nil {
return nil, errors.Errorf("release name is invalid: %s", name) return nil, fmt.Errorf("release name is invalid: %s", name)
} }
h.cfg.Log("getting history for release %s", name) slog.Debug("getting history for release", "release", name)
return h.cfg.Releases.History(name) return h.cfg.Releases.History(name)
} }

@ -25,17 +25,15 @@ import (
"helm.sh/helm/v4/pkg/kube" "helm.sh/helm/v4/pkg/kube"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/pkg/errors"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"helm.sh/helm/v4/pkg/release" release "helm.sh/helm/v4/pkg/release/v1"
helmtime "helm.sh/helm/v4/pkg/time" helmtime "helm.sh/helm/v4/pkg/time"
) )
// execHook executes all of the hooks for the given hook event. // execHook executes all of the hooks for the given hook event.
func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, timeout time.Duration) error { func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, timeout time.Duration) error {
executingHooks := []*release.Hook{} executingHooks := []*release.Hook{}
for _, h := range rl.Hooks { for _, h := range rl.Hooks {
@ -49,23 +47,17 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
// hooke are pre-ordered by kind, so keep order stable // hooke are pre-ordered by kind, so keep order stable
sort.Stable(hookByWeight(executingHooks)) sort.Stable(hookByWeight(executingHooks))
for _, h := range executingHooks { for i, h := range executingHooks {
// Set default delete policy to before-hook-creation // Set default delete policy to before-hook-creation
if len(h.DeletePolicies) == 0 { cfg.hookSetDeletePolicy(h)
// TODO(jlegrone): Only apply before-hook-creation delete policy to run to completion
// resources. For all other resource types update in place if a
// resource with the same name already exists and is owned by the
// current release.
h.DeletePolicies = []release.HookDeletePolicy{release.HookBeforeHookCreation}
}
if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation, timeout); err != nil { if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation, waitStrategy, timeout); err != nil {
return err return err
} }
resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), true) resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), true)
if err != nil { if err != nil {
return errors.Wrapf(err, "unable to build kubernetes object for %s hook %s", hook, h.Path) return fmt.Errorf("unable to build kubernetes object for %s hook %s: %w", hook, h.Path, err)
} }
// Record the time at which the hook was applied to the cluster // Record the time at which the hook was applied to the cluster
@ -84,11 +76,15 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
if _, err := cfg.KubeClient.Create(resources); err != nil { if _, err := cfg.KubeClient.Create(resources); err != nil {
h.LastRun.CompletedAt = helmtime.Now() h.LastRun.CompletedAt = helmtime.Now()
h.LastRun.Phase = release.HookPhaseFailed h.LastRun.Phase = release.HookPhaseFailed
return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path) return fmt.Errorf("warning: Hook %s %s failed: %w", hook, h.Path, err)
} }
waiter, err := cfg.KubeClient.GetWaiter(waitStrategy)
if err != nil {
return fmt.Errorf("unable to get waiter: %w", err)
}
// Watch hook resources until they have completed // Watch hook resources until they have completed
err = cfg.KubeClient.WatchUntilReady(resources, timeout) err = waiter.WatchUntilReady(resources, timeout)
// Note the time of success/failure // Note the time of success/failure
h.LastRun.CompletedAt = helmtime.Now() h.LastRun.CompletedAt = helmtime.Now()
// Mark hook as succeeded or failed // Mark hook as succeeded or failed
@ -101,10 +97,17 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
} }
// If a hook is failed, check the annotation of the hook to determine whether the hook should be deleted // If a hook is failed, check the annotation of the hook to determine whether the hook should be deleted
// under failed condition. If so, then clear the corresponding resource object in the hook // under failed condition. If so, then clear the corresponding resource object in the hook
if errDeleting := cfg.deleteHookByPolicy(h, release.HookFailed, timeout); errDeleting != nil { if errDeleting := cfg.deleteHookByPolicy(h, release.HookFailed, waitStrategy, timeout); errDeleting != nil {
// We log the error here as we want to propagate the hook failure upwards to the release object. // We log the error here as we want to propagate the hook failure upwards to the release object.
log.Printf("error deleting the hook resource on hook failure: %v", errDeleting) log.Printf("error deleting the hook resource on hook failure: %v", errDeleting)
} }
// If a hook is failed, check the annotation of the previous successful hooks to determine whether the hooks
// should be deleted under succeeded condition.
if err := cfg.deleteHooksByPolicy(executingHooks[0:i], release.HookSucceeded, waitStrategy, timeout); err != nil {
return err
}
return err return err
} }
h.LastRun.Phase = release.HookPhaseSucceeded h.LastRun.Phase = release.HookPhaseSucceeded
@ -118,7 +121,7 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
// 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)
} }
if err := cfg.deleteHookByPolicy(h, release.HookSucceeded, timeout); err != nil { if err := cfg.deleteHookByPolicy(h, release.HookSucceeded, waitStrategy, timeout); err != nil {
return err return err
} }
} }
@ -139,41 +142,64 @@ 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, timeout time.Duration) error { func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy, waitStrategy kube.WaitStrategy, 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" {
return nil return nil
} }
if hookHasDeletePolicy(h, policy) { if cfg.hookHasDeletePolicy(h, policy) {
resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), false) resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), false)
if err != nil { if err != nil {
return errors.Wrapf(err, "unable to build kubernetes object for deleting hook %s", h.Path) return fmt.Errorf("unable to build kubernetes object for deleting hook %s: %w", h.Path, err)
} }
_, errs := cfg.KubeClient.Delete(resources) _, errs := cfg.KubeClient.Delete(resources)
if len(errs) > 0 { if len(errs) > 0 {
return errors.New(joinErrors(errs)) return joinErrors(errs, "; ")
} }
// wait for resources until they are deleted to avoid conflicts waiter, err := cfg.KubeClient.GetWaiter(waitStrategy)
if kubeClient, ok := cfg.KubeClient.(kube.InterfaceExt); ok { if err != nil {
if err := kubeClient.WaitForDelete(resources, timeout); err != nil { return err
return err }
} if err := waiter.WaitForDelete(resources, timeout); err != nil {
return err
} }
} }
return nil return nil
} }
// deleteHooksByPolicy deletes all hooks if the hook policy instructs it to
func (cfg *Configuration) deleteHooksByPolicy(hooks []*release.Hook, policy release.HookDeletePolicy, waitStrategy kube.WaitStrategy, timeout time.Duration) error {
for _, h := range hooks {
if err := cfg.deleteHookByPolicy(h, policy, waitStrategy, timeout); err != nil {
return err
}
}
return nil
}
// hookHasDeletePolicy determines whether the defined hook deletion policy matches the hook deletion polices // hookHasDeletePolicy determines whether the defined hook deletion policy matches the hook deletion polices
// supported by helm. If so, mark the hook as one should be deleted. // supported by helm. If so, mark the hook as one should be deleted.
func hookHasDeletePolicy(h *release.Hook, policy release.HookDeletePolicy) bool { func (cfg *Configuration) hookHasDeletePolicy(h *release.Hook, policy release.HookDeletePolicy) bool {
for _, v := range h.DeletePolicies { cfg.mutex.Lock()
if policy == v { defer cfg.mutex.Unlock()
return true return slices.Contains(h.DeletePolicies, policy)
} }
// hookSetDeletePolicy determines whether the defined hook deletion policy matches the hook deletion polices
// supported by helm. If so, mark the hook as one should be deleted.
func (cfg *Configuration) hookSetDeletePolicy(h *release.Hook) {
cfg.mutex.Lock()
defer cfg.mutex.Unlock()
if len(h.DeletePolicies) == 0 {
// TODO(jlegrone): Only apply before-hook-creation delete policy to run to completion
// resources. For all other resource types update in place if a
// resource with the same name already exists and is owned by the
// current release.
h.DeletePolicies = []release.HookDeletePolicy{release.HookBeforeHookCreation}
} }
return false
} }
// outputLogsByPolicy outputs a pods logs if the hook policy instructs it to // outputLogsByPolicy outputs a pods logs if the hook policy instructs it to
@ -216,7 +242,7 @@ func (cfg *Configuration) deriveNamespace(h *release.Hook, namespace string) (st
}{} }{}
err := yaml.Unmarshal([]byte(h.Manifest), &tmp) err := yaml.Unmarshal([]byte(h.Manifest), &tmp)
if err != nil { if err != nil {
return "", errors.Wrapf(err, "unable to parse metadata.namespace from kubernetes manifest for output logs hook %s", h.Path) return "", fmt.Errorf("unable to parse metadata.namespace from kubernetes manifest for output logs hook %s: %w", h.Path, err)
} }
if tmp.Metadata.Namespace == "" { if tmp.Metadata.Namespace == "" {
return namespace, nil return namespace, nil

@ -20,13 +20,22 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"reflect"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/cli-runtime/pkg/resource"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/kube"
kubefake "helm.sh/helm/v4/pkg/kube/fake" kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v4/pkg/release" release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/storage"
"helm.sh/helm/v4/pkg/storage/driver"
) )
func podManifestWithOutputLogs(hookDefinitions []release.HookOutputLogPolicy) string { func podManifestWithOutputLogs(hookDefinitions []release.HookOutputLogPolicy) string {
@ -158,6 +167,7 @@ func TestInstallRelease_HooksOutputLogsOnSuccessAndFailure(t *testing.T) {
} }
func runInstallForHooksWithSuccess(t *testing.T, manifest, expectedNamespace string, shouldOutput bool) { func runInstallForHooksWithSuccess(t *testing.T, manifest, expectedNamespace string, shouldOutput bool) {
t.Helper()
var expectedOutput string var expectedOutput string
if shouldOutput { if shouldOutput {
expectedOutput = fmt.Sprintf("attempted to output logs for namespace: %s", expectedNamespace) expectedOutput = fmt.Sprintf("attempted to output logs for namespace: %s", expectedNamespace)
@ -181,6 +191,7 @@ func runInstallForHooksWithSuccess(t *testing.T, manifest, expectedNamespace str
} }
func runInstallForHooksWithFailure(t *testing.T, manifest, expectedNamespace string, shouldOutput bool) { func runInstallForHooksWithFailure(t *testing.T, manifest, expectedNamespace string, shouldOutput bool) {
t.Helper()
var expectedOutput string var expectedOutput string
if shouldOutput { if shouldOutput {
expectedOutput = fmt.Sprintf("attempted to output logs for namespace: %s", expectedNamespace) expectedOutput = fmt.Sprintf("attempted to output logs for namespace: %s", expectedNamespace)
@ -206,3 +217,187 @@ func runInstallForHooksWithFailure(t *testing.T, manifest, expectedNamespace str
is.Equal(expectedOutput, outBuffer.String()) is.Equal(expectedOutput, outBuffer.String())
is.Equal(release.StatusFailed, res.Info.Status) is.Equal(release.StatusFailed, res.Info.Status)
} }
type HookFailedError struct{}
func (e *HookFailedError) Error() string {
return "Hook failed!"
}
type HookFailingKubeClient struct {
kubefake.PrintingKubeClient
failOn resource.Info
deleteRecord []resource.Info
}
type HookFailingKubeWaiter struct {
*kubefake.PrintingKubeWaiter
failOn resource.Info
}
func (*HookFailingKubeClient) Build(reader io.Reader, _ bool) (kube.ResourceList, error) {
configMap := &v1.ConfigMap{}
err := yaml.NewYAMLOrJSONDecoder(reader, 1000).Decode(configMap)
if err != nil {
return kube.ResourceList{}, err
}
return kube.ResourceList{{
Name: configMap.Name,
Namespace: configMap.Namespace,
}}, nil
}
func (h *HookFailingKubeWaiter) WatchUntilReady(resources kube.ResourceList, _ time.Duration) error {
for _, res := range resources {
if res.Name == h.failOn.Name && res.Namespace == h.failOn.Namespace {
return &HookFailedError{}
}
}
return nil
}
func (h *HookFailingKubeClient) Delete(resources kube.ResourceList) (*kube.Result, []error) {
for _, res := range resources {
h.deleteRecord = append(h.deleteRecord, resource.Info{
Name: res.Name,
Namespace: res.Namespace,
})
}
return h.PrintingKubeClient.Delete(resources)
}
func (h *HookFailingKubeClient) GetWaiter(strategy kube.WaitStrategy) (kube.Waiter, error) {
waiter, _ := h.PrintingKubeClient.GetWaiter(strategy)
return &HookFailingKubeWaiter{
PrintingKubeWaiter: waiter.(*kubefake.PrintingKubeWaiter),
failOn: h.failOn,
}, nil
}
func TestHooksCleanUp(t *testing.T) {
hookEvent := release.HookPreInstall
testCases := []struct {
name string
inputRelease release.Release
failOn resource.Info
expectedDeleteRecord []resource.Info
expectError bool
}{
{
"Deletion hook runs for previously successful hook on failure of a heavier weight hook",
release.Release{
Name: "test-release",
Namespace: "test",
Hooks: []*release.Hook{
{
Name: "hook-1",
Kind: "ConfigMap",
Path: "templates/service_account.yaml",
Manifest: `apiVersion: v1
kind: ConfigMap
metadata:
name: build-config-1
namespace: test
data:
foo: bar
`,
Weight: -5,
Events: []release.HookEvent{
hookEvent,
},
DeletePolicies: []release.HookDeletePolicy{
release.HookBeforeHookCreation,
release.HookSucceeded,
release.HookFailed,
},
LastRun: release.HookExecution{
Phase: release.HookPhaseSucceeded,
},
},
{
Name: "hook-2",
Kind: "ConfigMap",
Path: "templates/job.yaml",
Manifest: `apiVersion: v1
kind: ConfigMap
metadata:
name: build-config-2
namespace: test
data:
foo: bar
`,
Weight: 0,
Events: []release.HookEvent{
hookEvent,
},
DeletePolicies: []release.HookDeletePolicy{
release.HookBeforeHookCreation,
release.HookSucceeded,
release.HookFailed,
},
LastRun: release.HookExecution{
Phase: release.HookPhaseFailed,
},
},
},
}, resource.Info{
Name: "build-config-2",
Namespace: "test",
}, []resource.Info{
{
// This should be in the record for `before-hook-creation`
Name: "build-config-1",
Namespace: "test",
},
{
// This should be in the record for `before-hook-creation`
Name: "build-config-2",
Namespace: "test",
},
{
// This should be in the record for cleaning up (the failure first)
Name: "build-config-2",
Namespace: "test",
},
{
// This should be in the record for cleaning up (then the previously successful)
Name: "build-config-1",
Namespace: "test",
},
}, true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
kubeClient := &HookFailingKubeClient{
kubefake.PrintingKubeClient{Out: io.Discard}, tc.failOn, []resource.Info{},
}
configuration := &Configuration{
Releases: storage.Init(driver.NewMemory()),
KubeClient: kubeClient,
Capabilities: chartutil.DefaultCapabilities,
}
err := configuration.execHook(&tc.inputRelease, hookEvent, kube.StatusWatcherStrategy, 600)
if !reflect.DeepEqual(kubeClient.deleteRecord, tc.expectedDeleteRecord) {
t.Fatalf("Got unexpected delete record, expected: %#v, but got: %#v", kubeClient.deleteRecord, tc.expectedDeleteRecord)
}
if err != nil && !tc.expectError {
t.Fatalf("Got an unexpected error.")
}
if err == nil && tc.expectError {
t.Fatalf("Expected and error but did not get it.")
}
})
}
}

@ -19,8 +19,11 @@ package action
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"io/fs"
"log/slog"
"net/url" "net/url"
"os" "os"
"path" "path"
@ -31,7 +34,6 @@ import (
"time" "time"
"github.com/Masterminds/sprig/v3" "github.com/Masterminds/sprig/v3"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
@ -39,8 +41,8 @@ import (
"k8s.io/cli-runtime/pkg/resource" "k8s.io/cli-runtime/pkg/resource"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
chartutil "helm.sh/helm/v4/pkg/chart/util" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/cli" "helm.sh/helm/v4/pkg/cli"
"helm.sh/helm/v4/pkg/downloader" "helm.sh/helm/v4/pkg/downloader"
"helm.sh/helm/v4/pkg/getter" "helm.sh/helm/v4/pkg/getter"
@ -48,8 +50,8 @@ import (
kubefake "helm.sh/helm/v4/pkg/kube/fake" kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v4/pkg/postrender" "helm.sh/helm/v4/pkg/postrender"
"helm.sh/helm/v4/pkg/registry" "helm.sh/helm/v4/pkg/registry"
"helm.sh/helm/v4/pkg/release" releaseutil "helm.sh/helm/v4/pkg/release/util"
"helm.sh/helm/v4/pkg/releaseutil" release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/repo" "helm.sh/helm/v4/pkg/repo"
"helm.sh/helm/v4/pkg/storage" "helm.sh/helm/v4/pkg/storage"
"helm.sh/helm/v4/pkg/storage/driver" "helm.sh/helm/v4/pkg/storage/driver"
@ -79,7 +81,7 @@ type Install struct {
HideSecret bool HideSecret bool
DisableHooks bool DisableHooks bool
Replace bool Replace bool
Wait bool WaitStrategy kube.WaitStrategy
WaitForJobs bool WaitForJobs bool
Devel bool Devel bool
DependencyUpdate bool DependencyUpdate bool
@ -142,19 +144,19 @@ func NewInstall(cfg *Configuration) *Install {
in := &Install{ in := &Install{
cfg: cfg, cfg: cfg,
} }
in.ChartPathOptions.registryClient = cfg.RegistryClient in.registryClient = cfg.RegistryClient
return in return in
} }
// SetRegistryClient sets the registry client for the install action // SetRegistryClient sets the registry client for the install action
func (i *Install) SetRegistryClient(registryClient *registry.Client) { func (i *Install) SetRegistryClient(registryClient *registry.Client) {
i.ChartPathOptions.registryClient = registryClient i.registryClient = registryClient
} }
// GetRegistryClient get the registry client. // GetRegistryClient get the registry client.
func (i *Install) GetRegistryClient() *registry.Client { func (i *Install) GetRegistryClient() *registry.Client {
return i.ChartPathOptions.registryClient return i.registryClient
} }
func (i *Install) installCRDs(crds []chart.CRD) error { func (i *Install) installCRDs(crds []chart.CRD) error {
@ -164,7 +166,7 @@ func (i *Install) installCRDs(crds []chart.CRD) error {
// Read in the resources // Read in the resources
res, err := i.cfg.KubeClient.Build(bytes.NewBuffer(obj.File.Data), false) res, err := i.cfg.KubeClient.Build(bytes.NewBuffer(obj.File.Data), false)
if err != nil { if err != nil {
return errors.Wrapf(err, "failed to install CRD %s", obj.Name) return fmt.Errorf("failed to install CRD %s: %w", obj.Name, err)
} }
// Send them to Kube // Send them to Kube
@ -172,16 +174,20 @@ func (i *Install) installCRDs(crds []chart.CRD) error {
// If the error is CRD already exists, continue. // If the error is CRD already exists, continue.
if apierrors.IsAlreadyExists(err) { if apierrors.IsAlreadyExists(err) {
crdName := res[0].Name crdName := res[0].Name
i.cfg.Log("CRD %s is already present. Skipping.", crdName) slog.Debug("CRD is already present. Skipping", "crd", crdName)
continue continue
} }
return errors.Wrapf(err, "failed to install CRD %s", obj.Name) return fmt.Errorf("failed to install CRD %s: %w", obj.Name, err)
} }
totalItems = append(totalItems, res...) totalItems = append(totalItems, res...)
} }
if len(totalItems) > 0 { if len(totalItems) > 0 {
waiter, err := i.cfg.KubeClient.GetWaiter(i.WaitStrategy)
if err != nil {
return fmt.Errorf("unable to get waiter: %w", err)
}
// Give time for the CRD to be recognized. // Give time for the CRD to be recognized.
if err := i.cfg.KubeClient.Wait(totalItems, 60*time.Second); err != nil { if err := waiter.Wait(totalItems, 60*time.Second); err != nil {
return err return err
} }
@ -196,7 +202,7 @@ func (i *Install) installCRDs(crds []chart.CRD) error {
return err return err
} }
i.cfg.Log("Clearing discovery cache") slog.Debug("clearing discovery cache")
discoveryClient.Invalidate() discoveryClient.Invalidate()
_, _ = discoveryClient.ServerGroups() _, _ = discoveryClient.ServerGroups()
@ -209,7 +215,7 @@ func (i *Install) installCRDs(crds []chart.CRD) error {
return err return err
} }
if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok { if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok {
i.cfg.Log("Clearing REST mapper cache") slog.Debug("clearing REST mapper cache")
resettable.Reset() resettable.Reset()
} }
} }
@ -233,25 +239,25 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
// Check reachability of cluster unless in client-only mode (e.g. `helm template` without `--validate`) // Check reachability of cluster unless in client-only mode (e.g. `helm template` without `--validate`)
if !i.ClientOnly { if !i.ClientOnly {
if err := i.cfg.KubeClient.IsReachable(); err != nil { if err := i.cfg.KubeClient.IsReachable(); err != nil {
i.cfg.Log(fmt.Sprintf("ERROR: Cluster reachability check failed: %v", err)) slog.Error(fmt.Sprintf("cluster reachability check failed: %v", err))
return nil, errors.Wrap(err, "cluster reachability check failed") return nil, fmt.Errorf("cluster reachability check failed: %w", err)
} }
} }
// HideSecret must be used with dry run. Otherwise, return an error. // HideSecret must be used with dry run. Otherwise, return an error.
if !i.isDryRun() && i.HideSecret { if !i.isDryRun() && i.HideSecret {
i.cfg.Log("ERROR: Hiding Kubernetes secrets requires a dry-run mode") slog.Error("hiding Kubernetes secrets requires a dry-run mode")
return nil, errors.New("Hiding Kubernetes secrets requires a dry-run mode") return nil, errors.New("hiding Kubernetes secrets requires a dry-run mode")
} }
if err := i.availableName(); err != nil { if err := i.availableName(); err != nil {
i.cfg.Log(fmt.Sprintf("ERROR: Release name check failed: %v", err)) slog.Error("release name check failed", slog.Any("error", err))
return nil, errors.Wrap(err, "release name check failed") return nil, fmt.Errorf("release name check failed: %w", err)
} }
if err := chartutil.ProcessDependencies(chrt, vals); err != nil { if err := chartutil.ProcessDependencies(chrt, vals); err != nil {
i.cfg.Log(fmt.Sprintf("ERROR: Processing chart dependencies failed: %v", err)) slog.Error("chart dependencies processing failed", slog.Any("error", err))
return nil, errors.Wrap(err, "chart dependencies processing failed") return nil, fmt.Errorf("chart dependencies processing failed: %w", err)
} }
var interactWithRemote bool var interactWithRemote bool
@ -264,7 +270,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
if crds := chrt.CRDObjects(); !i.ClientOnly && !i.SkipCRDs && len(crds) > 0 { if crds := chrt.CRDObjects(); !i.ClientOnly && !i.SkipCRDs && len(crds) > 0 {
// On dry run, bail here // On dry run, bail here
if i.isDryRun() { if i.isDryRun() {
i.cfg.Log("WARNING: This chart or one of its subcharts contains CRDs. Rendering may fail or contain inaccuracies.") slog.Warn("This chart or one of its subcharts contains CRDs. Rendering may fail or contain inaccuracies.")
} else if err := i.installCRDs(crds); err != nil { } else if err := i.installCRDs(crds); err != nil {
return nil, err return nil, err
} }
@ -284,12 +290,14 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
mem.SetNamespace(i.Namespace) mem.SetNamespace(i.Namespace)
i.cfg.Releases = storage.Init(mem) i.cfg.Releases = storage.Init(mem)
} else if !i.ClientOnly && len(i.APIVersions) > 0 { } else if !i.ClientOnly && len(i.APIVersions) > 0 {
i.cfg.Log("API Version list given outside of client only mode, this list will be ignored") slog.Debug("API Version list given outside of client only mode, this list will be ignored")
} }
// Make sure if Atomic is set, that wait is set as well. This makes it so // Make sure if Atomic is set, that wait is set as well. This makes it so
// the user doesn't have to specify both // the user doesn't have to specify both
i.Wait = i.Wait || i.Atomic if i.WaitStrategy == kube.HookOnlyStrategy && i.Atomic {
i.WaitStrategy = kube.StatusWatcherStrategy
}
caps, err := i.cfg.getCapabilities() caps, err := i.cfg.getCapabilities()
if err != nil { if err != nil {
@ -335,7 +343,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
var toBeAdopted kube.ResourceList var toBeAdopted kube.ResourceList
resources, err := i.cfg.KubeClient.Build(bytes.NewBufferString(rel.Manifest), !i.DisableOpenAPIValidation) resources, err := i.cfg.KubeClient.Build(bytes.NewBufferString(rel.Manifest), !i.DisableOpenAPIValidation)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "unable to build kubernetes objects from release manifest") return nil, fmt.Errorf("unable to build kubernetes objects from release manifest: %w", err)
} }
// It is safe to use "force" here because these are resources currently rendered by the chart. // It is safe to use "force" here because these are resources currently rendered by the chart.
@ -357,7 +365,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
toBeAdopted, err = existingResourceConflict(resources, rel.Name, rel.Namespace) toBeAdopted, err = existingResourceConflict(resources, rel.Name, rel.Namespace)
} }
if err != nil { if err != nil {
return nil, errors.Wrap(err, "Unable to continue with install") return nil, fmt.Errorf("unable to continue with install: %w", err)
} }
} }
@ -448,7 +456,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource
var err error var err error
// pre-install hooks // pre-install hooks
if !i.DisableHooks { if !i.DisableHooks {
if err := i.cfg.execHook(rel, release.HookPreInstall, i.Timeout); err != nil { if err := i.cfg.execHook(rel, release.HookPreInstall, i.WaitStrategy, i.Timeout); err != nil {
return rel, fmt.Errorf("failed pre-install: %s", err) return rel, fmt.Errorf("failed pre-install: %s", err)
} }
} }
@ -459,25 +467,32 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource
if len(toBeAdopted) == 0 && len(resources) > 0 { if len(toBeAdopted) == 0 && len(resources) > 0 {
_, err = i.cfg.KubeClient.Create(resources) _, err = i.cfg.KubeClient.Create(resources)
} else if len(resources) > 0 { } else if len(resources) > 0 {
_, err = i.cfg.KubeClient.Update(toBeAdopted, resources, i.Force) if i.TakeOwnership {
_, err = i.cfg.KubeClient.(kube.InterfaceThreeWayMerge).UpdateThreeWayMerge(toBeAdopted, resources, i.Force)
} else {
_, err = i.cfg.KubeClient.Update(toBeAdopted, resources, i.Force)
}
} }
if err != nil { if err != nil {
return rel, err return rel, err
} }
if i.Wait { waiter, err := i.cfg.KubeClient.GetWaiter(i.WaitStrategy)
if i.WaitForJobs { if err != nil {
err = i.cfg.KubeClient.WaitWithJobs(resources, i.Timeout) return rel, fmt.Errorf("failed to get waiter: %w", err)
} else { }
err = i.cfg.KubeClient.Wait(resources, i.Timeout)
} if i.WaitForJobs {
if err != nil { err = waiter.WaitWithJobs(resources, i.Timeout)
return rel, err } else {
} err = waiter.Wait(resources, i.Timeout)
}
if err != nil {
return rel, err
} }
if !i.DisableHooks { if !i.DisableHooks {
if err := i.cfg.execHook(rel, release.HookPostInstall, i.Timeout); err != nil { if err := i.cfg.execHook(rel, release.HookPostInstall, i.WaitStrategy, i.Timeout); err != nil {
return rel, fmt.Errorf("failed post-install: %s", err) return rel, fmt.Errorf("failed post-install: %s", err)
} }
} }
@ -496,7 +511,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource
// One possible strategy would be to do a timed retry to see if we can get // One possible strategy would be to do a timed retry to see if we can get
// this stored in the future. // this stored in the future.
if err := i.recordRelease(rel); err != nil { if err := i.recordRelease(rel); err != nil {
i.cfg.Log("failed to record the release: %s", err) slog.Error("failed to record the release", slog.Any("error", err))
} }
return rel, nil return rel, nil
@ -505,15 +520,15 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource
func (i *Install) failRelease(rel *release.Release, err error) (*release.Release, error) { func (i *Install) failRelease(rel *release.Release, err error) (*release.Release, error) {
rel.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", i.ReleaseName, err.Error())) rel.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", i.ReleaseName, err.Error()))
if i.Atomic { if i.Atomic {
i.cfg.Log("Install failed and atomic is set, uninstalling release") slog.Debug("install failed, uninstalling release", "release", i.ReleaseName)
uninstall := NewUninstall(i.cfg) uninstall := NewUninstall(i.cfg)
uninstall.DisableHooks = i.DisableHooks uninstall.DisableHooks = i.DisableHooks
uninstall.KeepHistory = false uninstall.KeepHistory = false
uninstall.Timeout = i.Timeout uninstall.Timeout = i.Timeout
if _, uninstallErr := uninstall.Run(i.ReleaseName); uninstallErr != nil { if _, uninstallErr := uninstall.Run(i.ReleaseName); uninstallErr != nil {
return rel, errors.Wrapf(uninstallErr, "an error occurred while uninstalling the release. original install error: %s", err) return rel, fmt.Errorf("an error occurred while uninstalling the release. original install error: %w: %w", err, uninstallErr)
} }
return rel, errors.Wrapf(err, "release %s failed, and has been uninstalled due to atomic being set", i.ReleaseName) return rel, fmt.Errorf("release %s failed, and has been uninstalled due to atomic being set: %w", i.ReleaseName, err)
} }
i.recordRelease(rel) // Ignore the error, since we have another error to deal with. i.recordRelease(rel) // Ignore the error, since we have another error to deal with.
return rel, err return rel, err
@ -531,7 +546,7 @@ func (i *Install) availableName() error {
start := i.ReleaseName start := i.ReleaseName
if err := chartutil.ValidateReleaseName(start); err != nil { if err := chartutil.ValidateReleaseName(start); err != nil {
return errors.Wrapf(err, "release name %q", start) return fmt.Errorf("release name %q: %w", start, err)
} }
// On dry run, bail here // On dry run, bail here
if i.isDryRun() { if i.isDryRun() {
@ -618,7 +633,7 @@ func writeToFile(outputDir string, name string, data string, appendData bool) er
defer f.Close() defer f.Close()
_, err = f.WriteString(fmt.Sprintf("---\n# Source: %s\n%s\n", name, data)) _, err = fmt.Fprintf(f, "---\n# Source: %s\n%s\n", name, data)
if err != nil { if err != nil {
return err return err
@ -639,7 +654,7 @@ func createOrOpenFile(filename string, appendData bool) (*os.File, error) {
func ensureDirectoryForFile(file string) error { func ensureDirectoryForFile(file string) error {
baseDir := path.Dir(file) baseDir := path.Dir(file)
_, err := os.Stat(baseDir) _, err := os.Stat(baseDir)
if err != nil && !os.IsNotExist(err) { if err != nil && !errors.Is(err, fs.ErrNotExist) {
return err return err
} }
@ -661,7 +676,7 @@ func (i *Install) NameAndChart(args []string) (string, string, error) {
} }
if len(args) > 2 { if len(args) > 2 {
return args[0], args[1], errors.Errorf("expected at most two arguments, unexpected arguments: %v", strings.Join(args[2:], ", ")) return args[0], args[1], fmt.Errorf("expected at most two arguments, unexpected arguments: %v", strings.Join(args[2:], ", "))
} }
if len(args) == 2 { if len(args) == 2 {
@ -726,7 +741,7 @@ OUTER:
} }
if len(missing) > 0 { if len(missing) > 0 {
return errors.Errorf("found in Chart.yaml, but missing in charts/ directory: %s", strings.Join(missing, ", ")) return fmt.Errorf("found in Chart.yaml, but missing in charts/ directory: %s", strings.Join(missing, ", "))
} }
return nil return nil
} }
@ -762,7 +777,7 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (
return abs, nil return abs, nil
} }
if filepath.IsAbs(name) || strings.HasPrefix(name, ".") { if filepath.IsAbs(name) || strings.HasPrefix(name, ".") {
return name, errors.Errorf("path %q not found", name) return name, fmt.Errorf("path %q not found", name)
} }
dl := downloader.ChartDownloader{ dl := downloader.ChartDownloader{

@ -19,8 +19,11 @@ package action
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"io/fs"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@ -31,12 +34,21 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kuberuntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest/fake"
"helm.sh/helm/v4/internal/test" "helm.sh/helm/v4/internal/test"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
chartutil "helm.sh/helm/v4/pkg/chart/util" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/kube"
kubefake "helm.sh/helm/v4/pkg/kube/fake" kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v4/pkg/release" release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/storage/driver" "helm.sh/helm/v4/pkg/storage/driver"
helmtime "helm.sh/helm/v4/pkg/time" helmtime "helm.sh/helm/v4/pkg/time"
) )
@ -47,7 +59,64 @@ type nameTemplateTestCase struct {
expectedErrorStr string expectedErrorStr string
} }
func createDummyResourceList(owned bool) kube.ResourceList {
obj := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "dummyName",
Namespace: "spaced",
},
}
if owned {
obj.Labels = map[string]string{
"app.kubernetes.io/managed-by": "Helm",
}
obj.Annotations = map[string]string{
"meta.helm.sh/release-name": "test-install-release",
"meta.helm.sh/release-namespace": "spaced",
}
}
resInfo := resource.Info{
Name: "dummyName",
Namespace: "spaced",
Mapping: &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployment"},
GroupVersionKind: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Scope: meta.RESTScopeNamespace,
},
Object: obj,
}
body := io.NopCloser(bytes.NewReader([]byte(kuberuntime.EncodeOrDie(appsv1Codec, obj))))
resInfo.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "apps", Version: "v1"},
NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
Client: fake.CreateHTTPClient(func(_ *http.Request) (*http.Response, error) {
header := http.Header{}
header.Set("Content-Type", kuberuntime.ContentTypeJSON)
return &http.Response{
StatusCode: http.StatusOK,
Header: header,
Body: body,
}, nil
}),
}
var resourceList kube.ResourceList
resourceList.Append(&resInfo)
return resourceList
}
func installActionWithConfig(config *Configuration) *Install {
instAction := NewInstall(config)
instAction.Namespace = "spaced"
instAction.ReleaseName = "test-install-release"
return instAction
}
func installAction(t *testing.T) *Install { func installAction(t *testing.T) *Install {
t.Helper()
config := actionConfigFixture(t) config := actionConfigFixture(t)
instAction := NewInstall(config) instAction := NewInstall(config)
instAction.Namespace = "spaced" instAction.Namespace = "spaced"
@ -62,7 +131,7 @@ func TestInstallRelease(t *testing.T) {
instAction := installAction(t) instAction := installAction(t)
vals := map[string]interface{}{} vals := map[string]interface{}{}
ctx, done := context.WithCancel(context.Background()) ctx, done := context.WithCancel(t.Context())
res, err := instAction.RunWithContext(ctx, buildChart(), vals) res, err := instAction.RunWithContext(ctx, buildChart(), vals)
if err != nil { if err != nil {
t.Fatalf("Failed install: %s", err) t.Fatalf("Failed install: %s", err)
@ -92,6 +161,61 @@ func TestInstallRelease(t *testing.T) {
is.Equal(lastRelease.Info.Status, release.StatusDeployed) is.Equal(lastRelease.Info.Status, release.StatusDeployed)
} }
func TestInstallReleaseWithTakeOwnership_ResourceNotOwned(t *testing.T) {
// This test will test checking ownership of a resource
// returned by the fake client. If the resource is not
// owned by the chart, ownership is taken.
// To verify ownership has been taken, the fake client
// needs to store state which is a bigger rewrite.
// TODO: Ensure fake kube client stores state. Maybe using
// "k8s.io/client-go/kubernetes/fake" could be sufficient? i.e
// "Client{Namespace: namespace, kubeClient: k8sfake.NewClientset()}"
is := assert.New(t)
// Resource list from cluster is NOT owned by helm chart
config := actionConfigFixtureWithDummyResources(t, createDummyResourceList(false))
instAction := installActionWithConfig(config)
instAction.TakeOwnership = true
res, err := instAction.Run(buildChart(), nil)
if err != nil {
t.Fatalf("Failed install: %s", err)
}
rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
is.NoError(err)
is.Equal(rel.Info.Description, "Install complete")
}
func TestInstallReleaseWithTakeOwnership_ResourceOwned(t *testing.T) {
is := assert.New(t)
// Resource list from cluster is owned by helm chart
config := actionConfigFixtureWithDummyResources(t, createDummyResourceList(true))
instAction := installActionWithConfig(config)
instAction.TakeOwnership = false
res, err := instAction.Run(buildChart(), nil)
if err != nil {
t.Fatalf("Failed install: %s", err)
}
rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
is.NoError(err)
is.Equal(rel.Info.Description, "Install complete")
}
func TestInstallReleaseWithTakeOwnership_ResourceOwnedNoFlag(t *testing.T) {
is := assert.New(t)
// Resource list from cluster is NOT owned by helm chart
config := actionConfigFixtureWithDummyResources(t, createDummyResourceList(false))
instAction := installActionWithConfig(config)
_, err := instAction.Run(buildChart(), nil)
is.Error(err)
is.Contains(err.Error(), "unable to continue with install")
}
func TestInstallReleaseWithValues(t *testing.T) { func TestInstallReleaseWithValues(t *testing.T) {
is := assert.New(t) is := assert.New(t)
instAction := installAction(t) instAction := installAction(t)
@ -323,7 +447,9 @@ func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) {
instAction.DryRun = true instAction.DryRun = true
vals := map[string]interface{}{} vals := map[string]interface{}{}
_, err := instAction.Run(buildChart(withSampleIncludingIncorrectTemplates()), vals) _, err := instAction.Run(buildChart(withSampleIncludingIncorrectTemplates()), vals)
expectedErr := "\"hello/templates/incorrect\" at <.Values.bad.doh>: nil pointer evaluating interface {}.doh" expectedErr := `hello/templates/incorrect:1:10
executing "hello/templates/incorrect" at <.Values.bad.doh>:
nil pointer evaluating interface {}.doh`
if err == nil { if err == nil {
t.Fatalf("Install should fail containing error: %s", expectedErr) t.Fatalf("Install should fail containing error: %s", expectedErr)
} }
@ -411,7 +537,7 @@ func TestInstallRelease_Wait(t *testing.T) {
failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
failer.WaitError = fmt.Errorf("I timed out") failer.WaitError = fmt.Errorf("I timed out")
instAction.cfg.KubeClient = failer instAction.cfg.KubeClient = failer
instAction.Wait = true instAction.WaitStrategy = kube.StatusWatcherStrategy
vals := map[string]interface{}{} vals := map[string]interface{}{}
goroutines := runtime.NumGoroutine() goroutines := runtime.NumGoroutine()
@ -430,10 +556,10 @@ func TestInstallRelease_Wait_Interrupted(t *testing.T) {
failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
failer.WaitDuration = 10 * time.Second failer.WaitDuration = 10 * time.Second
instAction.cfg.KubeClient = failer instAction.cfg.KubeClient = failer
instAction.Wait = true instAction.WaitStrategy = kube.StatusWatcherStrategy
vals := map[string]interface{}{} vals := map[string]interface{}{}
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(t.Context())
time.AfterFunc(time.Second, cancel) time.AfterFunc(time.Second, cancel)
goroutines := runtime.NumGoroutine() goroutines := runtime.NumGoroutine()
@ -453,7 +579,7 @@ func TestInstallRelease_WaitForJobs(t *testing.T) {
failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
failer.WaitError = fmt.Errorf("I timed out") failer.WaitError = fmt.Errorf("I timed out")
instAction.cfg.KubeClient = failer instAction.cfg.KubeClient = failer
instAction.Wait = true instAction.WaitStrategy = kube.StatusWatcherStrategy
instAction.WaitForJobs = true instAction.WaitForJobs = true
vals := map[string]interface{}{} vals := map[string]interface{}{}
@ -517,9 +643,11 @@ func TestInstallRelease_Atomic_Interrupted(t *testing.T) {
instAction.Atomic = true instAction.Atomic = true
vals := map[string]interface{}{} vals := map[string]interface{}{}
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(t.Context())
time.AfterFunc(time.Second, cancel) time.AfterFunc(time.Second, cancel)
goroutines := runtime.NumGoroutine()
res, err := instAction.RunWithContext(ctx, buildChart(), vals) res, err := instAction.RunWithContext(ctx, buildChart(), vals)
is.Error(err) is.Error(err)
is.Contains(err.Error(), "context canceled") is.Contains(err.Error(), "context canceled")
@ -530,6 +658,9 @@ func TestInstallRelease_Atomic_Interrupted(t *testing.T) {
_, err = instAction.cfg.Releases.Get(res.Name, res.Version) _, err = instAction.cfg.Releases.Get(res.Name, res.Version)
is.Error(err) is.Error(err)
is.Equal(err, driver.ErrReleaseNotFound) is.Equal(err, driver.ErrReleaseNotFound)
is.Equal(goroutines+1, runtime.NumGoroutine()) // installation goroutine still is in background
time.Sleep(10 * time.Second) // wait for goroutine to finish
is.Equal(goroutines, runtime.NumGoroutine())
} }
func TestNameTemplate(t *testing.T) { func TestNameTemplate(t *testing.T) {
@ -630,7 +761,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(os.IsNotExist(err)) is.True(errors.Is(err, fs.ErrNotExist))
} }
func TestInstallOutputDirWithReleaseName(t *testing.T) { func TestInstallOutputDirWithReleaseName(t *testing.T) {
@ -666,7 +797,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(os.IsNotExist(err)) is.True(errors.Is(err, fs.ErrNotExist))
} }
func TestNameAndChart(t *testing.T) { func TestNameAndChart(t *testing.T) {

@ -17,13 +17,12 @@ limitations under the License.
package action package action
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/pkg/errors" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/lint" "helm.sh/helm/v4/pkg/lint"
"helm.sh/helm/v4/pkg/lint/support" "helm.sh/helm/v4/pkg/lint/support"
) )
@ -94,26 +93,26 @@ func lintChart(path string, vals map[string]interface{}, namespace string, kubeV
if strings.HasSuffix(path, ".tgz") || strings.HasSuffix(path, ".tar.gz") { if strings.HasSuffix(path, ".tgz") || strings.HasSuffix(path, ".tar.gz") {
tempDir, err := os.MkdirTemp("", "helm-lint") tempDir, err := os.MkdirTemp("", "helm-lint")
if err != nil { if err != nil {
return linter, errors.Wrap(err, "unable to create temp dir to extract tarball") return linter, fmt.Errorf("unable to create temp dir to extract tarball: %w", err)
} }
defer os.RemoveAll(tempDir) defer os.RemoveAll(tempDir)
file, err := os.Open(path) file, err := os.Open(path)
if err != nil { if err != nil {
return linter, errors.Wrap(err, "unable to open tarball") return linter, fmt.Errorf("unable to open tarball: %w", err)
} }
defer file.Close() defer file.Close()
if err = chartutil.Expand(tempDir, file); err != nil { if err = chartutil.Expand(tempDir, file); err != nil {
return linter, errors.Wrap(err, "unable to extract tarball") return linter, fmt.Errorf("unable to extract tarball: %w", err)
} }
files, err := os.ReadDir(tempDir) files, err := os.ReadDir(tempDir)
if err != nil { if err != nil {
return linter, errors.Wrapf(err, "unable to read temporary output directory %s", tempDir) return linter, fmt.Errorf("unable to read temporary output directory %s: %w", tempDir, err)
} }
if !files[0].IsDir() { if !files[0].IsDir() {
return linter, errors.Errorf("unexpected file %s in temporary output directory %s", files[0].Name(), tempDir) return linter, fmt.Errorf("unexpected file %s in temporary output directory %s", files[0].Name(), tempDir)
} }
chartPath = filepath.Join(tempDir, files[0].Name()) chartPath = filepath.Join(tempDir, files[0].Name())
@ -123,7 +122,7 @@ func lintChart(path string, vals map[string]interface{}, namespace string, kubeV
// Guard: Error out if this is not a chart. // Guard: Error out if this is not a chart.
if _, err := os.Stat(filepath.Join(chartPath, "Chart.yaml")); err != nil { if _, err := os.Stat(filepath.Join(chartPath, "Chart.yaml")); err != nil {
return linter, errors.Wrap(err, "unable to check Chart.yaml file in chart") return linter, fmt.Errorf("unable to check Chart.yaml file in chart: %w", err)
} }
return lint.RunAll( return lint.RunAll(

@ -22,8 +22,8 @@ import (
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"helm.sh/helm/v4/pkg/release" releaseutil "helm.sh/helm/v4/pkg/release/util"
"helm.sh/helm/v4/pkg/releaseutil" release "helm.sh/helm/v4/pkg/release/v1"
) )
// ListStates represents zero or more status codes that a list item may have set // ListStates represents zero or more status codes that a list item may have set

@ -21,7 +21,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"helm.sh/helm/v4/pkg/release" release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/storage" "helm.sh/helm/v4/pkg/storage"
) )
@ -64,13 +64,14 @@ func TestList_Empty(t *testing.T) {
} }
func newListFixture(t *testing.T) *List { func newListFixture(t *testing.T) *List {
t.Helper()
return NewList(actionConfigFixture(t)) return NewList(actionConfigFixture(t))
} }
func TestList_OneNamespace(t *testing.T) { func TestList_OneNamespace(t *testing.T) {
is := assert.New(t) is := assert.New(t)
lister := newListFixture(t) lister := newListFixture(t)
makeMeSomeReleases(lister.cfg.Releases, t) makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run() list, err := lister.Run()
is.NoError(err) is.NoError(err)
is.Len(list, 3) is.Len(list, 3)
@ -79,7 +80,7 @@ func TestList_OneNamespace(t *testing.T) {
func TestList_AllNamespaces(t *testing.T) { func TestList_AllNamespaces(t *testing.T) {
is := assert.New(t) is := assert.New(t)
lister := newListFixture(t) lister := newListFixture(t)
makeMeSomeReleases(lister.cfg.Releases, t) makeMeSomeReleases(t, lister.cfg.Releases)
lister.AllNamespaces = true lister.AllNamespaces = true
lister.SetStateMask() lister.SetStateMask()
list, err := lister.Run() list, err := lister.Run()
@ -91,7 +92,7 @@ func TestList_Sort(t *testing.T) {
is := assert.New(t) is := assert.New(t)
lister := newListFixture(t) lister := newListFixture(t)
lister.Sort = ByNameDesc // Other sorts are tested elsewhere lister.Sort = ByNameDesc // Other sorts are tested elsewhere
makeMeSomeReleases(lister.cfg.Releases, t) makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run() list, err := lister.Run()
is.NoError(err) is.NoError(err)
is.Len(list, 3) is.Len(list, 3)
@ -104,7 +105,7 @@ func TestList_Limit(t *testing.T) {
is := assert.New(t) is := assert.New(t)
lister := newListFixture(t) lister := newListFixture(t)
lister.Limit = 2 lister.Limit = 2
makeMeSomeReleases(lister.cfg.Releases, t) makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run() list, err := lister.Run()
is.NoError(err) is.NoError(err)
is.Len(list, 2) is.Len(list, 2)
@ -117,7 +118,7 @@ func TestList_BigLimit(t *testing.T) {
is := assert.New(t) is := assert.New(t)
lister := newListFixture(t) lister := newListFixture(t)
lister.Limit = 20 lister.Limit = 20
makeMeSomeReleases(lister.cfg.Releases, t) makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run() list, err := lister.Run()
is.NoError(err) is.NoError(err)
is.Len(list, 3) is.Len(list, 3)
@ -133,7 +134,7 @@ func TestList_LimitOffset(t *testing.T) {
lister := newListFixture(t) lister := newListFixture(t)
lister.Limit = 2 lister.Limit = 2
lister.Offset = 1 lister.Offset = 1
makeMeSomeReleases(lister.cfg.Releases, t) makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run() list, err := lister.Run()
is.NoError(err) is.NoError(err)
is.Len(list, 2) is.Len(list, 2)
@ -148,7 +149,7 @@ func TestList_LimitOffsetOutOfBounds(t *testing.T) {
lister := newListFixture(t) lister := newListFixture(t)
lister.Limit = 2 lister.Limit = 2
lister.Offset = 3 // Last item is index 2 lister.Offset = 3 // Last item is index 2
makeMeSomeReleases(lister.cfg.Releases, t) makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run() list, err := lister.Run()
is.NoError(err) is.NoError(err)
is.Len(list, 0) is.Len(list, 0)
@ -163,7 +164,7 @@ func TestList_LimitOffsetOutOfBounds(t *testing.T) {
func TestList_StateMask(t *testing.T) { func TestList_StateMask(t *testing.T) {
is := assert.New(t) is := assert.New(t)
lister := newListFixture(t) lister := newListFixture(t)
makeMeSomeReleases(lister.cfg.Releases, t) makeMeSomeReleases(t, lister.cfg.Releases)
one, err := lister.cfg.Releases.Get("one", 1) one, err := lister.cfg.Releases.Get("one", 1)
is.NoError(err) is.NoError(err)
one.SetStatus(release.StatusUninstalled, "uninstalled") one.SetStatus(release.StatusUninstalled, "uninstalled")
@ -193,7 +194,7 @@ func TestList_StateMaskWithStaleRevisions(t *testing.T) {
lister := newListFixture(t) lister := newListFixture(t)
lister.StateMask = ListFailed lister.StateMask = ListFailed
makeMeSomeReleasesWithStaleFailure(lister.cfg.Releases, t) makeMeSomeReleasesWithStaleFailure(t, lister.cfg.Releases)
res, err := lister.Run() res, err := lister.Run()
@ -205,7 +206,7 @@ func TestList_StateMaskWithStaleRevisions(t *testing.T) {
is.Equal("failed", res[0].Name) is.Equal("failed", res[0].Name)
} }
func makeMeSomeReleasesWithStaleFailure(store *storage.Storage, t *testing.T) { func makeMeSomeReleasesWithStaleFailure(t *testing.T, store *storage.Storage) {
t.Helper() t.Helper()
one := namedReleaseStub("clean", release.StatusDeployed) one := namedReleaseStub("clean", release.StatusDeployed)
one.Namespace = "default" one.Namespace = "default"
@ -242,7 +243,7 @@ func TestList_Filter(t *testing.T) {
is := assert.New(t) is := assert.New(t)
lister := newListFixture(t) lister := newListFixture(t)
lister.Filter = "th." lister.Filter = "th."
makeMeSomeReleases(lister.cfg.Releases, t) makeMeSomeReleases(t, lister.cfg.Releases)
res, err := lister.Run() res, err := lister.Run()
is.NoError(err) is.NoError(err)
@ -254,13 +255,13 @@ func TestList_FilterFailsCompile(t *testing.T) {
is := assert.New(t) is := assert.New(t)
lister := newListFixture(t) lister := newListFixture(t)
lister.Filter = "t[h.{{{" lister.Filter = "t[h.{{{"
makeMeSomeReleases(lister.cfg.Releases, t) makeMeSomeReleases(t, lister.cfg.Releases)
_, err := lister.Run() _, err := lister.Run()
is.Error(err) is.Error(err)
} }
func makeMeSomeReleases(store *storage.Storage, t *testing.T) { func makeMeSomeReleases(t *testing.T, store *storage.Storage) {
t.Helper() t.Helper()
one := releaseStub() one := releaseStub()
one.Name = "one" one.Name = "one"

@ -18,16 +18,16 @@ package action
import ( import (
"bufio" "bufio"
"errors"
"fmt" "fmt"
"os" "os"
"syscall" "syscall"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/pkg/errors"
"golang.org/x/term" "golang.org/x/term"
"helm.sh/helm/v4/pkg/chart/loader" "helm.sh/helm/v4/pkg/chart/v2/loader"
chartutil "helm.sh/helm/v4/pkg/chart/util" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/provenance" "helm.sh/helm/v4/pkg/provenance"
) )
@ -39,6 +39,7 @@ type Package struct {
Key string Key string
Keyring string Keyring string
PassphraseFile string PassphraseFile string
cachedPassphrase []byte
Version string Version string
AppVersion string AppVersion string
Destination string Destination string
@ -55,6 +56,10 @@ type Package struct {
InsecureSkipTLSverify bool InsecureSkipTLSverify bool
} }
const (
passPhraseFileStdin = "-"
)
// NewPackage creates a new Package object with the given configuration. // NewPackage creates a new Package object with the given configuration.
func NewPackage() *Package { func NewPackage() *Package {
return &Package{} return &Package{}
@ -100,7 +105,7 @@ func (p *Package) Run(path string, _ map[string]interface{}) (string, error) {
name, err := chartutil.Save(ch, dest) name, err := chartutil.Save(ch, dest)
if err != nil { if err != nil {
return "", errors.Wrap(err, "failed to save") return "", fmt.Errorf("failed to save: %w", err)
} }
if p.Sign { if p.Sign {
@ -128,7 +133,7 @@ func (p *Package) Clearsign(filename string) error {
passphraseFetcher := promptUser passphraseFetcher := promptUser
if p.PassphraseFile != "" { if p.PassphraseFile != "" {
passphraseFetcher, err = passphraseFileFetcher(p.PassphraseFile, os.Stdin) passphraseFetcher, err = p.passphraseFileFetcher(p.PassphraseFile, os.Stdin)
if err != nil { if err != nil {
return err return err
} }
@ -156,25 +161,42 @@ func promptUser(name string) ([]byte, error) {
return pw, err return pw, err
} }
func passphraseFileFetcher(passphraseFile string, stdin *os.File) (provenance.PassphraseFetcher, error) { func (p *Package) passphraseFileFetcher(passphraseFile string, stdin *os.File) (provenance.PassphraseFetcher, error) {
file, err := openPassphraseFile(passphraseFile, stdin) // When reading from stdin we cache the passphrase here. If we are
if err != nil { // packaging multiple charts, we reuse the cached passphrase. This
return nil, err // allows giving the passphrase once on stdin without failing with
} // complaints about stdin already being closed.
defer file.Close() //
// An alternative to this would be to omit file.Close() for stdin
// below and require the user to provide the same passphrase once
// per chart on stdin, but that does not seem very user-friendly.
if p.cachedPassphrase == nil {
file, err := openPassphraseFile(passphraseFile, stdin)
if err != nil {
return nil, err
}
defer file.Close()
reader := bufio.NewReader(file) reader := bufio.NewReader(file)
passphrase, _, err := reader.ReadLine() passphrase, _, err := reader.ReadLine()
if err != nil { if err != nil {
return nil, err return nil, err
}
p.cachedPassphrase = passphrase
return func(_ string) ([]byte, error) {
return passphrase, nil
}, nil
} }
return func(_ string) ([]byte, error) { return func(_ string) ([]byte, error) {
return passphrase, nil return p.cachedPassphrase, nil
}, nil }, nil
} }
func openPassphraseFile(passphraseFile string, stdin *os.File) (*os.File, error) { func openPassphraseFile(passphraseFile string, stdin *os.File) (*os.File, error) {
if passphraseFile == "-" { if passphraseFile == passPhraseFileStdin {
stat, err := stdin.Stat() stat, err := stdin.Stat()
if err != nil { if err != nil {
return nil, err return nil, err

@ -29,8 +29,9 @@ import (
func TestPassphraseFileFetcher(t *testing.T) { func TestPassphraseFileFetcher(t *testing.T) {
secret := "secret" secret := "secret"
directory := ensure.TempFile(t, "passphrase-file", []byte(secret)) directory := ensure.TempFile(t, "passphrase-file", []byte(secret))
testPkg := NewPackage()
fetcher, err := passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil) fetcher, err := testPkg.passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil)
if err != nil { if err != nil {
t.Fatal("Unable to create passphraseFileFetcher", err) t.Fatal("Unable to create passphraseFileFetcher", err)
} }
@ -48,8 +49,9 @@ func TestPassphraseFileFetcher(t *testing.T) {
func TestPassphraseFileFetcher_WithLineBreak(t *testing.T) { func TestPassphraseFileFetcher_WithLineBreak(t *testing.T) {
secret := "secret" secret := "secret"
directory := ensure.TempFile(t, "passphrase-file", []byte(secret+"\n\n.")) directory := ensure.TempFile(t, "passphrase-file", []byte(secret+"\n\n."))
testPkg := NewPackage()
fetcher, err := passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil) fetcher, err := testPkg.passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil)
if err != nil { if err != nil {
t.Fatal("Unable to create passphraseFileFetcher", err) t.Fatal("Unable to create passphraseFileFetcher", err)
} }
@ -66,17 +68,48 @@ func TestPassphraseFileFetcher_WithLineBreak(t *testing.T) {
func TestPassphraseFileFetcher_WithInvalidStdin(t *testing.T) { func TestPassphraseFileFetcher_WithInvalidStdin(t *testing.T) {
directory := t.TempDir() directory := t.TempDir()
testPkg := NewPackage()
stdin, err := os.CreateTemp(directory, "non-existing") stdin, err := os.CreateTemp(directory, "non-existing")
if err != nil { if err != nil {
t.Fatal("Unable to create test file", err) t.Fatal("Unable to create test file", err)
} }
if _, err := passphraseFileFetcher("-", stdin); err == nil { if _, err := testPkg.passphraseFileFetcher("-", stdin); err == nil {
t.Error("Expected passphraseFileFetcher returning an error") t.Error("Expected passphraseFileFetcher returning an error")
} }
} }
func TestPassphraseFileFetcher_WithStdinAndMultipleFetches(t *testing.T) {
testPkg := NewPackage()
stdin, w, err := os.Pipe()
if err != nil {
t.Fatal("Unable to create pipe", err)
}
passphrase := "secret-from-stdin"
go func() {
w.Write([]byte(passphrase + "\n"))
}()
for i := 0; i < 4; i++ {
fetcher, err := testPkg.passphraseFileFetcher("-", stdin)
if err != nil {
t.Errorf("Expected passphraseFileFetcher to not return an error, but got %v", err)
}
pass, err := fetcher("key")
if err != nil {
t.Errorf("Expected passphraseFileFetcher invocation to succeed, failed with %v", err)
}
if string(pass) != string(passphrase) {
t.Errorf("Expected multiple passphrase fetch to return %q, got %q", passphrase, pass)
}
}
}
func TestValidateVersion(t *testing.T) { func TestValidateVersion(t *testing.T) {
type args struct { type args struct {
ver string ver string

@ -22,9 +22,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/pkg/errors" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
chartutil "helm.sh/helm/v4/pkg/chart/util"
"helm.sh/helm/v4/pkg/cli" "helm.sh/helm/v4/pkg/cli"
"helm.sh/helm/v4/pkg/downloader" "helm.sh/helm/v4/pkg/downloader"
"helm.sh/helm/v4/pkg/getter" "helm.sh/helm/v4/pkg/getter"
@ -111,7 +109,7 @@ func (p *Pull) Run(chartRef string) (string, error) {
var err error var err error
dest, err = os.MkdirTemp("", "helm-") dest, err = os.MkdirTemp("", "helm-")
if err != nil { if err != nil {
return out.String(), errors.Wrap(err, "failed to untar") return out.String(), fmt.Errorf("failed to untar: %w", err)
} }
defer os.RemoveAll(dest) defer os.RemoveAll(dest)
} }
@ -163,11 +161,10 @@ func (p *Pull) Run(chartRef string) (string, error) {
if _, err := os.Stat(udCheck); err != nil { if _, err := os.Stat(udCheck); err != nil {
if err := os.MkdirAll(udCheck, 0755); err != nil { if err := os.MkdirAll(udCheck, 0755); err != nil {
return out.String(), errors.Wrap(err, "failed to untar (mkdir)") return out.String(), fmt.Errorf("failed to untar (mkdir): %w", err)
} }
} else { } else {
return out.String(), errors.Errorf("failed to untar: a file or directory with the name %s already exists", udCheck) return out.String(), fmt.Errorf("failed to untar: a file or directory with the name %s already exists", udCheck)
} }
return out.String(), chartutil.ExpandFile(ud, saved) return out.String(), chartutil.ExpandFile(ud, saved)

@ -24,11 +24,11 @@ import (
"sort" "sort"
"time" "time"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
chartutil "helm.sh/helm/v4/pkg/chart/util" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/kube"
release "helm.sh/helm/v4/pkg/release/v1"
) )
const ( const (
@ -63,7 +63,7 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) {
} }
if err := chartutil.ValidateReleaseName(name); err != nil { if err := chartutil.ValidateReleaseName(name); err != nil {
return nil, errors.Errorf("releaseTest: Release name is invalid: %s", name) return nil, fmt.Errorf("releaseTest: Release name is invalid: %s", name)
} }
// finds the non-deleted release with the given name // finds the non-deleted release with the given name
@ -96,7 +96,7 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) {
rel.Hooks = executingHooks rel.Hooks = executingHooks
} }
if err := r.cfg.execHook(rel, release.HookTest, r.Timeout); err != nil { if err := r.cfg.execHook(rel, release.HookTest, kube.StatusWatcherStrategy, r.Timeout); err != nil {
rel.Hooks = append(skippedHooks, rel.Hooks...) rel.Hooks = append(skippedHooks, rel.Hooks...)
r.cfg.Releases.Update(rel) r.cfg.Releases.Update(rel)
return rel, err return rel, err
@ -112,7 +112,7 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) {
func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error { func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error {
client, err := r.cfg.KubernetesClientSet() client, err := r.cfg.KubernetesClientSet()
if err != nil { if err != nil {
return errors.Wrap(err, "unable to get kubernetes client to fetch pod logs") return fmt.Errorf("unable to get kubernetes client to fetch pod logs: %w", err)
} }
hooksByWight := append([]*release.Hook{}, rel.Hooks...) hooksByWight := append([]*release.Hook{}, rel.Hooks...)
@ -129,14 +129,14 @@ func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error {
req := client.CoreV1().Pods(r.Namespace).GetLogs(h.Name, &v1.PodLogOptions{}) req := client.CoreV1().Pods(r.Namespace).GetLogs(h.Name, &v1.PodLogOptions{})
logReader, err := req.Stream(context.Background()) logReader, err := req.Stream(context.Background())
if err != nil { if err != nil {
return errors.Wrapf(err, "unable to get pod logs for %s", h.Name) return fmt.Errorf("unable to get pod logs for %s: %w", h.Name, err)
} }
fmt.Fprintf(out, "POD LOGS: %s\n", h.Name) fmt.Fprintf(out, "POD LOGS: %s\n", h.Name)
_, err = io.Copy(out, logReader) _, err = io.Copy(out, logReader)
fmt.Fprintln(out) fmt.Fprintln(out)
if err != nil { if err != nil {
return errors.Wrapf(err, "unable to write pod logs for %s", h.Name) return fmt.Errorf("unable to write pod logs for %s: %w", h.Name, err)
} }
} }
} }

@ -20,7 +20,7 @@ import (
"strings" "strings"
"helm.sh/helm/v4/pkg/kube" "helm.sh/helm/v4/pkg/kube"
"helm.sh/helm/v4/pkg/releaseutil" releaseutil "helm.sh/helm/v4/pkg/release/util"
) )
func filterManifestsToKeep(manifests []releaseutil.Manifest) (keep, remaining []releaseutil.Manifest) { func filterManifestsToKeep(manifests []releaseutil.Manifest) (keep, remaining []releaseutil.Manifest) {

@ -19,13 +19,13 @@ package action
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"log/slog"
"strings" "strings"
"time" "time"
"github.com/pkg/errors" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/kube"
chartutil "helm.sh/helm/v4/pkg/chart/util" release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/release"
helmtime "helm.sh/helm/v4/pkg/time" helmtime "helm.sh/helm/v4/pkg/time"
) )
@ -37,11 +37,10 @@ type Rollback struct {
Version int Version int
Timeout time.Duration Timeout time.Duration
Wait bool WaitStrategy kube.WaitStrategy
WaitForJobs bool WaitForJobs bool
DisableHooks bool DisableHooks bool
DryRun bool DryRun bool
Recreate bool // will (if true) recreate pods after a rollback.
Force bool // will (if true) force resource upgrade through uninstall/recreate if needed Force bool // will (if true) force resource upgrade through uninstall/recreate if needed
CleanupOnFail bool CleanupOnFail bool
MaxHistory int // MaxHistory limits the maximum number of revisions saved per release MaxHistory int // MaxHistory limits the maximum number of revisions saved per release
@ -62,26 +61,26 @@ func (r *Rollback) Run(name string) error {
r.cfg.Releases.MaxHistory = r.MaxHistory r.cfg.Releases.MaxHistory = r.MaxHistory
r.cfg.Log("preparing rollback of %s", name) slog.Debug("preparing rollback", "name", name)
currentRelease, targetRelease, err := r.prepareRollback(name) currentRelease, targetRelease, err := r.prepareRollback(name)
if err != nil { if err != nil {
return err return err
} }
if !r.DryRun { if !r.DryRun {
r.cfg.Log("creating rolled back release for %s", name) slog.Debug("creating rolled back release", "name", name)
if err := r.cfg.Releases.Create(targetRelease); err != nil { if err := r.cfg.Releases.Create(targetRelease); err != nil {
return err return err
} }
} }
r.cfg.Log("performing rollback of %s", name) slog.Debug("performing rollback", "name", name)
if _, err := r.performRollback(currentRelease, targetRelease); err != nil { if _, err := r.performRollback(currentRelease, targetRelease); err != nil {
return err return err
} }
if !r.DryRun { if !r.DryRun {
r.cfg.Log("updating status for rolled back release for %s", name) slog.Debug("updating status for rolled back release", "name", name)
if err := r.cfg.Releases.Update(targetRelease); err != nil { if err := r.cfg.Releases.Update(targetRelease); err != nil {
return err return err
} }
@ -93,7 +92,7 @@ func (r *Rollback) Run(name string) error {
// the previous release's configuration // the previous release's configuration
func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Release, error) { func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Release, error) {
if err := chartutil.ValidateReleaseName(name); err != nil { if err := chartutil.ValidateReleaseName(name); err != nil {
return nil, nil, errors.Errorf("prepareRollback: Release name is invalid: %s", name) return nil, nil, fmt.Errorf("prepareRollback: Release name is invalid: %s", name)
} }
if r.Version < 0 { if r.Version < 0 {
@ -125,10 +124,10 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele
} }
} }
if !previousVersionExist { if !previousVersionExist {
return nil, nil, errors.Errorf("release has no %d version", previousVersion) return nil, nil, fmt.Errorf("release has no %d version", previousVersion)
} }
r.cfg.Log("rolling back %s (current: v%d, target: v%d)", name, currentRelease.Version, previousVersion) slog.Debug("rolling back", "name", name, "currentVersion", currentRelease.Version, "targetVersion", previousVersion)
previousRelease, err := r.cfg.Releases.Get(name, previousVersion) previousRelease, err := r.cfg.Releases.Get(name, previousVersion)
if err != nil { if err != nil {
@ -161,89 +160,79 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele
func (r *Rollback) performRollback(currentRelease, targetRelease *release.Release) (*release.Release, error) { func (r *Rollback) performRollback(currentRelease, targetRelease *release.Release) (*release.Release, error) {
if r.DryRun { if r.DryRun {
r.cfg.Log("dry run for %s", targetRelease.Name) slog.Debug("dry run", "name", targetRelease.Name)
return targetRelease, nil return targetRelease, nil
} }
current, err := r.cfg.KubeClient.Build(bytes.NewBufferString(currentRelease.Manifest), false) current, err := r.cfg.KubeClient.Build(bytes.NewBufferString(currentRelease.Manifest), false)
if err != nil { if err != nil {
return targetRelease, errors.Wrap(err, "unable to build kubernetes objects from current release manifest") return targetRelease, fmt.Errorf("unable to build kubernetes objects from current release manifest: %w", err)
} }
target, err := r.cfg.KubeClient.Build(bytes.NewBufferString(targetRelease.Manifest), false) target, err := r.cfg.KubeClient.Build(bytes.NewBufferString(targetRelease.Manifest), false)
if err != nil { if err != nil {
return targetRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest") return targetRelease, fmt.Errorf("unable to build kubernetes objects from new release manifest: %w", err)
} }
// pre-rollback hooks // pre-rollback hooks
if !r.DisableHooks { if !r.DisableHooks {
if err := r.cfg.execHook(targetRelease, release.HookPreRollback, r.Timeout); err != nil { if err := r.cfg.execHook(targetRelease, release.HookPreRollback, r.WaitStrategy, r.Timeout); err != nil {
return targetRelease, err return targetRelease, err
} }
} else { } else {
r.cfg.Log("rollback hooks disabled for %s", targetRelease.Name) slog.Debug("rollback hooks disabled", "name", targetRelease.Name)
} }
// It is safe to use "force" here because these are resources currently rendered by the chart. // It is safe to use "force" here because these are resources currently rendered by the chart.
err = target.Visit(setMetadataVisitor(targetRelease.Name, targetRelease.Namespace, true)) err = target.Visit(setMetadataVisitor(targetRelease.Name, targetRelease.Namespace, true))
if err != nil { if err != nil {
return targetRelease, errors.Wrap(err, "unable to set metadata visitor from target release") return targetRelease, fmt.Errorf("unable to set metadata visitor from target release: %w", err)
} }
results, err := r.cfg.KubeClient.Update(current, target, r.Force) results, err := r.cfg.KubeClient.Update(current, target, r.Force)
if err != nil { if err != nil {
msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err)
r.cfg.Log("warning: %s", msg) slog.Warn(msg)
currentRelease.Info.Status = release.StatusSuperseded currentRelease.Info.Status = release.StatusSuperseded
targetRelease.Info.Status = release.StatusFailed targetRelease.Info.Status = release.StatusFailed
targetRelease.Info.Description = msg targetRelease.Info.Description = msg
r.cfg.recordRelease(currentRelease) r.cfg.recordRelease(currentRelease)
r.cfg.recordRelease(targetRelease) r.cfg.recordRelease(targetRelease)
if r.CleanupOnFail { if r.CleanupOnFail {
r.cfg.Log("Cleanup on fail set, cleaning up %d resources", len(results.Created)) slog.Debug("cleanup on fail set, cleaning up resources", "count", len(results.Created))
_, errs := r.cfg.KubeClient.Delete(results.Created) _, errs := r.cfg.KubeClient.Delete(results.Created)
if errs != nil { if errs != nil {
var errorList []string return targetRelease, fmt.Errorf(
for _, e := range errs { "an error occurred while cleaning up resources. original rollback error: %w",
errorList = append(errorList, e.Error()) fmt.Errorf("unable to cleanup resources: %w", joinErrors(errs, ", ")))
}
return targetRelease, errors.Wrapf(fmt.Errorf("unable to cleanup resources: %s", strings.Join(errorList, ", ")), "an error occurred while cleaning up resources. original rollback error: %s", err)
} }
r.cfg.Log("Resource cleanup complete") slog.Debug("resource cleanup complete")
} }
return targetRelease, err return targetRelease, err
} }
if r.Recreate { waiter, err := r.cfg.KubeClient.GetWaiter(r.WaitStrategy)
// NOTE: Because this is not critical for a release to succeed, we just if err != nil {
// log if an error occurs and continue onward. If we ever introduce log return nil, fmt.Errorf("unable to set metadata visitor from target release: %w", err)
// levels, we should make these error level logs so users are notified }
// that they'll need to go do the cleanup on their own if r.WaitForJobs {
if err := recreate(r.cfg, results.Updated); err != nil { if err := waiter.WaitWithJobs(target, r.Timeout); err != nil {
r.cfg.Log(err.Error()) targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error()))
r.cfg.recordRelease(currentRelease)
r.cfg.recordRelease(targetRelease)
return targetRelease, fmt.Errorf("release %s failed: %w", targetRelease.Name, err)
} }
} } else {
if err := waiter.Wait(target, r.Timeout); err != nil {
if r.Wait { targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error()))
if r.WaitForJobs { r.cfg.recordRelease(currentRelease)
if err := r.cfg.KubeClient.WaitWithJobs(target, r.Timeout); err != nil { r.cfg.recordRelease(targetRelease)
targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error())) return targetRelease, fmt.Errorf("release %s failed: %w", targetRelease.Name, err)
r.cfg.recordRelease(currentRelease)
r.cfg.recordRelease(targetRelease)
return targetRelease, errors.Wrapf(err, "release %s failed", targetRelease.Name)
}
} else {
if err := r.cfg.KubeClient.Wait(target, r.Timeout); err != nil {
targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error()))
r.cfg.recordRelease(currentRelease)
r.cfg.recordRelease(targetRelease)
return targetRelease, errors.Wrapf(err, "release %s failed", targetRelease.Name)
}
} }
} }
// post-rollback hooks // post-rollback hooks
if !r.DisableHooks { if !r.DisableHooks {
if err := r.cfg.execHook(targetRelease, release.HookPostRollback, r.Timeout); err != nil { if err := r.cfg.execHook(targetRelease, release.HookPostRollback, r.WaitStrategy, r.Timeout); err != nil {
return targetRelease, err return targetRelease, err
} }
} }
@ -254,7 +243,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas
} }
// Supersede all previous deployments, see issue #2941. // Supersede all previous deployments, see issue #2941.
for _, rel := range deployed { for _, rel := range deployed {
r.cfg.Log("superseding previous deployment %d", rel.Version) slog.Debug("superseding previous deployment", "version", rel.Version)
rel.Info.Status = release.StatusSuperseded rel.Info.Status = release.StatusSuperseded
r.cfg.recordRelease(rel) r.cfg.recordRelease(rel)
} }

@ -21,13 +21,12 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/pkg/errors"
"k8s.io/cli-runtime/pkg/printers" "k8s.io/cli-runtime/pkg/printers"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/chart/loader" "helm.sh/helm/v4/pkg/chart/v2/loader"
chartutil "helm.sh/helm/v4/pkg/chart/util" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/registry" "helm.sh/helm/v4/pkg/registry"
) )
@ -69,14 +68,14 @@ func NewShow(output ShowOutputFormat, cfg *Configuration) *Show {
sh := &Show{ sh := &Show{
OutputFormat: output, OutputFormat: output,
} }
sh.ChartPathOptions.registryClient = cfg.RegistryClient sh.registryClient = cfg.RegistryClient
return sh return sh
} }
// SetRegistryClient sets the registry client to use when pulling a chart from a registry. // SetRegistryClient sets the registry client to use when pulling a chart from a registry.
func (s *Show) SetRegistryClient(client *registry.Client) { func (s *Show) SetRegistryClient(client *registry.Client) {
s.ChartPathOptions.registryClient = client s.registryClient = client
} }
// Run executes 'helm show' against the given release. // Run executes 'helm show' against the given release.
@ -105,7 +104,7 @@ func (s *Show) Run(chartpath string) (string, error) {
if s.JSONPathTemplate != "" { if s.JSONPathTemplate != "" {
printer, err := printers.NewJSONPathPrinter(s.JSONPathTemplate) printer, err := printers.NewJSONPathPrinter(s.JSONPathTemplate)
if err != nil { if err != nil {
return "", errors.Wrapf(err, "error parsing jsonpath %s", s.JSONPathTemplate) return "", fmt.Errorf("error parsing jsonpath %s: %w", s.JSONPathTemplate, err)
} }
printer.Execute(&out, s.chart.Values) printer.Execute(&out, s.chart.Values)
} else { } else {

@ -19,7 +19,7 @@ package action
import ( import (
"testing" "testing"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
) )
func TestShow(t *testing.T) { func TestShow(t *testing.T) {

@ -21,7 +21,7 @@ import (
"errors" "errors"
"helm.sh/helm/v4/pkg/kube" "helm.sh/helm/v4/pkg/kube"
"helm.sh/helm/v4/pkg/release" release "helm.sh/helm/v4/pkg/release/v1"
) )
// Status is the action for checking the deployment status of releases. // Status is the action for checking the deployment status of releases.

@ -17,17 +17,17 @@ limitations under the License.
package action package action
import ( import (
"fmt"
"log/slog"
"strings" "strings"
"time" "time"
"github.com/pkg/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
chartutil "helm.sh/helm/v4/pkg/chart/util" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/kube" "helm.sh/helm/v4/pkg/kube"
"helm.sh/helm/v4/pkg/release" releaseutil "helm.sh/helm/v4/pkg/release/util"
"helm.sh/helm/v4/pkg/releaseutil" release "helm.sh/helm/v4/pkg/release/v1"
helmtime "helm.sh/helm/v4/pkg/time" helmtime "helm.sh/helm/v4/pkg/time"
) )
@ -41,7 +41,7 @@ type Uninstall struct {
DryRun bool DryRun bool
IgnoreNotFound bool IgnoreNotFound bool
KeepHistory bool KeepHistory bool
Wait bool WaitStrategy kube.WaitStrategy
DeletionPropagation string DeletionPropagation string
Timeout time.Duration Timeout time.Duration
Description string Description string
@ -60,6 +60,11 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
return nil, err return nil, err
} }
waiter, err := u.cfg.KubeClient.GetWaiter(u.WaitStrategy)
if err != nil {
return nil, err
}
if u.DryRun { if u.DryRun {
// In the dry run case, just see if the release exists // In the dry run case, just see if the release exists
r, err := u.cfg.releaseContent(name, 0) r, err := u.cfg.releaseContent(name, 0)
@ -70,7 +75,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
} }
if err := chartutil.ValidateReleaseName(name); err != nil { if err := chartutil.ValidateReleaseName(name); err != nil {
return nil, errors.Errorf("uninstall: Release name is invalid: %s", name) return nil, fmt.Errorf("uninstall: Release name is invalid: %s", name)
} }
rels, err := u.cfg.Releases.History(name) rels, err := u.cfg.Releases.History(name)
@ -78,7 +83,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
if u.IgnoreNotFound { if u.IgnoreNotFound {
return nil, nil return nil, nil
} }
return nil, errors.Wrapf(err, "uninstall: Release not loaded: %s", name) return nil, fmt.Errorf("uninstall: Release not loaded: %s: %w", name, err)
} }
if len(rels) < 1 { if len(rels) < 1 {
return nil, errMissingRelease return nil, errMissingRelease
@ -92,37 +97,37 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
if rel.Info.Status == release.StatusUninstalled { if rel.Info.Status == release.StatusUninstalled {
if !u.KeepHistory { if !u.KeepHistory {
if err := u.purgeReleases(rels...); err != nil { if err := u.purgeReleases(rels...); err != nil {
return nil, errors.Wrap(err, "uninstall: Failed to purge the release") return nil, fmt.Errorf("uninstall: Failed to purge the release: %w", err)
} }
return &release.UninstallReleaseResponse{Release: rel}, nil return &release.UninstallReleaseResponse{Release: rel}, nil
} }
return nil, errors.Errorf("the release named %q is already deleted", name) return nil, fmt.Errorf("the release named %q is already deleted", name)
} }
u.cfg.Log("uninstall: Deleting %s", name) slog.Debug("uninstall: deleting release", "name", name)
rel.Info.Status = release.StatusUninstalling rel.Info.Status = release.StatusUninstalling
rel.Info.Deleted = helmtime.Now() rel.Info.Deleted = helmtime.Now()
rel.Info.Description = "Deletion in progress (or silently failed)" rel.Info.Description = "Deletion in progress (or silently failed)"
res := &release.UninstallReleaseResponse{Release: rel} res := &release.UninstallReleaseResponse{Release: rel}
if !u.DisableHooks { if !u.DisableHooks {
if err := u.cfg.execHook(rel, release.HookPreDelete, u.Timeout); err != nil { if err := u.cfg.execHook(rel, release.HookPreDelete, u.WaitStrategy, u.Timeout); err != nil {
return res, err return res, err
} }
} else { } else {
u.cfg.Log("delete hooks disabled for %s", name) slog.Debug("delete hooks disabled", "release", name)
} }
// From here on out, the release is currently considered to be in StatusUninstalling // From here on out, the release is currently considered to be in StatusUninstalling
// state. // state.
if err := u.cfg.Releases.Update(rel); err != nil { if err := u.cfg.Releases.Update(rel); err != nil {
u.cfg.Log("uninstall: Failed to store updated release: %s", err) slog.Debug("uninstall: Failed to store updated release", slog.Any("error", err))
} }
deletedResources, kept, errs := u.deleteRelease(rel) deletedResources, kept, errs := u.deleteRelease(rel)
if errs != nil { if errs != nil {
u.cfg.Log("uninstall: Failed to delete release: %s", errs) slog.Debug("uninstall: Failed to delete release", slog.Any("error", errs))
return nil, errors.Errorf("failed to delete release: %s", name) return nil, fmt.Errorf("failed to delete release: %s", name)
} }
if kept != "" { if kept != "" {
@ -130,16 +135,12 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
} }
res.Info = kept res.Info = kept
if u.Wait { if err := waiter.WaitForDelete(deletedResources, u.Timeout); err != nil {
if kubeClient, ok := u.cfg.KubeClient.(kube.InterfaceExt); ok { errs = append(errs, err)
if err := kubeClient.WaitForDelete(deletedResources, u.Timeout); err != nil {
errs = append(errs, err)
}
}
} }
if !u.DisableHooks { if !u.DisableHooks {
if err := u.cfg.execHook(rel, release.HookPostDelete, u.Timeout); err != nil { if err := u.cfg.execHook(rel, release.HookPostDelete, u.WaitStrategy, u.Timeout); err != nil {
errs = append(errs, err) errs = append(errs, err)
} }
} }
@ -152,26 +153,26 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
} }
if !u.KeepHistory { if !u.KeepHistory {
u.cfg.Log("purge requested for %s", name) slog.Debug("purge requested", "release", name)
err := u.purgeReleases(rels...) err := u.purgeReleases(rels...)
if err != nil { if err != nil {
errs = append(errs, errors.Wrap(err, "uninstall: Failed to purge the release")) errs = append(errs, fmt.Errorf("uninstall: Failed to purge the release: %w", err))
} }
// Return the errors that occurred while deleting the release, if any // Return the errors that occurred while deleting the release, if any
if len(errs) > 0 { if len(errs) > 0 {
return res, errors.Errorf("uninstallation completed with %d error(s): %s", len(errs), joinErrors(errs)) return res, fmt.Errorf("uninstallation completed with %d error(s): %w", len(errs), joinErrors(errs, "; "))
} }
return res, nil return res, nil
} }
if err := u.cfg.Releases.Update(rel); err != nil { if err := u.cfg.Releases.Update(rel); err != nil {
u.cfg.Log("uninstall: Failed to store updated release: %s", err) slog.Debug("uninstall: Failed to store updated release", slog.Any("error", err))
} }
if len(errs) > 0 { if len(errs) > 0 {
return res, errors.Errorf("uninstallation completed with %d error(s): %s", len(errs), joinErrors(errs)) return res, fmt.Errorf("uninstallation completed with %d error(s): %w", len(errs), joinErrors(errs, "; "))
} }
return res, nil return res, nil
} }
@ -185,12 +186,28 @@ func (u *Uninstall) purgeReleases(rels ...*release.Release) error {
return nil return nil
} }
func joinErrors(errs []error) string { type joinedErrors struct {
es := make([]string, 0, len(errs)) errs []error
for _, e := range errs { sep string
es = append(es, e.Error()) }
func joinErrors(errs []error, sep string) error {
return &joinedErrors{
errs: errs,
sep: sep,
} }
return strings.Join(es, "; ") }
func (e *joinedErrors) Error() string {
errs := make([]string, 0, len(e.errs))
for _, err := range e.errs {
errs = append(errs, err.Error())
}
return strings.Join(errs, e.sep)
}
func (e *joinedErrors) Unwrap() []error {
return e.errs
} }
// deleteRelease deletes the release and returns list of delete resources and manifests that were kept in the deletion process // deleteRelease deletes the release and returns list of delete resources and manifests that were kept in the deletion process
@ -204,7 +221,7 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri
// FIXME: One way to delete at this point would be to try a label-based // FIXME: One way to delete at this point would be to try a label-based
// deletion. The problem with this is that we could get a false positive // deletion. The problem with this is that we could get a false positive
// and delete something that was not legitimately part of this release. // and delete something that was not legitimately part of this release.
return nil, rel.Manifest, []error{errors.Wrap(err, "corrupted release record. You must manually delete the resources")} return nil, rel.Manifest, []error{fmt.Errorf("corrupted release record. You must manually delete the resources: %w", err)}
} }
filesToKeep, filesToDelete := filterManifestsToKeep(files) filesToKeep, filesToDelete := filterManifestsToKeep(files)
@ -220,11 +237,11 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri
resources, err := u.cfg.KubeClient.Build(strings.NewReader(builder.String()), false) resources, err := u.cfg.KubeClient.Build(strings.NewReader(builder.String()), false)
if err != nil { if err != nil {
return nil, "", []error{errors.Wrap(err, "unable to build kubernetes objects for delete")} return nil, "", []error{fmt.Errorf("unable to build kubernetes objects for delete: %w", err)}
} }
if len(resources) > 0 { if len(resources) > 0 {
if kubeClient, ok := u.cfg.KubeClient.(kube.InterfaceDeletionPropagation); ok { if kubeClient, ok := u.cfg.KubeClient.(kube.InterfaceDeletionPropagation); ok {
_, errs = kubeClient.DeleteWithPropagationPolicy(resources, parseCascadingFlag(u.cfg, u.DeletionPropagation)) _, errs = kubeClient.DeleteWithPropagationPolicy(resources, parseCascadingFlag(u.DeletionPropagation))
return resources, kept, errs return resources, kept, errs
} }
_, errs = u.cfg.KubeClient.Delete(resources) _, errs = u.cfg.KubeClient.Delete(resources)
@ -232,7 +249,7 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri
return resources, kept, errs return resources, kept, errs
} }
func parseCascadingFlag(cfg *Configuration, cascadingFlag string) v1.DeletionPropagation { func parseCascadingFlag(cascadingFlag string) v1.DeletionPropagation {
switch cascadingFlag { switch cascadingFlag {
case "orphan": case "orphan":
return v1.DeletePropagationOrphan return v1.DeletePropagationOrphan
@ -241,7 +258,7 @@ func parseCascadingFlag(cfg *Configuration, cascadingFlag string) v1.DeletionPro
case "background": case "background":
return v1.DeletePropagationBackground return v1.DeletePropagationBackground
default: default:
cfg.Log("uninstall: given cascade value: %s, defaulting to delete propagation background", cascadingFlag) slog.Debug("uninstall: given cascade value, defaulting to delete propagation background", "value", cascadingFlag)
return v1.DeletePropagationBackground return v1.DeletePropagationBackground
} }
} }

@ -22,11 +22,13 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"helm.sh/helm/v4/pkg/kube"
kubefake "helm.sh/helm/v4/pkg/kube/fake" kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v4/pkg/release" release "helm.sh/helm/v4/pkg/release/v1"
) )
func uninstallAction(t *testing.T) *Uninstall { func uninstallAction(t *testing.T) *Uninstall {
t.Helper()
config := actionConfigFixture(t) config := actionConfigFixture(t)
unAction := NewUninstall(config) unAction := NewUninstall(config)
return unAction return unAction
@ -82,7 +84,7 @@ func TestUninstallRelease_Wait(t *testing.T) {
unAction := uninstallAction(t) unAction := uninstallAction(t)
unAction.DisableHooks = true unAction.DisableHooks = true
unAction.DryRun = false unAction.DryRun = false
unAction.Wait = true unAction.WaitStrategy = kube.StatusWatcherStrategy
rel := releaseStub() rel := releaseStub()
rel.Name = "come-fail-away" rel.Name = "come-fail-away"
@ -99,7 +101,7 @@ func TestUninstallRelease_Wait(t *testing.T) {
}` }`
unAction.cfg.Releases.Create(rel) unAction.cfg.Releases.Create(rel)
failer := unAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer := unAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
failer.WaitError = fmt.Errorf("U timed out") failer.WaitForDeleteError = fmt.Errorf("U timed out")
unAction.cfg.KubeClient = failer unAction.cfg.KubeClient = failer
res, err := unAction.Run(rel.Name) res, err := unAction.Run(rel.Name)
is.Error(err) is.Error(err)
@ -113,7 +115,7 @@ func TestUninstallRelease_Cascade(t *testing.T) {
unAction := uninstallAction(t) unAction := uninstallAction(t)
unAction.DisableHooks = true unAction.DisableHooks = true
unAction.DryRun = false unAction.DryRun = false
unAction.Wait = false unAction.WaitStrategy = kube.HookOnlyStrategy
unAction.DeletionPropagation = "foreground" unAction.DeletionPropagation = "foreground"
rel := releaseStub() rel := releaseStub()

@ -19,22 +19,22 @@ package action
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"log/slog"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/resource" "k8s.io/cli-runtime/pkg/resource"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
chartutil "helm.sh/helm/v4/pkg/chart/util" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/kube" "helm.sh/helm/v4/pkg/kube"
"helm.sh/helm/v4/pkg/postrender" "helm.sh/helm/v4/pkg/postrender"
"helm.sh/helm/v4/pkg/registry" "helm.sh/helm/v4/pkg/registry"
"helm.sh/helm/v4/pkg/release" releaseutil "helm.sh/helm/v4/pkg/release/util"
"helm.sh/helm/v4/pkg/releaseutil" release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/storage/driver" "helm.sh/helm/v4/pkg/storage/driver"
) )
@ -64,8 +64,8 @@ type Upgrade struct {
SkipCRDs bool SkipCRDs bool
// Timeout is the timeout for this operation // Timeout is the timeout for this operation
Timeout time.Duration Timeout time.Duration
// Wait determines whether the wait operation should be performed after the upgrade is requested. // WaitStrategy determines what type of waiting should be done
Wait bool WaitStrategy kube.WaitStrategy
// WaitForJobs determines whether the wait operation for the Jobs should be performed after the upgrade is requested. // WaitForJobs determines whether the wait operation for the Jobs should be performed after the upgrade is requested.
WaitForJobs bool WaitForJobs bool
// DisableHooks disables hook processing if set to true. // DisableHooks disables hook processing if set to true.
@ -87,8 +87,6 @@ type Upgrade struct {
ReuseValues bool ReuseValues bool
// ResetThenReuseValues will reset the values to the chart's built-ins then merge with user's last supplied values. // ResetThenReuseValues will reset the values to the chart's built-ins then merge with user's last supplied values.
ResetThenReuseValues bool ResetThenReuseValues bool
// Recreate will (if true) recreate pods after a rollback.
Recreate bool
// MaxHistory limits the maximum number of revisions saved per release // MaxHistory limits the maximum number of revisions saved per release
MaxHistory int MaxHistory int
// Atomic, if true, will roll back on failure. // Atomic, if true, will roll back on failure.
@ -131,14 +129,14 @@ func NewUpgrade(cfg *Configuration) *Upgrade {
up := &Upgrade{ up := &Upgrade{
cfg: cfg, cfg: cfg,
} }
up.ChartPathOptions.registryClient = cfg.RegistryClient up.registryClient = cfg.RegistryClient
return up return up
} }
// SetRegistryClient sets the registry client to use when fetching charts. // SetRegistryClient sets the registry client to use when fetching charts.
func (u *Upgrade) SetRegistryClient(client *registry.Client) { func (u *Upgrade) SetRegistryClient(client *registry.Client) {
u.ChartPathOptions.registryClient = client u.registryClient = client
} }
// Run executes the upgrade on the given release. // Run executes the upgrade on the given release.
@ -155,13 +153,15 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart.
// Make sure if Atomic is set, that wait is set as well. This makes it so // Make sure if Atomic is set, that wait is set as well. This makes it so
// the user doesn't have to specify both // the user doesn't have to specify both
u.Wait = u.Wait || u.Atomic if u.WaitStrategy == kube.HookOnlyStrategy && u.Atomic {
u.WaitStrategy = kube.StatusWatcherStrategy
}
if err := chartutil.ValidateReleaseName(name); err != nil { if err := chartutil.ValidateReleaseName(name); err != nil {
return nil, errors.Errorf("release name is invalid: %s", name) return nil, fmt.Errorf("release name is invalid: %s", name)
} }
u.cfg.Log("preparing upgrade for %s", name) slog.Debug("preparing upgrade", "name", name)
currentRelease, upgradedRelease, err := u.prepareUpgrade(name, chart, vals) currentRelease, upgradedRelease, err := u.prepareUpgrade(name, chart, vals)
if err != nil { if err != nil {
return nil, err return nil, err
@ -169,7 +169,7 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart.
u.cfg.Releases.MaxHistory = u.MaxHistory u.cfg.Releases.MaxHistory = u.MaxHistory
u.cfg.Log("performing update for %s", name) slog.Debug("performing update", "name", name)
res, err := u.performUpgrade(ctx, currentRelease, upgradedRelease) res, err := u.performUpgrade(ctx, currentRelease, upgradedRelease)
if err != nil { if err != nil {
return res, err return res, err
@ -177,7 +177,7 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart.
// Do not update for dry runs // Do not update for dry runs
if !u.isDryRun() { if !u.isDryRun() {
u.cfg.Log("updating status for upgraded release for %s", name) slog.Debug("updating status for upgraded release", "name", name)
if err := u.cfg.Releases.Update(upgradedRelease); err != nil { if err := u.cfg.Releases.Update(upgradedRelease); err != nil {
return res, err return res, err
} }
@ -202,7 +202,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin
// HideSecret must be used with dry run. Otherwise, return an error. // HideSecret must be used with dry run. Otherwise, return an error.
if !u.isDryRun() && u.HideSecret { if !u.isDryRun() && u.HideSecret {
return nil, nil, errors.New("Hiding Kubernetes secrets requires a dry-run mode") return nil, nil, errors.New("hiding Kubernetes secrets requires a dry-run mode")
} }
// finds the last non-deleted release with the given name // finds the last non-deleted release with the given name
@ -313,15 +313,15 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR
// Checking for removed Kubernetes API error so can provide a more informative error message to the user // Checking for removed Kubernetes API error so can provide a more informative error message to the user
// Ref: https://github.com/helm/helm/issues/7219 // Ref: https://github.com/helm/helm/issues/7219
if strings.Contains(err.Error(), "unable to recognize \"\": no matches for kind") { if strings.Contains(err.Error(), "unable to recognize \"\": no matches for kind") {
return upgradedRelease, errors.Wrap(err, "current release manifest contains removed kubernetes api(s) for this "+ return upgradedRelease, fmt.Errorf("current release manifest contains removed kubernetes api(s) for this "+
"kubernetes version and it is therefore unable to build the kubernetes "+ "kubernetes version and it is therefore unable to build the kubernetes "+
"objects for performing the diff. error from kubernetes") "objects for performing the diff. error from kubernetes: %w", err)
} }
return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from current release manifest") return upgradedRelease, fmt.Errorf("unable to build kubernetes objects from current release manifest: %w", err)
} }
target, err := u.cfg.KubeClient.Build(bytes.NewBufferString(upgradedRelease.Manifest), !u.DisableOpenAPIValidation) target, err := u.cfg.KubeClient.Build(bytes.NewBufferString(upgradedRelease.Manifest), !u.DisableOpenAPIValidation)
if err != nil { if err != nil {
return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest") return upgradedRelease, fmt.Errorf("unable to build kubernetes objects from new release manifest: %w", err)
} }
// It is safe to use force only on target because these are resources currently rendered by the chart. // It is safe to use force only on target because these are resources currently rendered by the chart.
@ -350,7 +350,7 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR
toBeUpdated, err = existingResourceConflict(toBeCreated, upgradedRelease.Name, upgradedRelease.Namespace) toBeUpdated, err = existingResourceConflict(toBeCreated, upgradedRelease.Name, upgradedRelease.Namespace)
} }
if err != nil { if err != nil {
return nil, errors.Wrap(err, "Unable to continue with update") return nil, fmt.Errorf("unable to continue with update: %w", err)
} }
toBeUpdated.Visit(func(r *resource.Info, err error) error { toBeUpdated.Visit(func(r *resource.Info, err error) error {
@ -363,7 +363,7 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR
// Run if it is a dry run // Run if it is a dry run
if u.isDryRun() { if u.isDryRun() {
u.cfg.Log("dry run for %s", upgradedRelease.Name) slog.Debug("dry run for release", "name", upgradedRelease.Name)
if len(u.Description) > 0 { if len(u.Description) > 0 {
upgradedRelease.Info.Description = u.Description upgradedRelease.Info.Description = u.Description
} else { } else {
@ -372,7 +372,7 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR
return upgradedRelease, nil return upgradedRelease, nil
} }
u.cfg.Log("creating upgraded release for %s", upgradedRelease.Name) slog.Debug("creating upgraded release", "name", upgradedRelease.Name)
if err := u.cfg.Releases.Create(upgradedRelease); err != nil { if err := u.cfg.Releases.Create(upgradedRelease); err != nil {
return nil, err return nil, err
} }
@ -418,12 +418,12 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele
// pre-upgrade hooks // pre-upgrade hooks
if !u.DisableHooks { if !u.DisableHooks {
if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.Timeout); err != nil { if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.WaitStrategy, u.Timeout); err != nil {
u.reportToPerformUpgrade(c, upgradedRelease, kube.ResourceList{}, fmt.Errorf("pre-upgrade hooks failed: %s", err)) u.reportToPerformUpgrade(c, upgradedRelease, kube.ResourceList{}, fmt.Errorf("pre-upgrade hooks failed: %s", err))
return return
} }
} else { } else {
u.cfg.Log("upgrade hooks disabled for %s", upgradedRelease.Name) slog.Debug("upgrade hooks disabled", "name", upgradedRelease.Name)
} }
results, err := u.cfg.KubeClient.Update(current, target, u.Force) results, err := u.cfg.KubeClient.Update(current, target, u.Force)
@ -433,38 +433,29 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele
return return
} }
if u.Recreate { waiter, err := u.cfg.KubeClient.GetWaiter(u.WaitStrategy)
// NOTE: Because this is not critical for a release to succeed, we just if err != nil {
// log if an error occurs and continue onward. If we ever introduce log u.cfg.recordRelease(originalRelease)
// levels, we should make these error level logs so users are notified u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err)
// that they'll need to go do the cleanup on their own return
if err := recreate(u.cfg, results.Updated); err != nil {
u.cfg.Log(err.Error())
}
} }
if u.WaitForJobs {
if u.Wait { if err := waiter.WaitWithJobs(target, u.Timeout); err != nil {
u.cfg.Log( u.cfg.recordRelease(originalRelease)
"waiting for release %s resources (created: %d updated: %d deleted: %d)", u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err)
upgradedRelease.Name, len(results.Created), len(results.Updated), len(results.Deleted)) return
if u.WaitForJobs { }
if err := u.cfg.KubeClient.WaitWithJobs(target, u.Timeout); err != nil { } else {
u.cfg.recordRelease(originalRelease) if err := waiter.Wait(target, u.Timeout); err != nil {
u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) u.cfg.recordRelease(originalRelease)
return u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err)
} return
} else {
if err := u.cfg.KubeClient.Wait(target, u.Timeout); err != nil {
u.cfg.recordRelease(originalRelease)
u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err)
return
}
} }
} }
// post-upgrade hooks // post-upgrade hooks
if !u.DisableHooks { if !u.DisableHooks {
if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.Timeout); err != nil { if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.WaitStrategy, u.Timeout); err != nil {
u.reportToPerformUpgrade(c, upgradedRelease, results.Created, fmt.Errorf("post-upgrade hooks failed: %s", err)) u.reportToPerformUpgrade(c, upgradedRelease, results.Created, fmt.Errorf("post-upgrade hooks failed: %s", err))
return return
} }
@ -484,32 +475,35 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele
func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, err error) (*release.Release, error) { func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, err error) (*release.Release, error) {
msg := fmt.Sprintf("Upgrade %q failed: %s", rel.Name, err) msg := fmt.Sprintf("Upgrade %q failed: %s", rel.Name, err)
u.cfg.Log("warning: %s", msg) slog.Warn("upgrade failed", "name", rel.Name, slog.Any("error", err))
rel.Info.Status = release.StatusFailed rel.Info.Status = release.StatusFailed
rel.Info.Description = msg rel.Info.Description = msg
u.cfg.recordRelease(rel) u.cfg.recordRelease(rel)
if u.CleanupOnFail && len(created) > 0 { if u.CleanupOnFail && len(created) > 0 {
u.cfg.Log("Cleanup on fail set, cleaning up %d resources", len(created)) slog.Debug("cleanup on fail set", "cleaning_resources", len(created))
_, errs := u.cfg.KubeClient.Delete(created) _, errs := u.cfg.KubeClient.Delete(created)
if errs != nil { if errs != nil {
var errorList []string return rel, fmt.Errorf(
for _, e := range errs { "an error occurred while cleaning up resources. original upgrade error: %w: %w",
errorList = append(errorList, e.Error()) err,
} fmt.Errorf(
return rel, errors.Wrapf(fmt.Errorf("unable to cleanup resources: %s", strings.Join(errorList, ", ")), "an error occurred while cleaning up resources. original upgrade error: %s", err) "unable to cleanup resources: %w",
joinErrors(errs, ", "),
),
)
} }
u.cfg.Log("Resource cleanup complete") slog.Debug("resource cleanup complete")
} }
if u.Atomic { if u.Atomic {
u.cfg.Log("Upgrade failed and atomic is set, rolling back to last successful release") slog.Debug("upgrade failed and atomic is set, rolling back to last successful release")
// As a protection, get the last successful release before rollback. // As a protection, get the last successful release before rollback.
// If there are no successful releases, bail out // If there are no successful releases, bail out
hist := NewHistory(u.cfg) hist := NewHistory(u.cfg)
fullHistory, herr := hist.Run(rel.Name) fullHistory, herr := hist.Run(rel.Name)
if herr != nil { if herr != nil {
return rel, errors.Wrapf(herr, "an error occurred while finding last successful release. original upgrade error: %s", err) return rel, fmt.Errorf("an error occurred while finding last successful release. original upgrade error: %w: %w", err, herr)
} }
// There isn't a way to tell if a previous release was successful, but // There isn't a way to tell if a previous release was successful, but
@ -519,23 +513,24 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e
return r.Info.Status == release.StatusSuperseded || r.Info.Status == release.StatusDeployed return r.Info.Status == release.StatusSuperseded || r.Info.Status == release.StatusDeployed
}).Filter(fullHistory) }).Filter(fullHistory)
if len(filteredHistory) == 0 { if len(filteredHistory) == 0 {
return rel, errors.Wrap(err, "unable to find a previously successful release when attempting to rollback. original upgrade error") return rel, fmt.Errorf("unable to find a previously successful release when attempting to rollback. original upgrade error: %w", err)
} }
releaseutil.Reverse(filteredHistory, releaseutil.SortByRevision) releaseutil.Reverse(filteredHistory, releaseutil.SortByRevision)
rollin := NewRollback(u.cfg) rollin := NewRollback(u.cfg)
rollin.Version = filteredHistory[0].Version rollin.Version = filteredHistory[0].Version
rollin.Wait = true if u.WaitStrategy == kube.HookOnlyStrategy {
rollin.WaitStrategy = kube.StatusWatcherStrategy
}
rollin.WaitForJobs = u.WaitForJobs rollin.WaitForJobs = u.WaitForJobs
rollin.DisableHooks = u.DisableHooks rollin.DisableHooks = u.DisableHooks
rollin.Recreate = u.Recreate
rollin.Force = u.Force rollin.Force = u.Force
rollin.Timeout = u.Timeout rollin.Timeout = u.Timeout
if rollErr := rollin.Run(rel.Name); rollErr != nil { if rollErr := rollin.Run(rel.Name); rollErr != nil {
return rel, errors.Wrapf(rollErr, "an error occurred while rolling back the release. original upgrade error: %s", err) return rel, fmt.Errorf("an error occurred while rolling back the release. original upgrade error: %w: %w", err, rollErr)
} }
return rel, errors.Wrapf(err, "release %s failed, and has been rolled back due to atomic being set", rel.Name) return rel, fmt.Errorf("release %s failed, and has been rolled back due to atomic being set: %w", rel.Name, err)
} }
return rel, err return rel, err
@ -552,18 +547,18 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e
func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release, newVals map[string]interface{}) (map[string]interface{}, error) { func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release, newVals map[string]interface{}) (map[string]interface{}, error) {
if u.ResetValues { if u.ResetValues {
// If ResetValues is set, we completely ignore current.Config. // If ResetValues is set, we completely ignore current.Config.
u.cfg.Log("resetting values to the chart's original version") slog.Debug("resetting values to the chart's original version")
return newVals, nil return newVals, nil
} }
// If the ReuseValues flag is set, we always copy the old values over the new config's values. // If the ReuseValues flag is set, we always copy the old values over the new config's values.
if u.ReuseValues { if u.ReuseValues {
u.cfg.Log("reusing the old release's values") slog.Debug("reusing the old release's values")
// We have to regenerate the old coalesced values: // We have to regenerate the old coalesced values:
oldVals, err := chartutil.CoalesceValues(current.Chart, current.Config) oldVals, err := chartutil.CoalesceValues(current.Chart, current.Config)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to rebuild old values") return nil, fmt.Errorf("failed to rebuild old values: %w", err)
} }
newVals = chartutil.CoalesceTables(newVals, current.Config) newVals = chartutil.CoalesceTables(newVals, current.Config)
@ -575,7 +570,7 @@ func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release, newV
// If the ResetThenReuseValues flag is set, we use the new chart's values, but we copy the old config's values over the new config's values. // If the ResetThenReuseValues flag is set, we use the new chart's values, but we copy the old config's values over the new config's values.
if u.ResetThenReuseValues { if u.ResetThenReuseValues {
u.cfg.Log("merging values from old release to new values") slog.Debug("merging values from old release to new values")
newVals = chartutil.CoalesceTables(newVals, current.Config) newVals = chartutil.CoalesceTables(newVals, current.Config)
@ -583,7 +578,7 @@ func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release, newV
} }
if len(newVals) == 0 && len(current.Config) > 0 { if len(newVals) == 0 && len(current.Config) > 0 {
u.cfg.Log("copying values from %s (v%d) to new release.", current.Name, current.Version) slog.Debug("copying values from old release", "name", current.Name, "version", current.Version)
newVals = current.Config newVals = current.Config
} }
return newVals, nil return newVals, nil
@ -594,42 +589,6 @@ func validateManifest(c kube.Interface, manifest []byte, openAPIValidation bool)
return err return err
} }
// recreate captures all the logic for recreating pods for both upgrade and
// rollback. If we end up refactoring rollback to use upgrade, this can just be
// made an unexported method on the upgrade action.
func recreate(cfg *Configuration, resources kube.ResourceList) error {
for _, res := range resources {
versioned := kube.AsVersioned(res)
selector, err := kube.SelectorsForObject(versioned)
if err != nil {
// If no selector is returned, it means this object is
// definitely not a pod, so continue onward
continue
}
client, err := cfg.KubernetesClientSet()
if err != nil {
return errors.Wrapf(err, "unable to recreate pods for object %s/%s because an error occurred", res.Namespace, res.Name)
}
pods, err := client.CoreV1().Pods(res.Namespace).List(context.Background(), metav1.ListOptions{
LabelSelector: selector.String(),
})
if err != nil {
return errors.Wrapf(err, "unable to recreate pods for object %s/%s because an error occurred", res.Namespace, res.Name)
}
// Restart pods
for _, pod := range pods.Items {
// Delete each pod for get them restarted with changed spec.
if err := client.CoreV1().Pods(pod.Namespace).Delete(context.Background(), pod.Name, *metav1.NewPreconditionDeleteOptions(string(pod.UID))); err != nil {
return errors.Wrapf(err, "unable to recreate pods for object %s/%s because an error occurred", res.Namespace, res.Name)
}
}
}
return nil
}
func objectKey(r *resource.Info) string { func objectKey(r *resource.Info) string {
gvk := r.Object.GetObjectKind().GroupVersionKind() gvk := r.Object.GetObjectKind().GroupVersionKind()
return fmt.Sprintf("%s/%s/%s/%s", gvk.GroupVersion().String(), gvk.Kind, r.Namespace, r.Name) return fmt.Sprintf("%s/%s/%s/%s", gvk.GroupVersion().String(), gvk.Kind, r.Namespace, r.Name)

@ -23,18 +23,20 @@ import (
"testing" "testing"
"time" "time"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/kube"
"helm.sh/helm/v4/pkg/storage/driver" "helm.sh/helm/v4/pkg/storage/driver"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
kubefake "helm.sh/helm/v4/pkg/kube/fake" kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v4/pkg/release" release "helm.sh/helm/v4/pkg/release/v1"
helmtime "helm.sh/helm/v4/pkg/time" helmtime "helm.sh/helm/v4/pkg/time"
) )
func upgradeAction(t *testing.T) *Upgrade { func upgradeAction(t *testing.T) *Upgrade {
t.Helper()
config := actionConfigFixture(t) config := actionConfigFixture(t)
upAction := NewUpgrade(config) upAction := NewUpgrade(config)
upAction.Namespace = "spaced" upAction.Namespace = "spaced"
@ -52,10 +54,10 @@ func TestUpgradeRelease_Success(t *testing.T) {
rel.Info.Status = release.StatusDeployed rel.Info.Status = release.StatusDeployed
req.NoError(upAction.cfg.Releases.Create(rel)) req.NoError(upAction.cfg.Releases.Create(rel))
upAction.Wait = true upAction.WaitStrategy = kube.StatusWatcherStrategy
vals := map[string]interface{}{} vals := map[string]interface{}{}
ctx, done := context.WithCancel(context.Background()) ctx, done := context.WithCancel(t.Context())
res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals)
done() done()
req.NoError(err) req.NoError(err)
@ -82,7 +84,7 @@ func TestUpgradeRelease_Wait(t *testing.T) {
failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
failer.WaitError = fmt.Errorf("I timed out") failer.WaitError = fmt.Errorf("I timed out")
upAction.cfg.KubeClient = failer upAction.cfg.KubeClient = failer
upAction.Wait = true upAction.WaitStrategy = kube.StatusWatcherStrategy
vals := map[string]interface{}{} vals := map[string]interface{}{}
res, err := upAction.Run(rel.Name, buildChart(), vals) res, err := upAction.Run(rel.Name, buildChart(), vals)
@ -104,7 +106,7 @@ func TestUpgradeRelease_WaitForJobs(t *testing.T) {
failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
failer.WaitError = fmt.Errorf("I timed out") failer.WaitError = fmt.Errorf("I timed out")
upAction.cfg.KubeClient = failer upAction.cfg.KubeClient = failer
upAction.Wait = true upAction.WaitStrategy = kube.StatusWatcherStrategy
upAction.WaitForJobs = true upAction.WaitForJobs = true
vals := map[string]interface{}{} vals := map[string]interface{}{}
@ -128,7 +130,7 @@ func TestUpgradeRelease_CleanupOnFail(t *testing.T) {
failer.WaitError = fmt.Errorf("I timed out") failer.WaitError = fmt.Errorf("I timed out")
failer.DeleteError = fmt.Errorf("I tried to delete nil") failer.DeleteError = fmt.Errorf("I tried to delete nil")
upAction.cfg.KubeClient = failer upAction.cfg.KubeClient = failer
upAction.Wait = true upAction.WaitStrategy = kube.StatusWatcherStrategy
upAction.CleanupOnFail = true upAction.CleanupOnFail = true
vals := map[string]interface{}{} vals := map[string]interface{}{}
@ -382,7 +384,6 @@ func TestUpgradeRelease_Pending(t *testing.T) {
} }
func TestUpgradeRelease_Interrupted_Wait(t *testing.T) { func TestUpgradeRelease_Interrupted_Wait(t *testing.T) {
is := assert.New(t) is := assert.New(t)
req := require.New(t) req := require.New(t)
@ -395,11 +396,10 @@ func TestUpgradeRelease_Interrupted_Wait(t *testing.T) {
failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
failer.WaitDuration = 10 * time.Second failer.WaitDuration = 10 * time.Second
upAction.cfg.KubeClient = failer upAction.cfg.KubeClient = failer
upAction.Wait = true upAction.WaitStrategy = kube.StatusWatcherStrategy
vals := map[string]interface{}{} vals := map[string]interface{}{}
ctx := context.Background() ctx, cancel := context.WithCancel(t.Context())
ctx, cancel := context.WithCancel(ctx)
time.AfterFunc(time.Second, cancel) time.AfterFunc(time.Second, cancel)
res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals)
@ -407,11 +407,9 @@ func TestUpgradeRelease_Interrupted_Wait(t *testing.T) {
req.Error(err) req.Error(err)
is.Contains(res.Info.Description, "Upgrade \"interrupted-release\" failed: context canceled") is.Contains(res.Info.Description, "Upgrade \"interrupted-release\" failed: context canceled")
is.Equal(res.Info.Status, release.StatusFailed) is.Equal(res.Info.Status, release.StatusFailed)
} }
func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) { func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) {
is := assert.New(t) is := assert.New(t)
req := require.New(t) req := require.New(t)
@ -427,8 +425,7 @@ func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) {
upAction.Atomic = true upAction.Atomic = true
vals := map[string]interface{}{} vals := map[string]interface{}{}
ctx := context.Background() ctx, cancel := context.WithCancel(t.Context())
ctx, cancel := context.WithCancel(ctx)
time.AfterFunc(time.Second, cancel) time.AfterFunc(time.Second, cancel)
res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals)
@ -444,7 +441,7 @@ func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) {
} }
func TestMergeCustomLabels(t *testing.T) { func TestMergeCustomLabels(t *testing.T) {
var tests = [][3]map[string]string{ tests := [][3]map[string]string{
{nil, nil, map[string]string{}}, {nil, nil, map[string]string{}},
{map[string]string{}, map[string]string{}, map[string]string{}}, {map[string]string{}, map[string]string{}, map[string]string{}},
{map[string]string{"k1": "v1", "k2": "v2"}, nil, map[string]string{"k1": "v1", "k2": "v2"}}, {map[string]string{"k1": "v1", "k2": "v2"}, nil, map[string]string{"k1": "v1", "k2": "v2"}},
@ -549,7 +546,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) {
upAction.DryRun = true upAction.DryRun = true
vals := map[string]interface{}{} vals := map[string]interface{}{}
ctx, done := context.WithCancel(context.Background()) ctx, done := context.WithCancel(t.Context())
res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals) res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals)
done() done()
req.NoError(err) req.NoError(err)
@ -565,7 +562,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) {
upAction.HideSecret = true upAction.HideSecret = true
vals = map[string]interface{}{} vals = map[string]interface{}{}
ctx, done = context.WithCancel(context.Background()) ctx, done = context.WithCancel(t.Context())
res, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals) res, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals)
done() done()
req.NoError(err) req.NoError(err)
@ -581,7 +578,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) {
upAction.DryRun = false upAction.DryRun = false
vals = map[string]interface{}{} vals = map[string]interface{}{}
ctx, done = context.WithCancel(context.Background()) ctx, done = context.WithCancel(t.Context())
_, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals) _, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals)
done() done()
req.Error(err) req.Error(err)

@ -18,8 +18,8 @@ package action
import ( import (
"fmt" "fmt"
"maps"
"github.com/pkg/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -52,7 +52,7 @@ func requireAdoption(resources kube.ResourceList) (kube.ResourceList, error) {
if apierrors.IsNotFound(err) { if apierrors.IsNotFound(err) {
return nil return nil
} }
return errors.Wrapf(err, "could not get information about the resource %s", resourceString(info)) return fmt.Errorf("could not get information about the resource %s: %w", resourceString(info), err)
} }
requireUpdate.Append(info) requireUpdate.Append(info)
@ -76,7 +76,7 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN
if apierrors.IsNotFound(err) { if apierrors.IsNotFound(err) {
return nil return nil
} }
return errors.Wrapf(err, "could not get information about the resource %s", resourceString(info)) return fmt.Errorf("could not get information about the resource %s: %w", resourceString(info), err)
} }
// Allow adoption of the resource if it is managed by Helm and is annotated with correct release name and namespace. // Allow adoption of the resource if it is managed by Helm and is annotated with correct release name and namespace.
@ -113,11 +113,7 @@ func checkOwnership(obj runtime.Object, releaseName, releaseNamespace string) er
} }
if len(errs) > 0 { if len(errs) > 0 {
err := errors.New("invalid ownership metadata") return fmt.Errorf("invalid ownership metadata; %w", joinErrors(errs, "; "))
for _, e := range errs {
err = fmt.Errorf("%w; %s", err, e)
}
return err
} }
return nil return nil
@ -199,11 +195,7 @@ func mergeAnnotations(obj runtime.Object, annotations map[string]string) error {
// merge two maps, always taking the value on the right // merge two maps, always taking the value on the right
func mergeStrStrMaps(current, desired map[string]string) map[string]string { func mergeStrStrMaps(current, desired map[string]string) map[string]string {
result := make(map[string]string) result := make(map[string]string)
for k, v := range current { maps.Copy(result, current)
result[k] = v maps.Copy(result, desired)
}
for k, desiredVal := range desired {
result[k] = desiredVal
}
return result return result
} }

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chart package v2
import ( import (
"path/filepath" "path/filepath"
@ -113,6 +113,8 @@ func (ch *Chart) ChartPath() string {
} }
// ChartFullPath returns the full path to this chart. // ChartFullPath returns the full path to this chart.
// Note that the path may not correspond to the path where the file can be found on the file system if the path
// points to an aliased subchart.
func (ch *Chart) ChartFullPath() string { func (ch *Chart) ChartFullPath() string {
if !ch.IsRoot() { if !ch.IsRoot() {
return ch.Parent().ChartFullPath() + "/charts/" + ch.Name() return ch.Parent().ChartFullPath() + "/charts/" + ch.Name()

@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chart package v2
import ( import (
"encoding/json" "encoding/json"

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chart package v2
import "time" import "time"

@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chart package v2
import ( import (
"testing" "testing"

@ -0,0 +1,23 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
Package v2 provides chart handling for apiVersion v1 and v2 charts
This package and its sub-packages provide handling for apiVersion v1 and v2 charts.
The changes from v1 to v2 charts are minor and were able to be handled with minor
switches based on characteristics.
*/
package v2

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chart package v2
import "fmt" import "fmt"

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chart package v2
// File represents a file as a name/value pair. // File represents a file as a name/value pair.
// //

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package chart package v2
import ( import (
"testing" "testing"

@ -20,6 +20,7 @@ import (
"archive/tar" "archive/tar"
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@ -28,11 +29,18 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/pkg/errors" chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/chart"
) )
// MaxDecompressedChartSize is the maximum size of a chart archive that will be
// decompressed. This is the decompressed size of all the files.
// The default value is 100 MiB.
var MaxDecompressedChartSize int64 = 100 * 1024 * 1024 // Default 100 MiB
// MaxDecompressedFileSize is the size of the largest file that Helm will attempt to load.
// The size of the file is the decompressed version of it when it is stored in an archive.
var MaxDecompressedFileSize int64 = 5 * 1024 * 1024 // Default 5 MiB
var drivePathPattern = regexp.MustCompile(`^[a-zA-Z]:/`) var drivePathPattern = regexp.MustCompile(`^[a-zA-Z]:/`)
// FileLoader loads a chart from a file // FileLoader loads a chart from a file
@ -119,6 +127,7 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) {
files := []*BufferedFile{} files := []*BufferedFile{}
tr := tar.NewReader(unzipped) tr := tar.NewReader(unzipped)
remainingSize := MaxDecompressedChartSize
for { for {
b := bytes.NewBuffer(nil) b := bytes.NewBuffer(nil)
hd, err := tr.Next() hd, err := tr.Next()
@ -160,7 +169,7 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) {
n = path.Clean(n) n = path.Clean(n)
if n == "." { if n == "." {
// In this case, the original path was relative when it should have been absolute. // In this case, the original path was relative when it should have been absolute.
return nil, errors.Errorf("chart illegally contains content outside the base directory: %q", hd.Name) return nil, fmt.Errorf("chart illegally contains content outside the base directory: %q", hd.Name)
} }
if strings.HasPrefix(n, "..") { if strings.HasPrefix(n, "..") {
return nil, errors.New("chart illegally references parent directory") return nil, errors.New("chart illegally references parent directory")
@ -178,10 +187,30 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) {
return nil, errors.New("chart yaml not in base directory") return nil, errors.New("chart yaml not in base directory")
} }
if _, err := io.Copy(b, tr); err != nil { if hd.Size > remainingSize {
return nil, fmt.Errorf("decompressed chart is larger than the maximum size %d", MaxDecompressedChartSize)
}
if hd.Size > MaxDecompressedFileSize {
return nil, fmt.Errorf("decompressed chart file %q is larger than the maximum file size %d", hd.Name, MaxDecompressedFileSize)
}
limitedReader := io.LimitReader(tr, remainingSize)
bytesWritten, err := io.Copy(b, limitedReader)
if err != nil {
return nil, err return nil, err
} }
remainingSize -= bytesWritten
// When the bytesWritten are less than the file size it means the limit reader ended
// copying early. Here we report that error. This is important if the last file extracted
// is the one that goes over the limit. It assumes the Size stored in the tar header
// is correct, something many applications do.
if bytesWritten < hd.Size || remainingSize <= 0 {
return nil, fmt.Errorf("decompressed chart is larger than the maximum size %d", MaxDecompressedChartSize)
}
data := bytes.TrimPrefix(b.Bytes(), utf8bom) data := bytes.TrimPrefix(b.Bytes(), utf8bom)
files = append(files, &BufferedFile{Name: n, Data: data}) files = append(files, &BufferedFile{Name: n, Data: data})

@ -33,6 +33,7 @@ func TestLoadArchiveFiles(t *testing.T) {
name: "empty input should return no files", name: "empty input should return no files",
generate: func(_ *tar.Writer) {}, generate: func(_ *tar.Writer) {},
check: func(t *testing.T, _ []*BufferedFile, err error) { check: func(t *testing.T, _ []*BufferedFile, err error) {
t.Helper()
if err.Error() != "no files in chart archive" { if err.Error() != "no files in chart archive" {
t.Fatalf(`expected "no files in chart archive", got [%#v]`, err) t.Fatalf(`expected "no files in chart archive", got [%#v]`, err)
} }
@ -61,6 +62,7 @@ func TestLoadArchiveFiles(t *testing.T) {
} }
}, },
check: func(t *testing.T, files []*BufferedFile, err error) { check: func(t *testing.T, files []*BufferedFile, err error) {
t.Helper()
if err != nil { if err != nil {
t.Fatalf(`got unwanted error [%#v] for tar file with pax_global_header content`, err) t.Fatalf(`got unwanted error [%#v] for tar file with pax_global_header content`, err)
} }

@ -23,10 +23,8 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/pkg/errors"
"helm.sh/helm/v4/internal/sympath" "helm.sh/helm/v4/internal/sympath"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/ignore" "helm.sh/helm/v4/pkg/ignore"
) )
@ -101,9 +99,13 @@ func LoadDir(dir string) (*chart.Chart, error) {
return fmt.Errorf("cannot load irregular file %s as it has file mode type bits set", name) return fmt.Errorf("cannot load irregular file %s as it has file mode type bits set", name)
} }
if fi.Size() > MaxDecompressedFileSize {
return fmt.Errorf("chart file %q is larger than the maximum file size %d", fi.Name(), MaxDecompressedFileSize)
}
data, err := os.ReadFile(name) data, err := os.ReadFile(name)
if err != nil { if err != nil {
return errors.Wrapf(err, "error reading %s", n) return fmt.Errorf("error reading %s: %w", n, err)
} }
data = bytes.TrimPrefix(data, utf8bom) data = bytes.TrimPrefix(data, utf8bom)

@ -19,18 +19,19 @@ package loader
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"encoding/json" "errors"
"fmt"
"io" "io"
"log" "log"
"maps"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/pkg/errors"
utilyaml "k8s.io/apimachinery/pkg/util/yaml" utilyaml "k8s.io/apimachinery/pkg/util/yaml"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
) )
// ChartLoader loads a chart. // ChartLoader loads a chart.
@ -48,7 +49,6 @@ func Loader(name string) (ChartLoader, error) {
return DirLoader(name), nil return DirLoader(name), nil
} }
return FileLoader(name), nil return FileLoader(name), nil
} }
// Load takes a string name, tries to resolve it to a file or directory, and then loads it. // Load takes a string name, tries to resolve it to a file or directory, and then loads it.
@ -86,7 +86,7 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
c.Metadata = new(chart.Metadata) c.Metadata = new(chart.Metadata)
} }
if err := yaml.Unmarshal(f.Data, c.Metadata); err != nil { if err := yaml.Unmarshal(f.Data, c.Metadata); err != nil {
return c, errors.Wrap(err, "cannot load Chart.yaml") return c, fmt.Errorf("cannot load Chart.yaml: %w", err)
} }
// NOTE(bacongobbler): while the chart specification says that APIVersion must be set, // NOTE(bacongobbler): while the chart specification says that APIVersion must be set,
// Helm 2 accepted charts that did not provide an APIVersion in their chart metadata. // Helm 2 accepted charts that did not provide an APIVersion in their chart metadata.
@ -104,12 +104,12 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
case f.Name == "Chart.lock": case f.Name == "Chart.lock":
c.Lock = new(chart.Lock) c.Lock = new(chart.Lock)
if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil { if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil {
return c, errors.Wrap(err, "cannot load Chart.lock") return c, fmt.Errorf("cannot load Chart.lock: %w", err)
} }
case f.Name == "values.yaml": case f.Name == "values.yaml":
values, err := LoadValues(bytes.NewReader(f.Data)) values, err := LoadValues(bytes.NewReader(f.Data))
if err != nil { if err != nil {
return c, errors.Wrap(err, "cannot load values.yaml") return c, fmt.Errorf("cannot load values.yaml: %w", err)
} }
c.Values = values c.Values = values
case f.Name == "values.schema.json": case f.Name == "values.schema.json":
@ -125,7 +125,7 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
log.Printf("Warning: Dependencies are handled in Chart.yaml since apiVersion \"v2\". We recommend migrating dependencies to Chart.yaml.") log.Printf("Warning: Dependencies are handled in Chart.yaml since apiVersion \"v2\". We recommend migrating dependencies to Chart.yaml.")
} }
if err := yaml.Unmarshal(f.Data, c.Metadata); err != nil { if err := yaml.Unmarshal(f.Data, c.Metadata); err != nil {
return c, errors.Wrap(err, "cannot load requirements.yaml") return c, fmt.Errorf("cannot load requirements.yaml: %w", err)
} }
if c.Metadata.APIVersion == chart.APIVersionV1 { if c.Metadata.APIVersion == chart.APIVersionV1 {
c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data}) c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data})
@ -134,7 +134,7 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
case f.Name == "requirements.lock": case f.Name == "requirements.lock":
c.Lock = new(chart.Lock) c.Lock = new(chart.Lock)
if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil { if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil {
return c, errors.Wrap(err, "cannot load requirements.lock") return c, fmt.Errorf("cannot load requirements.lock: %w", err)
} }
if c.Metadata == nil { if c.Metadata == nil {
c.Metadata = new(chart.Metadata) c.Metadata = new(chart.Metadata)
@ -163,7 +163,7 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
} }
if c.Metadata == nil { if c.Metadata == nil {
return c, errors.New("Chart.yaml file is missing") return c, errors.New("Chart.yaml file is missing") //nolint:staticcheck
} }
if err := c.Validate(); err != nil { if err := c.Validate(); err != nil {
@ -179,7 +179,7 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
case filepath.Ext(n) == ".tgz": case filepath.Ext(n) == ".tgz":
file := files[0] file := files[0]
if file.Name != n { if file.Name != n {
return c, errors.Errorf("error unpacking subchart tar in %s: expected %s, got %s", c.Name(), n, file.Name) return c, fmt.Errorf("error unpacking subchart tar in %s: expected %s, got %s", c.Name(), n, file.Name)
} }
// Untar the chart and add to c.Dependencies // Untar the chart and add to c.Dependencies
sc, err = LoadArchive(bytes.NewBuffer(file.Data)) sc, err = LoadArchive(bytes.NewBuffer(file.Data))
@ -199,7 +199,7 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
} }
if err != nil { if err != nil {
return c, errors.Wrapf(err, "error unpacking subchart %s in %s", n, c.Name()) return c, fmt.Errorf("error unpacking subchart %s in %s: %w", n, c.Name(), err)
} }
c.AddDependency(sc) c.AddDependency(sc)
} }
@ -221,13 +221,10 @@ func LoadValues(data io.Reader) (map[string]interface{}, error) {
if err == io.EOF { if err == io.EOF {
break break
} }
return nil, errors.Wrap(err, "error reading yaml document") return nil, fmt.Errorf("error reading yaml document: %w", err)
} }
if err := yaml.Unmarshal(raw, &currentMap, func(d *json.Decoder) *json.Decoder { if err := yaml.Unmarshal(raw, &currentMap); err != nil {
d.UseNumber() return nil, fmt.Errorf("cannot unmarshal yaml document: %w", err)
return d
}); err != nil {
return nil, errors.Wrap(err, "cannot unmarshal yaml document")
} }
values = MergeMaps(values, currentMap) values = MergeMaps(values, currentMap)
} }
@ -238,9 +235,7 @@ func LoadValues(data io.Reader) (map[string]interface{}, error) {
// If the value is a map, the maps will be merged recursively. // If the value is a map, the maps will be merged recursively.
func MergeMaps(a, b map[string]interface{}) map[string]interface{} { func MergeMaps(a, b map[string]interface{}) map[string]interface{} {
out := make(map[string]interface{}, len(a)) out := make(map[string]interface{}, len(a))
for k, v := range a { maps.Copy(out, a)
out[k] = v
}
for k, v := range b { for k, v := range b {
if v, ok := v.(map[string]interface{}); ok { if v, ok := v.(map[string]interface{}); ok {
if bv, ok := out[k]; ok { if bv, ok := out[k]; ok {

@ -30,7 +30,7 @@ import (
"testing" "testing"
"time" "time"
"helm.sh/helm/v4/pkg/chart" chart "helm.sh/helm/v4/pkg/chart/v2"
) )
func TestLoadDir(t *testing.T) { func TestLoadDir(t *testing.T) {
@ -648,6 +648,7 @@ func verifyChart(t *testing.T, c *chart.Chart) {
} }
func verifyDependencies(t *testing.T, c *chart.Chart) { func verifyDependencies(t *testing.T, c *chart.Chart) {
t.Helper()
if len(c.Metadata.Dependencies) != 2 { if len(c.Metadata.Dependencies) != 2 {
t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies))
} }
@ -670,6 +671,7 @@ func verifyDependencies(t *testing.T, c *chart.Chart) {
} }
func verifyDependenciesLock(t *testing.T, c *chart.Chart) { func verifyDependenciesLock(t *testing.T, c *chart.Chart) {
t.Helper()
if len(c.Metadata.Dependencies) != 2 { if len(c.Metadata.Dependencies) != 2 {
t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies))
} }
@ -692,10 +694,12 @@ func verifyDependenciesLock(t *testing.T, c *chart.Chart) {
} }
func verifyFrobnitz(t *testing.T, c *chart.Chart) { func verifyFrobnitz(t *testing.T, c *chart.Chart) {
t.Helper()
verifyChartFileAndTemplate(t, c, "frobnitz") verifyChartFileAndTemplate(t, c, "frobnitz")
} }
func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) { func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) {
t.Helper()
if c.Metadata == nil { if c.Metadata == nil {
t.Fatal("Metadata is nil") t.Fatal("Metadata is nil")
} }
@ -750,6 +754,7 @@ func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) {
} }
func verifyBomStripped(t *testing.T, files []*chart.File) { func verifyBomStripped(t *testing.T, files []*chart.File) {
t.Helper()
for _, file := range files { for _, file := range files {
if bytes.HasPrefix(file.Data, utf8bom) { if bytes.HasPrefix(file.Data, utf8bom) {
t.Errorf("Byte Order Mark still present in processed file %s", file.Name) t.Errorf("Byte Order Mark still present in processed file %s", file.Name)

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

Loading…
Cancel
Save