Merge remote-tracking branch 'upstream/main' into em/reinstate-logger-param

pull/31411/head
Evans Mungai 2 months ago
commit 0f90c83118
No known key found for this signature in database
GPG Key ID: BBEB812143DD14E1

@ -1,8 +1,8 @@
BINDIR := $(CURDIR)/bin
INSTALL_PATH ?= /usr/local/bin
DIST_DIRS := find * -type d -exec
TARGETS := darwin/amd64 darwin/arm64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le linux/s390x linux/riscv64 windows/amd64 windows/arm64
TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 darwin-amd64.tar.gz.sha256sum darwin-arm64.tar.gz darwin-arm64.tar.gz.sha256 darwin-arm64.tar.gz.sha256sum linux-amd64.tar.gz linux-amd64.tar.gz.sha256 linux-amd64.tar.gz.sha256sum linux-386.tar.gz linux-386.tar.gz.sha256 linux-386.tar.gz.sha256sum linux-arm.tar.gz linux-arm.tar.gz.sha256 linux-arm.tar.gz.sha256sum linux-arm64.tar.gz linux-arm64.tar.gz.sha256 linux-arm64.tar.gz.sha256sum linux-ppc64le.tar.gz linux-ppc64le.tar.gz.sha256 linux-ppc64le.tar.gz.sha256sum linux-s390x.tar.gz linux-s390x.tar.gz.sha256 linux-s390x.tar.gz.sha256sum linux-riscv64.tar.gz linux-riscv64.tar.gz.sha256 linux-riscv64.tar.gz.sha256sum windows-amd64.zip windows-amd64.zip.sha256 windows-amd64.zip.sha256sum windows-arm64.zip windows-arm64.zip.sha256 windows-arm64.zip.sha256sum
TARGETS := darwin/amd64 darwin/arm64 linux/amd64 linux/386 linux/arm linux/arm64 linux/loong64 linux/ppc64le linux/s390x linux/riscv64 windows/amd64 windows/arm64
TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 darwin-amd64.tar.gz.sha256sum darwin-arm64.tar.gz darwin-arm64.tar.gz.sha256 darwin-arm64.tar.gz.sha256sum linux-amd64.tar.gz linux-amd64.tar.gz.sha256 linux-amd64.tar.gz.sha256sum linux-386.tar.gz linux-386.tar.gz.sha256 linux-386.tar.gz.sha256sum linux-arm.tar.gz linux-arm.tar.gz.sha256 linux-arm.tar.gz.sha256sum linux-arm64.tar.gz linux-arm64.tar.gz.sha256 linux-arm64.tar.gz.sha256sum linux-loong64.tar.gz linux-loong64.tar.gz.sha256 linux-loong64.tar.gz.sha256sum linux-ppc64le.tar.gz linux-ppc64le.tar.gz.sha256 linux-ppc64le.tar.gz.sha256sum linux-s390x.tar.gz linux-s390x.tar.gz.sha256 linux-s390x.tar.gz.sha256sum linux-riscv64.tar.gz linux-riscv64.tar.gz.sha256 linux-riscv64.tar.gz.sha256sum windows-amd64.zip windows-amd64.zip.sha256 windows-amd64.zip.sha256sum windows-arm64.zip windows-arm64.zip.sha256 windows-arm64.zip.sha256sum
BINNAME ?= helm
GOBIN = $(shell go env GOBIN)
@ -69,6 +69,8 @@ LDFLAGS += -X helm.sh/helm/v4/pkg/internal/v3/lint/rules.k8sVersionMajor=$(K8S_M
LDFLAGS += -X helm.sh/helm/v4/pkg/internal/v3/lint/rules.k8sVersionMinor=$(K8S_MODULES_MINOR_VER)
LDFLAGS += -X helm.sh/helm/v4/pkg/chart/common/util.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER)
LDFLAGS += -X helm.sh/helm/v4/pkg/chart/common/util.k8sVersionMinor=$(K8S_MODULES_MINOR_VER)
LDFLAGS += -X helm.sh/helm/v4/internal/version.kubeClientVersionMajor=$(K8S_MODULES_MAJOR_VER)
LDFLAGS += -X helm.sh/helm/v4/internal/version.kubeClientVersionMinor=$(K8S_MODULES_MINOR_VER)
.PHONY: all
all: build

@ -126,7 +126,7 @@ func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string
linter.RunLinterRule(support.ErrorSev, fpath, validateAllowedExtension(fileName))
// We only apply the following lint rules to yaml files
if filepath.Ext(fileName) != ".yaml" || filepath.Ext(fileName) == ".yml" {
if !isYamlFileExtension(fileName) {
continue
}
@ -335,6 +335,11 @@ func validateListAnnotations(yamlStruct *k8sYamlStruct, manifest string) error {
return nil
}
func isYamlFileExtension(fileName string) bool {
ext := strings.ToLower(filepath.Ext(fileName))
return ext == ".yaml" || ext == ".yml"
}
// k8sYamlStruct stubs a Kubernetes YAML file.
type k8sYamlStruct struct {
APIVersion string `json:"apiVersion"`

@ -439,3 +439,23 @@ items:
t.Fatalf("List objects keep annotations should pass. got: %s", err)
}
}
func TestIsYamlFileExtension(t *testing.T) {
tests := []struct {
filename string
expected bool
}{
{"test.yaml", true},
{"test.yml", true},
{"test.txt", false},
{"test", false},
}
for _, test := range tests {
result := isYamlFileExtension(test.filename)
if result != test.expected {
t.Errorf("isYamlFileExtension(%s) = %v; want %v", test.filename, result, test.expected)
}
}
}

@ -218,9 +218,10 @@ httpRoute:
# value: v2
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# For publicly distributed charts, we recommend leaving 'resources' commented out.
# This makes resource allocation a conscious choice for the user and increases the chances
# charts run on a wide range of environments from low-resource clusters like Minikube to those
# with strict resource policies. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m

@ -85,10 +85,10 @@ func NewExtractor(source string) (Extractor, error) {
//
// - The character `:` is considered illegal because it is a separator on UNIX and a
// drive designator on Windows.
// - The path component `..` is considered suspicions, and therefore illegal
// - The path component `..` is considered suspicious, and therefore illegal
// - The character \ (backslash) is treated as a path separator and is converted to /.
// - Beginning a path with a path separator is illegal
// - Rudimentary symlink protects are offered by SecureJoin.
// - Rudimentary symlink protections are offered by SecureJoin.
func cleanJoin(root, dest string) (string, error) {
// On Windows, this is a drive separator. On UNIX-like, this is the path list separator.

@ -139,18 +139,24 @@ func Update(i Installer) error {
}
// NewForSource determines the correct Installer for the given source.
func NewForSource(source, version string) (Installer, error) {
// Check if source is an OCI registry reference
func NewForSource(source, version string) (installer Installer, err error) {
if strings.HasPrefix(source, fmt.Sprintf("%s://", registry.OCIScheme)) {
return NewOCIInstaller(source)
}
// Check if source is a local directory
if isLocalReference(source) {
return NewLocalInstaller(source)
// Source is an OCI registry reference
installer, err = NewOCIInstaller(source)
} else if isLocalReference(source) {
// Source is a local directory
installer, err = NewLocalInstaller(source)
} else if isRemoteHTTPArchive(source) {
return NewHTTPInstaller(source)
installer, err = NewHTTPInstaller(source)
} else {
installer, err = NewVCSInstaller(source, version)
}
if err != nil {
return installer, fmt.Errorf("cannot get information about plugin source %q (if it's a local directory, does it exist?), last error was: %w", source, err)
}
return NewVCSInstaller(source, version)
return
}
// FindSource determines the correct Installer for the given source.

@ -29,8 +29,8 @@ import (
"helm.sh/helm/v4/pkg/helmpath"
)
// ErrPluginNotAFolder indicates that the plugin path is not a folder.
var ErrPluginNotAFolder = errors.New("expected plugin to be a folder")
// ErrPluginNotADirectory indicates that the plugin path is not a directory.
var ErrPluginNotADirectory = errors.New("expected plugin to be a directory (containing a file 'plugin.yaml')")
// LocalInstaller installs plugins from the filesystem.
type LocalInstaller struct {
@ -91,7 +91,7 @@ func (i *LocalInstaller) installFromDirectory() error {
return err
}
if !stat.IsDir() {
return ErrPluginNotAFolder
return ErrPluginNotADirectory
}
if !isPlugin(i.Source) {

@ -64,7 +64,7 @@ func TestLocalInstallerNotAFolder(t *testing.T) {
if err == nil {
t.Fatal("expected error")
}
if err != ErrPluginNotAFolder {
if err != ErrPluginNotADirectory {
t.Fatalf("expected error to equal: %q", err)
}
}

@ -73,27 +73,27 @@ type pluginTypeMeta struct {
var pluginTypes = []pluginTypeMeta{
{
pluginType: "test/v1",
inputType: reflect.TypeOf(schema.InputMessageTestV1{}),
outputType: reflect.TypeOf(schema.OutputMessageTestV1{}),
configType: reflect.TypeOf(schema.ConfigTestV1{}),
inputType: reflect.TypeFor[schema.InputMessageTestV1](),
outputType: reflect.TypeFor[schema.OutputMessageTestV1](),
configType: reflect.TypeFor[schema.ConfigTestV1](),
},
{
pluginType: "cli/v1",
inputType: reflect.TypeOf(schema.InputMessageCLIV1{}),
outputType: reflect.TypeOf(schema.OutputMessageCLIV1{}),
configType: reflect.TypeOf(schema.ConfigCLIV1{}),
inputType: reflect.TypeFor[schema.InputMessageCLIV1](),
outputType: reflect.TypeFor[schema.OutputMessageCLIV1](),
configType: reflect.TypeFor[schema.ConfigCLIV1](),
},
{
pluginType: "getter/v1",
inputType: reflect.TypeOf(schema.InputMessageGetterV1{}),
outputType: reflect.TypeOf(schema.OutputMessageGetterV1{}),
configType: reflect.TypeOf(schema.ConfigGetterV1{}),
inputType: reflect.TypeFor[schema.InputMessageGetterV1](),
outputType: reflect.TypeFor[schema.OutputMessageGetterV1](),
configType: reflect.TypeFor[schema.ConfigGetterV1](),
},
{
pluginType: "postrenderer/v1",
inputType: reflect.TypeOf(schema.InputMessagePostRendererV1{}),
outputType: reflect.TypeOf(schema.OutputMessagePostRendererV1{}),
configType: reflect.TypeOf(schema.ConfigPostRendererV1{}),
inputType: reflect.TypeFor[schema.InputMessagePostRendererV1](),
outputType: reflect.TypeFor[schema.OutputMessagePostRendererV1](),
configType: reflect.TypeFor[schema.ConfigPostRendererV1](),
},
}

@ -71,7 +71,7 @@ func TestSubprocessPluginRuntime(t *testing.T) {
output, err := p.Invoke(t.Context(), &Input{
Message: schema.InputMessageCLIV1{
ExtraArgs: []string{"arg1", "arg2"},
//Env: []string{"FOO=bar"},
// Env: []string{"FOO=bar"},
},
})

@ -70,7 +70,7 @@ func symwalk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
if err != nil {
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.
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 {
return err

@ -18,6 +18,7 @@ package version // import "helm.sh/helm/v4/internal/version"
import (
"flag"
"fmt"
"runtime"
"strings"
)
@ -37,6 +38,11 @@ var (
gitCommit = ""
// gitTreeState is the state of the git tree
gitTreeState = ""
// The Kubernetes version can be set by LDFLAGS. In order to do that the value
// must be a string.
kubeClientVersionMajor = ""
kubeClientVersionMinor = ""
)
// BuildInfo describes the compile time information.
@ -49,6 +55,8 @@ type BuildInfo struct {
GitTreeState string `json:"git_tree_state,omitempty"`
// GoVersion is the version of the Go compiler used.
GoVersion string `json:"go_version,omitempty"`
// KubeClientVersion is the version of client-go Helm was build with
KubeClientVersion string `json:"kube_client_version"`
}
// GetVersion returns the semver string of the version
@ -67,10 +75,11 @@ func GetUserAgent() string {
// Get returns build info
func Get() BuildInfo {
v := BuildInfo{
Version: GetVersion(),
GitCommit: gitCommit,
GitTreeState: gitTreeState,
GoVersion: runtime.Version(),
Version: GetVersion(),
GitCommit: gitCommit,
GitTreeState: gitTreeState,
GoVersion: runtime.Version(),
KubeClientVersion: fmt.Sprintf("v%s.%s", kubeClientVersionMajor, kubeClientVersionMinor),
}
// HACK(bacongobbler): strip out GoVersion during a test run for consistent test output

@ -156,7 +156,7 @@ func (t *templateLinter) Lint() {
t.linter.RunLinterRule(support.ErrorSev, fileName, validateAllowedExtension(fileName))
// We only apply the following lint rules to yaml files
if filepath.Ext(fileName) != ".yaml" || filepath.Ext(fileName) == ".yml" {
if !isYamlFileExtension(fileName) {
continue
}
@ -366,6 +366,11 @@ func validateListAnnotations(yamlStruct *k8sYamlStruct, manifest string) error {
return nil
}
func isYamlFileExtension(fileName string) bool {
ext := strings.ToLower(filepath.Ext(fileName))
return ext == ".yaml" || ext == ".yml"
}
// k8sYamlStruct stubs a Kubernetes YAML file.
type k8sYamlStruct struct {
APIVersion string `json:"apiVersion"`

@ -462,3 +462,23 @@ items:
t.Fatalf("List objects keep annotations should pass. got: %s", err)
}
}
func TestIsYamlFileExtension(t *testing.T) {
tests := []struct {
filename string
expected bool
}{
{"test.yaml", true},
{"test.yml", true},
{"test.txt", false},
{"test", false},
}
for _, test := range tests {
result := isYamlFileExtension(test.filename)
if result != test.expected {
t.Errorf("isYamlFileExtension(%s) = %v; want %v", test.filename, result, test.expected)
}
}
}

@ -245,3 +245,373 @@ func TestListOutputCompletion(t *testing.T) {
func TestListFileCompletion(t *testing.T) {
checkFileCompletion(t, "list", false)
}
func TestListOutputFormats(t *testing.T) {
defaultNamespace := "default"
timestamp := time.Unix(1452902400, 0).UTC()
chartInfo := &chart.Chart{
Metadata: &chart.Metadata{
Name: "test-chart",
Version: "1.0.0",
AppVersion: "0.0.1",
},
}
releaseFixture := []*release.Release{
{
Name: "test-release",
Version: 1,
Namespace: defaultNamespace,
Info: &release.Info{
LastDeployed: timestamp,
Status: common.StatusDeployed,
},
Chart: chartInfo,
},
}
tests := []cmdTestCase{{
name: "list releases in json format",
cmd: "list --output json",
golden: "output/list-json.txt",
rels: releaseFixture,
}, {
name: "list releases in yaml format",
cmd: "list --output yaml",
golden: "output/list-yaml.txt",
rels: releaseFixture,
}}
runTestCmd(t, tests)
}
func TestReleaseListWriter(t *testing.T) {
timestamp := time.Unix(1452902400, 0).UTC()
chartInfo := &chart.Chart{
Metadata: &chart.Metadata{
Name: "test-chart",
Version: "1.0.0",
AppVersion: "0.0.1",
},
}
releases := []*release.Release{
{
Name: "test-release",
Version: 1,
Namespace: "default",
Info: &release.Info{
LastDeployed: timestamp,
Status: common.StatusDeployed,
},
Chart: chartInfo,
},
}
tests := []struct {
name string
releases []*release.Release
timeFormat string
noHeaders bool
noColor bool
}{
{
name: "empty releases list",
releases: []*release.Release{},
timeFormat: "",
noHeaders: false,
noColor: false,
},
{
name: "custom time format",
releases: releases,
timeFormat: "2006-01-02",
noHeaders: false,
noColor: false,
},
{
name: "no headers",
releases: releases,
timeFormat: "",
noHeaders: true,
noColor: false,
},
{
name: "no color",
releases: releases,
timeFormat: "",
noHeaders: false,
noColor: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
writer := newReleaseListWriter(tt.releases, tt.timeFormat, tt.noHeaders, tt.noColor)
if writer == nil {
t.Error("Expected writer to be non-nil")
} else {
if len(writer.releases) != len(tt.releases) {
t.Errorf("Expected %d releases, got %d", len(tt.releases), len(writer.releases))
}
}
})
}
}
func TestReleaseListWriterMethods(t *testing.T) {
timestamp := time.Unix(1452902400, 0).UTC()
zeroTimestamp := time.Time{}
chartInfo := &chart.Chart{
Metadata: &chart.Metadata{
Name: "test-chart",
Version: "1.0.0",
AppVersion: "0.0.1",
},
}
releases := []*release.Release{
{
Name: "test-release",
Version: 1,
Namespace: "default",
Info: &release.Info{
LastDeployed: timestamp,
Status: common.StatusDeployed,
},
Chart: chartInfo,
},
{
Name: "zero-time-release",
Version: 1,
Namespace: "default",
Info: &release.Info{
LastDeployed: zeroTimestamp,
Status: common.StatusFailed,
},
Chart: chartInfo,
},
}
tests := []struct {
name string
status common.Status
}{
{"deployed", common.StatusDeployed},
{"failed", common.StatusFailed},
{"pending-install", common.StatusPendingInstall},
{"pending-upgrade", common.StatusPendingUpgrade},
{"pending-rollback", common.StatusPendingRollback},
{"uninstalling", common.StatusUninstalling},
{"uninstalled", common.StatusUninstalled},
{"superseded", common.StatusSuperseded},
{"unknown", common.StatusUnknown},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
testReleases := []*release.Release{
{
Name: "test-release",
Version: 1,
Namespace: "default",
Info: &release.Info{
LastDeployed: timestamp,
Status: tt.status,
},
Chart: chartInfo,
},
}
writer := newReleaseListWriter(testReleases, "", false, false)
var buf []byte
out := &bytesWriter{buf: &buf}
err := writer.WriteJSON(out)
if err != nil {
t.Errorf("WriteJSON failed: %v", err)
}
err = writer.WriteYAML(out)
if err != nil {
t.Errorf("WriteYAML failed: %v", err)
}
err = writer.WriteTable(out)
if err != nil {
t.Errorf("WriteTable failed: %v", err)
}
})
}
writer := newReleaseListWriter(releases, "", false, false)
var buf []byte
out := &bytesWriter{buf: &buf}
err := writer.WriteJSON(out)
if err != nil {
t.Errorf("WriteJSON failed: %v", err)
}
err = writer.WriteYAML(out)
if err != nil {
t.Errorf("WriteYAML failed: %v", err)
}
err = writer.WriteTable(out)
if err != nil {
t.Errorf("WriteTable failed: %v", err)
}
}
func TestFilterReleases(t *testing.T) {
releases := []*release.Release{
{Name: "release1"},
{Name: "release2"},
{Name: "release3"},
}
tests := []struct {
name string
releases []*release.Release
ignoredReleaseNames []string
expectedCount int
}{
{
name: "nil ignored list",
releases: releases,
ignoredReleaseNames: nil,
expectedCount: 3,
},
{
name: "empty ignored list",
releases: releases,
ignoredReleaseNames: []string{},
expectedCount: 3,
},
{
name: "filter one release",
releases: releases,
ignoredReleaseNames: []string{"release1"},
expectedCount: 2,
},
{
name: "filter multiple releases",
releases: releases,
ignoredReleaseNames: []string{"release1", "release3"},
expectedCount: 1,
},
{
name: "filter non-existent release",
releases: releases,
ignoredReleaseNames: []string{"non-existent"},
expectedCount: 3,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := filterReleases(tt.releases, tt.ignoredReleaseNames)
if len(result) != tt.expectedCount {
t.Errorf("Expected %d releases, got %d", tt.expectedCount, len(result))
}
})
}
}
type bytesWriter struct {
buf *[]byte
}
func (b *bytesWriter) Write(p []byte) (n int, err error) {
*b.buf = append(*b.buf, p...)
return len(p), nil
}
func TestListCustomTimeFormat(t *testing.T) {
defaultNamespace := "default"
timestamp := time.Unix(1452902400, 0).UTC()
chartInfo := &chart.Chart{
Metadata: &chart.Metadata{
Name: "test-chart",
Version: "1.0.0",
AppVersion: "0.0.1",
},
}
releaseFixture := []*release.Release{
{
Name: "test-release",
Version: 1,
Namespace: defaultNamespace,
Info: &release.Info{
LastDeployed: timestamp,
Status: common.StatusDeployed,
},
Chart: chartInfo,
},
}
tests := []cmdTestCase{{
name: "list releases with custom time format",
cmd: "list --time-format '2006-01-02 15:04:05'",
golden: "output/list-time-format.txt",
rels: releaseFixture,
}}
runTestCmd(t, tests)
}
func TestListStatusMapping(t *testing.T) {
defaultNamespace := "default"
timestamp := time.Unix(1452902400, 0).UTC()
chartInfo := &chart.Chart{
Metadata: &chart.Metadata{
Name: "test-chart",
Version: "1.0.0",
AppVersion: "0.0.1",
},
}
testCases := []struct {
name string
status common.Status
}{
{"deployed", common.StatusDeployed},
{"failed", common.StatusFailed},
{"pending-install", common.StatusPendingInstall},
{"pending-upgrade", common.StatusPendingUpgrade},
{"pending-rollback", common.StatusPendingRollback},
{"uninstalling", common.StatusUninstalling},
{"uninstalled", common.StatusUninstalled},
{"superseded", common.StatusSuperseded},
{"unknown", common.StatusUnknown},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
releaseFixture := []*release.Release{
{
Name: "test-release",
Version: 1,
Namespace: defaultNamespace,
Info: &release.Info{
LastDeployed: timestamp,
Status: tc.status,
},
Chart: chartInfo,
},
}
writer := newReleaseListWriter(releaseFixture, "", false, false)
if len(writer.releases) != 1 {
t.Errorf("Expected 1 release, got %d", len(writer.releases))
}
if writer.releases[0].Status != tc.status.String() {
t.Errorf("Expected status %s, got %s", tc.status.String(), writer.releases[0].Status)
}
})
}
}

@ -287,7 +287,7 @@ func compListChartsOfRepo(repoName string, prefix string) []string {
if isNotExist(err) {
// If there is no cached charts file, fallback to the full index file.
// This is much slower but can happen after the caching feature is first
// installed but before the user does a 'helm repo update' to generate the
// installed but before the user does a 'helm repo update' to generate the
// first cached charts file.
path = filepath.Join(settings.RepositoryCache, helmpath.CacheIndexFile(repoName))
if indexFile, err := repo.LoadIndexFile(path); err == nil {

@ -0,0 +1 @@
[{"name":"test-release","namespace":"default","revision":"1","updated":"2016-01-16 00:00:00 +0000 UTC","status":"deployed","chart":"test-chart-1.0.0","app_version":"0.0.1"}]

@ -0,0 +1,2 @@
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
test-release default 1 2016-01-16 00:00:00 deployed test-chart-1.0.0 0.0.1

@ -0,0 +1,7 @@
- app_version: 0.0.1
chart: test-chart-1.0.0
name: test-release
namespace: default
revision: "1"
status: deployed
updated: 2016-01-16 00:00:00 +0000 UTC

@ -1 +1 @@
version.BuildInfo{Version:"v4.0", GitCommit:"", GitTreeState:"", GoVersion:""}
version.BuildInfo{Version:"v4.0", GitCommit:"", GitTreeState:"", GoVersion:"", KubeClientVersion:"v."}

@ -64,7 +64,7 @@ func (f files) Get(name string) string {
}
// Glob takes a glob pattern and returns another files object only containing
// matched files.
// matched files.
//
// This is designed to be called from a template.
//

@ -520,11 +520,11 @@ func TestHTTPGetterTarDownload(t *testing.T) {
b := make([]byte, 512)
f.Read(b)
//Get the file size
// Get the file size
FileStat, _ := f.Stat()
FileSize := strconv.FormatInt(FileStat.Size(), 10)
//Simulating improper header values from bitbucket
// Simulating improper header values from bitbucket
w.Header().Set("Content-Type", "application/x-tar")
w.Header().Set("Content-Encoding", "gzip")
w.Header().Set("Content-Length", FileSize)

@ -109,7 +109,7 @@ func (g *getterPlugin) Get(href string, options ...Option) (*bytes.Buffer, error
Protocol: u.Scheme,
},
// TODO should we pass Stdin, Stdout, and Stderr through Input here to getter plugins?
//Stdout: os.Stdout,
// Stdout: os.Stdout,
}
output, err := g.plg.Invoke(context.Background(), input)
if err != nil {

@ -573,10 +573,17 @@ func (c *Client) update(originals, targets ResourceList, updateApplyFunc UpdateA
}
if err := deleteResource(info, metav1.DeletePropagationBackground); err != nil {
c.Logger().Debug("failed to delete resource", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, slog.Any("error", err))
if !apierrors.IsNotFound(err) {
updateErrors = append(updateErrors, fmt.Errorf("failed to delete resource %s: %w", info.Name, err))
}
continue
}
res.Deleted = append(res.Deleted, info)
}
if len(updateErrors) != 0 {
return res, joinErrors(updateErrors, " && ")
}
return res, nil
}
@ -693,19 +700,19 @@ func (c *Client) Update(originals, targets ResourceList, options ...ClientUpdate
errs = append(errs, o(&updateOptions))
}
if err := errors.Join(errs...); err != nil {
return nil, fmt.Errorf("invalid client update option(s): %w", err)
return &Result{}, fmt.Errorf("invalid client update option(s): %w", err)
}
if updateOptions.threeWayMergeForUnstructured && updateOptions.serverSideApply {
return nil, fmt.Errorf("invalid operation: cannot use three-way merge for unstructured and server-side apply together")
return &Result{}, fmt.Errorf("invalid operation: cannot use three-way merge for unstructured and server-side apply together")
}
if updateOptions.forceConflicts && updateOptions.forceReplace {
return nil, fmt.Errorf("invalid operation: cannot use force conflicts and force replace together")
return &Result{}, fmt.Errorf("invalid operation: cannot use force conflicts and force replace together")
}
if updateOptions.serverSideApply && updateOptions.forceReplace {
return nil, fmt.Errorf("invalid operation: cannot use server-side apply and force replace together")
return &Result{}, fmt.Errorf("invalid operation: cannot use server-side apply and force replace together")
}
makeUpdateApplyFunc := func() UpdateApplyFunc {

@ -321,6 +321,7 @@ func TestUpdate(t *testing.T) {
ThreeWayMergeForUnstructured bool
ServerSideApply bool
ExpectedActions []string
ExpectedError string
}
expectedActionsClientSideApply := []string{
@ -336,6 +337,8 @@ func TestUpdate(t *testing.T) {
"/namespaces/default/pods:POST", // retry due to 409
"/namespaces/default/pods/squid:GET",
"/namespaces/default/pods/squid:DELETE",
"/namespaces/default/pods/notfound:GET",
"/namespaces/default/pods/notfound:DELETE",
}
expectedActionsServerSideApply := []string{
@ -351,11 +354,13 @@ func TestUpdate(t *testing.T) {
"/namespaces/default/pods:POST", // retry due to 409
"/namespaces/default/pods/squid:GET",
"/namespaces/default/pods/squid:DELETE",
"/namespaces/default/pods/notfound:GET",
"/namespaces/default/pods/notfound:DELETE",
}
testCases := map[string]testCase{
"client-side apply": {
OriginalPods: newPodList("starfish", "otter", "squid"),
OriginalPods: newPodList("starfish", "otter", "squid", "notfound"),
TargetPods: func() v1.PodList {
listTarget := newPodList("starfish", "otter", "dolphin")
listTarget.Items[0].Spec.Containers[0].Ports = []v1.ContainerPort{{Name: "https", ContainerPort: 443}}
@ -365,9 +370,10 @@ func TestUpdate(t *testing.T) {
ThreeWayMergeForUnstructured: false,
ServerSideApply: false,
ExpectedActions: expectedActionsClientSideApply,
ExpectedError: "",
},
"client-side apply (three-way merge for unstructured)": {
OriginalPods: newPodList("starfish", "otter", "squid"),
OriginalPods: newPodList("starfish", "otter", "squid", "notfound"),
TargetPods: func() v1.PodList {
listTarget := newPodList("starfish", "otter", "dolphin")
listTarget.Items[0].Spec.Containers[0].Ports = []v1.ContainerPort{{Name: "https", ContainerPort: 443}}
@ -377,9 +383,10 @@ func TestUpdate(t *testing.T) {
ThreeWayMergeForUnstructured: true,
ServerSideApply: false,
ExpectedActions: expectedActionsClientSideApply,
ExpectedError: "",
},
"serverSideApply": {
OriginalPods: newPodList("starfish", "otter", "squid"),
OriginalPods: newPodList("starfish", "otter", "squid", "notfound"),
TargetPods: func() v1.PodList {
listTarget := newPodList("starfish", "otter", "dolphin")
listTarget.Items[0].Spec.Containers[0].Ports = []v1.ContainerPort{{Name: "https", ContainerPort: 443}}
@ -389,6 +396,23 @@ func TestUpdate(t *testing.T) {
ThreeWayMergeForUnstructured: false,
ServerSideApply: true,
ExpectedActions: expectedActionsServerSideApply,
ExpectedError: "",
},
"serverSideApply with forbidden deletion": {
OriginalPods: newPodList("starfish", "otter", "squid", "notfound", "forbidden"),
TargetPods: func() v1.PodList {
listTarget := newPodList("starfish", "otter", "dolphin")
listTarget.Items[0].Spec.Containers[0].Ports = []v1.ContainerPort{{Name: "https", ContainerPort: 443}}
return listTarget
}(),
ThreeWayMergeForUnstructured: false,
ServerSideApply: true,
ExpectedActions: append(expectedActionsServerSideApply,
"/namespaces/default/pods/forbidden:GET",
"/namespaces/default/pods/forbidden:DELETE",
),
ExpectedError: "failed to delete resource forbidden:",
},
}
@ -444,6 +468,22 @@ func TestUpdate(t *testing.T) {
return newResponse(http.StatusOK, &listTarget.Items[1])
case p == "/namespaces/default/pods/squid" && m == http.MethodGet:
return newResponse(http.StatusOK, &listTarget.Items[2])
case p == "/namespaces/default/pods/notfound" && m == http.MethodGet:
// Resource exists in original but will simulate not found on delete
return newResponse(http.StatusOK, &listOriginal.Items[3])
case p == "/namespaces/default/pods/notfound" && m == http.MethodDelete:
// Simulate a not found during deletion; should not cause update to fail
return newResponse(http.StatusNotFound, notFoundBody())
case p == "/namespaces/default/pods/forbidden" && m == http.MethodGet:
return newResponse(http.StatusOK, &listOriginal.Items[4])
case p == "/namespaces/default/pods/forbidden" && m == http.MethodDelete:
// Simulate RBAC forbidden that should cause update to fail
return newResponse(http.StatusForbidden, &metav1.Status{
Status: metav1.StatusFailure,
Message: "pods \"forbidden\" is forbidden: User \"test-user\" cannot delete resource \"pods\" in API group \"\" in the namespace \"default\"",
Reason: metav1.StatusReasonForbidden,
Code: http.StatusForbidden,
})
default:
}
@ -471,7 +511,13 @@ func TestUpdate(t *testing.T) {
ClientUpdateOptionForceReplace(false),
ClientUpdateOptionServerSideApply(tc.ServerSideApply, false),
ClientUpdateOptionUpgradeClientSideFieldManager(true))
require.NoError(t, err)
if tc.ExpectedError != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tc.ExpectedError)
} else {
require.NoError(t, err)
}
assert.Len(t, result.Created, 1, "expected 1 resource created, got %d", len(result.Created))
assert.Len(t, result.Updated, 2, "expected 2 resource updated, got %d", len(result.Updated))

@ -237,7 +237,7 @@ func (t *parser) key(data map[string]interface{}, nestedNameLevel int) (reterr e
_, err = t.emptyVal()
return err
}
//End of key. Consume =, Get value.
// End of key. Consume =, Get value.
// FIXME: Get value list first
vl, e := t.valList()
switch e {

@ -60,7 +60,7 @@ runAsRoot() {
# verifySupported checks that the os/arch combination is supported for
# binary builds.
verifySupported() {
local supported="darwin-amd64\nlinux-386\nlinux-amd64\nlinux-arm\nlinux-arm64\nlinux-ppc64le\nlinux-s390x\nlinux-riscv64\nwindows-amd64\nwindows-arm64"
local supported="darwin-amd64\nlinux-386\nlinux-amd64\nlinux-arm\nlinux-arm64\nlinux-loong64\nlinux-ppc64le\nlinux-s390x\nwindows-amd64\nwindows-arm64"
if ! echo "${supported}" | grep -q "${OS}-${ARCH}"; then
echo "No prebuilt binary for ${OS}-${ARCH}."
echo "To build from source, go to https://github.com/helm/helm"

@ -69,7 +69,7 @@ runAsRoot() {
# verifySupported checks that the os/arch combination is supported for
# binary builds, as well whether or not necessary tools are present.
verifySupported() {
local supported="darwin-amd64\ndarwin-arm64\nlinux-386\nlinux-amd64\nlinux-arm\nlinux-arm64\nlinux-ppc64le\nlinux-s390x\nlinux-riscv64\nwindows-amd64\nwindows-arm64"
local supported="darwin-amd64\ndarwin-arm64\nlinux-386\nlinux-amd64\nlinux-arm\nlinux-arm64\nlinux-loong64\nlinux-ppc64le\nlinux-s390x\nlinux-riscv64\nwindows-amd64\nwindows-arm64"
if ! echo "${supported}" | grep -q "${OS}-${ARCH}"; then
echo "No prebuilt binary for ${OS}-${ARCH}."
echo "To build from source, go to https://github.com/helm/helm"

@ -87,6 +87,7 @@ Download Helm ${RELEASE}. The common platform binaries are here:
- [Linux arm](https://get.helm.sh/helm-${RELEASE}-linux-arm.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-arm.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-linux-arm.tar.gz.sha256))
- [Linux arm64](https://get.helm.sh/helm-${RELEASE}-linux-arm64.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-arm64.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-linux-arm64.tar.gz.sha256))
- [Linux i386](https://get.helm.sh/helm-${RELEASE}-linux-386.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-386.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-linux-386.tar.gz.sha256))
- [Linux loong64](https://get.helm.sh/helm-${RELEASE}-linux-loong64.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-loong64.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-linux-loong64.tar.gz.sha256))
- [Linux ppc64le](https://get.helm.sh/helm-${RELEASE}-linux-ppc64le.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-ppc64le.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-linux-ppc64le.tar.gz.sha256))
- [Linux s390x](https://get.helm.sh/helm-${RELEASE}-linux-s390x.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-s390x.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-linux-s390x.tar.gz.sha256))
- [Linux riscv64](https://get.helm.sh/helm-${RELEASE}-linux-riscv64.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-riscv64.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-linux-riscv64.tar.gz.sha256))

Loading…
Cancel
Save