mirror of https://github.com/helm/helm
commit
848bc4cb89
@ -1,14 +0,0 @@
|
||||
---
|
||||
|
||||
# This file can be removed when Helm no longer uses CircleCI on any release
|
||||
# branches. Once CircleCI is turned off this file can be removed.
|
||||
version: 2
|
||||
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: cimg/go:1.18
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
@ -1,7 +1,39 @@
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
- # Keep dev-v3 branch dependencies up to date, while Helm v3 is within support
|
||||
package-ecosystem: "gomod"
|
||||
target-branch: "dev-v3"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
groups:
|
||||
k8s.io:
|
||||
patterns:
|
||||
- "k8s.io/api"
|
||||
- "k8s.io/apiextensions-apiserver"
|
||||
- "k8s.io/apimachinery"
|
||||
- "k8s.io/apiserver"
|
||||
- "k8s.io/cli-runtime"
|
||||
- "k8s.io/client-go"
|
||||
- "k8s.io/kubectl"
|
||||
- package-ecosystem: "gomod"
|
||||
target-branch: "main"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
groups:
|
||||
k8s.io:
|
||||
patterns:
|
||||
- "k8s.io/api"
|
||||
- "k8s.io/apiextensions-apiserver"
|
||||
- "k8s.io/apimachinery"
|
||||
- "k8s.io/apiserver"
|
||||
- "k8s.io/cli-runtime"
|
||||
- "k8s.io/client-go"
|
||||
- "k8s.io/kubectl"
|
||||
- package-ecosystem: "github-actions"
|
||||
target-branch: "main"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
interval: "daily"
|
||||
|
@ -0,0 +1,2 @@
|
||||
GOLANG_VERSION=1.24
|
||||
GOLANGCI_LINT_VERSION=v2.0.2
|
@ -0,0 +1,27 @@
|
||||
name: golangci-lint
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
golangci:
|
||||
name: golangci-lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2
|
||||
- name: Add variables to environment file
|
||||
run: cat ".github/env" >> "$GITHUB_ENV"
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # pin@5.4.0
|
||||
with:
|
||||
go-version: '${{ env.GOLANG_VERSION }}'
|
||||
check-latest: true
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@1481404843c368bc19ca9406f87d6e0fc97bdcfd #pin@7.0.0
|
||||
with:
|
||||
version: ${{ env.GOLANGCI_LINT_VERSION }}
|
@ -0,0 +1,28 @@
|
||||
name: govulncheck
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- go.sum
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
govulncheck:
|
||||
name: govulncheck
|
||||
runs-on: ubuntu-latest
|
||||
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
|
||||
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # pin@5.4.0
|
||||
with:
|
||||
go-version: '${{ env.GOLANG_VERSION }}'
|
||||
check-latest: true
|
||||
- name: govulncheck
|
||||
uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # pin@1.0.4
|
||||
with:
|
||||
go-package: ./...
|
@ -0,0 +1,69 @@
|
||||
name: Scorecard supply-chain security
|
||||
on:
|
||||
# For Branch-Protection check. Only the default branch is supported. See
|
||||
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
|
||||
branch_protection_rule:
|
||||
# To guarantee Maintained check is occasionally updated. See
|
||||
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
|
||||
schedule:
|
||||
- cron: '25 7 * * 0'
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecard analysis
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Needed to upload the results to code-scanning dashboard.
|
||||
security-events: write
|
||||
# Needed to publish results and get a badge (see publish_results below).
|
||||
id-token: write
|
||||
# Uncomment the permissions below if installing in a private repository.
|
||||
# contents: read
|
||||
# actions: read
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
|
||||
# - you want to enable the Branch-Protection check on a *public* repository, or
|
||||
# - you are installing Scorecard on a *private* repository
|
||||
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional.
|
||||
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
|
||||
|
||||
# Public repositories:
|
||||
# - Publish results to OpenSSF REST API for easy access by consumers
|
||||
# - Allows the repository to include the Scorecard badge.
|
||||
# - See https://github.com/ossf/scorecard-action#publishing-results.
|
||||
# For private repositories:
|
||||
# - `publish_results` will always be set to `false`, regardless
|
||||
# of the value entered here.
|
||||
publish_results: true
|
||||
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard (optional).
|
||||
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: results.sarif
|
@ -1,25 +1,61 @@
|
||||
run:
|
||||
timeout: 10m
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- goimports
|
||||
|
||||
exclusions:
|
||||
generated: lax
|
||||
|
||||
settings:
|
||||
gofmt:
|
||||
simplify: true
|
||||
|
||||
goimports:
|
||||
local-prefixes:
|
||||
- helm.sh/helm/v4
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
default: none
|
||||
|
||||
enable:
|
||||
- depguard
|
||||
- dupl
|
||||
- gofmt
|
||||
- goimports
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- nakedret
|
||||
- revive
|
||||
- unused
|
||||
- staticcheck
|
||||
- unused
|
||||
- usestdlibvars
|
||||
|
||||
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
|
||||
|
||||
run:
|
||||
timeout: 10m
|
||||
|
||||
linters-settings:
|
||||
gofmt:
|
||||
simplify: true
|
||||
goimports:
|
||||
local-prefixes: helm.sh/helm/v3
|
||||
dupl:
|
||||
threshold: 400
|
||||
version: "2"
|
||||
|
@ -1,58 +0,0 @@
|
||||
//go:build !windows
|
||||
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func checkPerms() {
|
||||
// This function MUST NOT FAIL, as it is just a check for a common permissions problem.
|
||||
// If for some reason the function hits a stopping condition, it may panic. But only if
|
||||
// we can be sure that it is panicking because Helm cannot proceed.
|
||||
|
||||
kc := settings.KubeConfig
|
||||
if kc == "" {
|
||||
kc = os.Getenv("KUBECONFIG")
|
||||
}
|
||||
if kc == "" {
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
// No idea where to find KubeConfig, so return silently. Many helm commands
|
||||
// can proceed happily without a KUBECONFIG, so this is not a fatal error.
|
||||
return
|
||||
}
|
||||
kc = filepath.Join(u.HomeDir, ".kube", "config")
|
||||
}
|
||||
fi, err := os.Stat(kc)
|
||||
if err != nil {
|
||||
// DO NOT error if no KubeConfig is found. Not all commands require one.
|
||||
return
|
||||
}
|
||||
|
||||
perm := fi.Mode().Perm()
|
||||
if perm&0040 > 0 {
|
||||
warning("Kubernetes configuration file is group-readable. This is insecure. Location: %s", kc)
|
||||
}
|
||||
if perm&0004 > 0 {
|
||||
warning("Kubernetes configuration file is world-readable. This is insecure. Location: %s", kc)
|
||||
}
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
//go:build !windows
|
||||
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func checkPermsStderr() (string, error) {
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
stderr := os.Stderr
|
||||
os.Stderr = w
|
||||
defer func() {
|
||||
os.Stderr = stderr
|
||||
}()
|
||||
|
||||
checkPerms()
|
||||
w.Close()
|
||||
|
||||
var text bytes.Buffer
|
||||
io.Copy(&text, r)
|
||||
return text.String(), nil
|
||||
}
|
||||
|
||||
func TestCheckPerms(t *testing.T) {
|
||||
tdir := t.TempDir()
|
||||
tfile := filepath.Join(tdir, "testconfig")
|
||||
fh, err := os.OpenFile(tfile, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0440)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create temp file: %s", err)
|
||||
}
|
||||
|
||||
tconfig := settings.KubeConfig
|
||||
settings.KubeConfig = tfile
|
||||
defer func() { settings.KubeConfig = tconfig }()
|
||||
|
||||
text, err := checkPermsStderr()
|
||||
if err != nil {
|
||||
t.Fatalf("could not read from stderr: %s", err)
|
||||
}
|
||||
expectPrefix := "WARNING: Kubernetes configuration file is group-readable. This is insecure. Location:"
|
||||
if !strings.HasPrefix(text, expectPrefix) {
|
||||
t.Errorf("Expected to get a warning for group perms. Got %q", text)
|
||||
}
|
||||
|
||||
if err := fh.Chmod(0404); err != nil {
|
||||
t.Errorf("Could not change mode on file: %s", err)
|
||||
}
|
||||
text, err = checkPermsStderr()
|
||||
if err != nil {
|
||||
t.Fatalf("could not read from stderr: %s", err)
|
||||
}
|
||||
expectPrefix = "WARNING: Kubernetes configuration file is world-readable. This is insecure. Location:"
|
||||
if !strings.HasPrefix(text, expectPrefix) {
|
||||
t.Errorf("Expected to get a warning for world perms. Got %q", text)
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
:4
|
||||
Completion ended with directive: ShellCompDirectiveNoFileComp
|
@ -1,7 +0,0 @@
|
||||
==> Linting testdata/testcharts/chart-with-bad-subcharts
|
||||
[INFO] Chart.yaml: icon is recommended
|
||||
[WARNING] templates/: directory not found
|
||||
[ERROR] : unable to load chart
|
||||
error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required
|
||||
|
||||
Error: 1 chart(s) linted, 1 chart(s) failed
|
@ -1,4 +0,0 @@
|
||||
==> Linting testdata/testcharts/chart-with-only-crds
|
||||
[WARNING] templates/: directory not found
|
||||
|
||||
1 chart(s) linted, 0 chart(s) failed
|
@ -1,2 +0,0 @@
|
||||
:4
|
||||
Completion ended with directive: ShellCompDirectiveNoFileComp
|
@ -1,2 +0,0 @@
|
||||
:4
|
||||
Completion ended with directive: ShellCompDirectiveNoFileComp
|
@ -1 +0,0 @@
|
||||
version.BuildInfo{Version:"v3.12", GitCommit:"", GitTreeState:"", GoVersion:""}
|
@ -1 +0,0 @@
|
||||
version.BuildInfo{Version:"v3.12", GitCommit:"", GitTreeState:"", GoVersion:""}
|
@ -1 +0,0 @@
|
||||
v3.12
|
@ -1 +0,0 @@
|
||||
Version: v3.12
|
@ -1 +0,0 @@
|
||||
version.BuildInfo{Version:"v3.12", GitCommit:"", GitTreeState:"", GoVersion:""}
|
@ -1,164 +1,180 @@
|
||||
module helm.sh/helm/v3
|
||||
module helm.sh/helm/v4
|
||||
|
||||
go 1.19
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.2.1
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||
github.com/Masterminds/semver/v3 v3.2.1
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24
|
||||
github.com/BurntSushi/toml v1.5.0
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||
github.com/Masterminds/semver/v3 v3.3.0
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/Masterminds/squirrel v1.5.4
|
||||
github.com/Masterminds/vcs v1.13.3
|
||||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535
|
||||
github.com/containerd/containerd v1.7.0
|
||||
github.com/cyphar/filepath-securejoin v0.2.3
|
||||
github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible
|
||||
github.com/foxcpp/go-mockdns v1.0.0
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
||||
github.com/cyphar/filepath-securejoin v0.4.1
|
||||
github.com/distribution/distribution/v3 v3.0.0
|
||||
github.com/evanphx/json-patch v5.9.11+incompatible
|
||||
github.com/fluxcd/cli-utils v0.36.0-flux.13
|
||||
github.com/foxcpp/go-mockdns v1.1.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gofrs/flock v0.8.1
|
||||
github.com/gofrs/flock v0.12.1
|
||||
github.com/gosuri/uitable v0.0.4
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/jmoiron/sqlx v1.4.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/mattn/go-shellwords v1.0.12
|
||||
github.com/mitchellh/copystructure v1.2.0
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b
|
||||
github.com/moby/term v0.5.2
|
||||
github.com/opencontainers/image-spec v1.1.1
|
||||
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rubenv/sql-migrate v1.3.1
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
golang.org/x/crypto v0.5.0
|
||||
golang.org/x/term v0.6.0
|
||||
golang.org/x/text v0.9.0
|
||||
k8s.io/api v0.27.2
|
||||
k8s.io/apiextensions-apiserver v0.27.2
|
||||
k8s.io/apimachinery v0.27.2
|
||||
k8s.io/apiserver v0.27.2
|
||||
k8s.io/cli-runtime v0.27.2
|
||||
k8s.io/client-go v0.27.2
|
||||
k8s.io/klog/v2 v2.90.1
|
||||
k8s.io/kubectl v0.27.2
|
||||
oras.land/oras-go v1.2.2
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
github.com/rubenv/sql-migrate v1.8.0
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/pflag v1.0.6
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/crypto v0.38.0
|
||||
golang.org/x/term v0.32.0
|
||||
golang.org/x/text v0.25.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.33.0
|
||||
k8s.io/apiextensions-apiserver v0.33.0
|
||||
k8s.io/apimachinery v0.33.0
|
||||
k8s.io/apiserver v0.33.0
|
||||
k8s.io/cli-runtime v0.33.0
|
||||
k8s.io/client-go v0.33.0
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
k8s.io/kubectl v0.33.0
|
||||
oras.land/oras-go/v2 v2.6.0
|
||||
sigs.k8s.io/controller-runtime v0.20.4
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/MakeNowJust/heredoc v1.0.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect
|
||||
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd // indirect
|
||||
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b // indirect
|
||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/cli v20.10.21+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
||||
github.com/docker/docker v20.10.24+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.2 // 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-units v0.5.0 // indirect
|
||||
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.10.1 // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.1 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
github.com/fvbommel/sortorder v1.0.1 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-gorp/gorp/v3 v3.0.5 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/go-errors/errors v1.5.1 // indirect
|
||||
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.1 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/gomodule/redigo v1.8.2 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/gnostic-models v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gorilla/handlers v1.5.1 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/handlers v1.5.2 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect
|
||||
github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.16.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/miekg/dns v1.1.25 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
|
||||
github.com/miekg/dns v1.1.57 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/spdystream v0.2.0 // indirect
|
||||
github.com/moby/spdystream v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // 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/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.14.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.22.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.62.0 // 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/redisotel/v9 v9.0.5 // indirect
|
||||
github.com/redis/go-redis/v9 v9.7.3 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/spf13/cast v1.5.0 // 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.1.0 // indirect
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 // indirect
|
||||
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 // indirect
|
||||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f // indirect
|
||||
go.opentelemetry.io/otel v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.14.0 // indirect
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/oauth2 v0.4.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
|
||||
google.golang.org/grpc v1.53.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // 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/exporters/autoexport v0.57.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.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/otlploghttp v0.8.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/otlptrace v1.33.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/prometheus v0.54.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/stdouttrace v1.32.0 // indirect
|
||||
go.opentelemetry.io/otel/log v0.8.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.34.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/metric v1.32.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.34.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.4.0 // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/oauth2 v0.29.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.32.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // 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/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gotest.tools/v3 v3.4.0 // indirect
|
||||
k8s.io/component-base v0.27.2 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect
|
||||
k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/kustomize/api v0.13.2 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.14.1 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
k8s.io/component-base v0.33.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
|
||||
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.19.0 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
|
||||
)
|
||||
|
@ -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)
|
||||
}
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
/*
|
||||
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 tlsutil
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Options represents configurable options used to create client and server TLS configurations.
|
||||
type Options struct {
|
||||
CaCertFile string
|
||||
// If either the KeyFile or CertFile is empty, ClientConfig() will not load them.
|
||||
KeyFile string
|
||||
CertFile string
|
||||
// Client-only options
|
||||
InsecureSkipVerify bool
|
||||
}
|
||||
|
||||
// ClientConfig returns a TLS configuration for use by a Helm client.
|
||||
func ClientConfig(opts Options) (cfg *tls.Config, err error) {
|
||||
var cert *tls.Certificate
|
||||
var pool *x509.CertPool
|
||||
|
||||
if opts.CertFile != "" || opts.KeyFile != "" {
|
||||
if cert, err = CertFromFilePair(opts.CertFile, opts.KeyFile); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, errors.Wrapf(err, "could not load x509 key pair (cert: %q, key: %q)", opts.CertFile, opts.KeyFile)
|
||||
}
|
||||
return nil, errors.Wrapf(err, "could not read x509 key pair (cert: %q, key: %q)", opts.CertFile, opts.KeyFile)
|
||||
}
|
||||
}
|
||||
if !opts.InsecureSkipVerify && opts.CaCertFile != "" {
|
||||
if pool, err = CertPoolFromFile(opts.CaCertFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
cfg = &tls.Config{InsecureSkipVerify: opts.InsecureSkipVerify, Certificates: []tls.Certificate{*cert}, RootCAs: pool}
|
||||
return cfg, nil
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/*
|
||||
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 tlsutil
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const tlsTestDir = "../../testdata"
|
||||
|
||||
const (
|
||||
testCaCertFile = "rootca.crt"
|
||||
testCertFile = "crt.pem"
|
||||
testKeyFile = "key.pem"
|
||||
)
|
||||
|
||||
func testfile(t *testing.T, file string) (path string) {
|
||||
var err error
|
||||
if path, err = filepath.Abs(filepath.Join(tlsTestDir, file)); err != nil {
|
||||
t.Fatalf("error getting absolute path to test file %q: %v", file, err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func TestNewTLSConfig(t *testing.T) {
|
||||
certFile := testfile(t, testCertFile)
|
||||
keyFile := testfile(t, testKeyFile)
|
||||
caCertFile := testfile(t, testCaCertFile)
|
||||
insecureSkipTLSverify := false
|
||||
|
||||
{
|
||||
cfg, err := NewTLSConfig(
|
||||
WithInsecureSkipVerify(insecureSkipTLSverify),
|
||||
WithCertKeyPairFiles(certFile, keyFile),
|
||||
WithCAFile(caCertFile),
|
||||
)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if got := len(cfg.Certificates); got != 1 {
|
||||
t.Fatalf("expecting 1 client certificates, got %d", got)
|
||||
}
|
||||
if cfg.InsecureSkipVerify {
|
||||
t.Fatalf("insecure skip verify mismatch, expecting false")
|
||||
}
|
||||
if cfg.RootCAs == nil {
|
||||
t.Fatalf("mismatch tls RootCAs, expecting non-nil")
|
||||
}
|
||||
}
|
||||
{
|
||||
cfg, err := NewTLSConfig(
|
||||
WithInsecureSkipVerify(insecureSkipTLSverify),
|
||||
WithCAFile(caCertFile),
|
||||
)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if got := len(cfg.Certificates); got != 0 {
|
||||
t.Fatalf("expecting 0 client certificates, got %d", got)
|
||||
}
|
||||
if cfg.InsecureSkipVerify {
|
||||
t.Fatalf("insecure skip verify mismatch, expecting false")
|
||||
}
|
||||
if cfg.RootCAs == nil {
|
||||
t.Fatalf("mismatch tls RootCAs, expecting non-nil")
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
cfg, err := NewTLSConfig(
|
||||
WithInsecureSkipVerify(insecureSkipTLSverify),
|
||||
WithCertKeyPairFiles(certFile, keyFile),
|
||||
)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if got := len(cfg.Certificates); got != 1 {
|
||||
t.Fatalf("expecting 1 client certificates, got %d", got)
|
||||
}
|
||||
if cfg.InsecureSkipVerify {
|
||||
t.Fatalf("insecure skip verify mismatch, expecting false")
|
||||
}
|
||||
if cfg.RootCAs != nil {
|
||||
t.Fatalf("mismatch tls RootCAs, expecting nil")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
/*
|
||||
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 tlsutil
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const tlsTestDir = "../../testdata"
|
||||
|
||||
const (
|
||||
testCaCertFile = "rootca.crt"
|
||||
testCertFile = "crt.pem"
|
||||
testKeyFile = "key.pem"
|
||||
)
|
||||
|
||||
func TestClientConfig(t *testing.T) {
|
||||
opts := Options{
|
||||
CaCertFile: testfile(t, testCaCertFile),
|
||||
CertFile: testfile(t, testCertFile),
|
||||
KeyFile: testfile(t, testKeyFile),
|
||||
InsecureSkipVerify: false,
|
||||
}
|
||||
|
||||
cfg, err := ClientConfig(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("error building tls client config: %v", err)
|
||||
}
|
||||
|
||||
if got := len(cfg.Certificates); got != 1 {
|
||||
t.Fatalf("expecting 1 client certificates, got %d", got)
|
||||
}
|
||||
if cfg.InsecureSkipVerify {
|
||||
t.Fatalf("insecure skip verify mismatch, expecting false")
|
||||
}
|
||||
if cfg.RootCAs == nil {
|
||||
t.Fatalf("mismatch tls RootCAs, expecting non-nil")
|
||||
}
|
||||
}
|
||||
|
||||
func testfile(t *testing.T, file string) (path string) {
|
||||
var err error
|
||||
if path, err = filepath.Abs(filepath.Join(tlsTestDir, file)); err != nil {
|
||||
t.Fatalf("error getting absolute path to test file %q: %v", file, err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func TestNewClientTLS(t *testing.T) {
|
||||
certFile := testfile(t, testCertFile)
|
||||
keyFile := testfile(t, testKeyFile)
|
||||
caCertFile := testfile(t, testCaCertFile)
|
||||
insecureSkipTLSverify := false
|
||||
|
||||
cfg, err := NewClientTLS(certFile, keyFile, caCertFile, insecureSkipTLSverify)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if got := len(cfg.Certificates); got != 1 {
|
||||
t.Fatalf("expecting 1 client certificates, got %d", got)
|
||||
}
|
||||
if cfg.InsecureSkipVerify {
|
||||
t.Fatalf("insecure skip verify mismatch, expecting false")
|
||||
}
|
||||
if cfg.RootCAs == nil {
|
||||
t.Fatalf("mismatch tls RootCAs, expecting non-nil")
|
||||
}
|
||||
|
||||
cfg, err = NewClientTLS("", "", caCertFile, insecureSkipTLSverify)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if got := len(cfg.Certificates); got != 0 {
|
||||
t.Fatalf("expecting 0 client certificates, got %d", got)
|
||||
}
|
||||
if cfg.InsecureSkipVerify {
|
||||
t.Fatalf("insecure skip verify mismatch, expecting false")
|
||||
}
|
||||
if cfg.RootCAs == nil {
|
||||
t.Fatalf("mismatch tls RootCAs, expecting non-nil")
|
||||
}
|
||||
|
||||
cfg, err = NewClientTLS(certFile, keyFile, "", insecureSkipTLSverify)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if got := len(cfg.Certificates); got != 1 {
|
||||
t.Fatalf("expecting 1 client certificates, got %d", got)
|
||||
}
|
||||
if cfg.InsecureSkipVerify {
|
||||
t.Fatalf("insecure skip verify mismatch, expecting false")
|
||||
}
|
||||
if cfg.RootCAs != nil {
|
||||
t.Fatalf("mismatch tls RootCAs, expecting nil")
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
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 action
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
chart "helm.sh/helm/v4/pkg/chart/v2"
|
||||
)
|
||||
|
||||
// GetMetadata is the action for checking a given release's metadata.
|
||||
//
|
||||
// It provides the implementation of 'helm get metadata'.
|
||||
type GetMetadata struct {
|
||||
cfg *Configuration
|
||||
|
||||
Version int
|
||||
}
|
||||
|
||||
type Metadata struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Chart string `json:"chart" yaml:"chart"`
|
||||
Version string `json:"version" yaml:"version"`
|
||||
AppVersion string `json:"appVersion" yaml:"appVersion"`
|
||||
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
|
||||
Dependencies []*chart.Dependency `json:"dependencies,omitempty" yaml:"dependencies,omitempty"`
|
||||
Namespace string `json:"namespace" yaml:"namespace"`
|
||||
Revision int `json:"revision" yaml:"revision"`
|
||||
Status string `json:"status" yaml:"status"`
|
||||
DeployedAt string `json:"deployedAt" yaml:"deployedAt"`
|
||||
}
|
||||
|
||||
// NewGetMetadata creates a new GetMetadata object with the given configuration.
|
||||
func NewGetMetadata(cfg *Configuration) *GetMetadata {
|
||||
return &GetMetadata{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes 'helm get metadata' against the given release.
|
||||
func (g *GetMetadata) Run(name string) (*Metadata, error) {
|
||||
if err := g.cfg.KubeClient.IsReachable(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rel, err := g.cfg.releaseContent(name, g.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Metadata{
|
||||
Name: rel.Name,
|
||||
Chart: rel.Chart.Metadata.Name,
|
||||
Version: rel.Chart.Metadata.Version,
|
||||
AppVersion: rel.Chart.Metadata.AppVersion,
|
||||
Dependencies: rel.Chart.Metadata.Dependencies,
|
||||
Annotations: rel.Chart.Metadata.Annotations,
|
||||
Namespace: rel.Namespace,
|
||||
Revision: rel.Version,
|
||||
Status: rel.Info.Status.String(),
|
||||
DeployedAt: rel.Info.LastDeployed.Format(time.RFC3339),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FormattedDepNames formats metadata.dependencies names into a comma-separated list.
|
||||
func (m *Metadata) FormattedDepNames() string {
|
||||
depsNames := make([]string, 0, len(m.Dependencies))
|
||||
for _, dep := range m.Dependencies {
|
||||
depsNames = append(depsNames, dep.Name)
|
||||
}
|
||||
sort.StringSlice(depsNames).Sort()
|
||||
|
||||
return strings.Join(depsNames, ",")
|
||||
}
|
@ -0,0 +1,401 @@
|
||||
/*
|
||||
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 action
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
|
||||
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"
|
||||
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 {
|
||||
hookDefinitionString := convertHooksToCommaSeparated(hookDefinitions)
|
||||
return fmt.Sprintf(`kind: Pod
|
||||
metadata:
|
||||
name: finding-sharky,
|
||||
annotations:
|
||||
"helm.sh/hook": pre-install
|
||||
"helm.sh/hook-output-log-policy": %s
|
||||
spec:
|
||||
containers:
|
||||
- name: sharky-test
|
||||
image: fake-image
|
||||
cmd: fake-command`, hookDefinitionString)
|
||||
}
|
||||
|
||||
func podManifestWithOutputLogWithNamespace(hookDefinitions []release.HookOutputLogPolicy) string {
|
||||
hookDefinitionString := convertHooksToCommaSeparated(hookDefinitions)
|
||||
return fmt.Sprintf(`kind: Pod
|
||||
metadata:
|
||||
name: finding-george
|
||||
namespace: sneaky-namespace
|
||||
annotations:
|
||||
"helm.sh/hook": pre-install
|
||||
"helm.sh/hook-output-log-policy": %s
|
||||
spec:
|
||||
containers:
|
||||
- name: george-test
|
||||
image: fake-image
|
||||
cmd: fake-command`, hookDefinitionString)
|
||||
}
|
||||
|
||||
func jobManifestWithOutputLog(hookDefinitions []release.HookOutputLogPolicy) string {
|
||||
hookDefinitionString := convertHooksToCommaSeparated(hookDefinitions)
|
||||
return fmt.Sprintf(`kind: Job
|
||||
apiVersion: batch/v1
|
||||
metadata:
|
||||
name: losing-religion
|
||||
annotations:
|
||||
"helm.sh/hook": pre-install
|
||||
"helm.sh/hook-output-log-policy": %s
|
||||
spec:
|
||||
completions: 1
|
||||
parallelism: 1
|
||||
activeDeadlineSeconds: 30
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: religion-container
|
||||
image: religion-image
|
||||
cmd: religion-command`, hookDefinitionString)
|
||||
}
|
||||
|
||||
func jobManifestWithOutputLogWithNamespace(hookDefinitions []release.HookOutputLogPolicy) string {
|
||||
hookDefinitionString := convertHooksToCommaSeparated(hookDefinitions)
|
||||
return fmt.Sprintf(`kind: Job
|
||||
apiVersion: batch/v1
|
||||
metadata:
|
||||
name: losing-religion
|
||||
namespace: rem-namespace
|
||||
annotations:
|
||||
"helm.sh/hook": pre-install
|
||||
"helm.sh/hook-output-log-policy": %s
|
||||
spec:
|
||||
completions: 1
|
||||
parallelism: 1
|
||||
activeDeadlineSeconds: 30
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: religion-container
|
||||
image: religion-image
|
||||
cmd: religion-command`, hookDefinitionString)
|
||||
}
|
||||
|
||||
func convertHooksToCommaSeparated(hookDefinitions []release.HookOutputLogPolicy) string {
|
||||
var commaSeparated string
|
||||
for i, policy := range hookDefinitions {
|
||||
if i+1 == len(hookDefinitions) {
|
||||
commaSeparated += policy.String()
|
||||
} else {
|
||||
commaSeparated += policy.String() + ","
|
||||
}
|
||||
}
|
||||
return commaSeparated
|
||||
}
|
||||
|
||||
func TestInstallRelease_HookOutputLogsOnFailure(t *testing.T) {
|
||||
// Should output on failure with expected namespace if hook-failed is set
|
||||
runInstallForHooksWithFailure(t, podManifestWithOutputLogs([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "spaced", true)
|
||||
runInstallForHooksWithFailure(t, podManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "sneaky-namespace", true)
|
||||
runInstallForHooksWithFailure(t, jobManifestWithOutputLog([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "spaced", true)
|
||||
runInstallForHooksWithFailure(t, jobManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "rem-namespace", true)
|
||||
|
||||
// Should not output on failure with expected namespace if hook-succeed is set
|
||||
runInstallForHooksWithFailure(t, podManifestWithOutputLogs([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "", false)
|
||||
runInstallForHooksWithFailure(t, podManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "", false)
|
||||
runInstallForHooksWithFailure(t, jobManifestWithOutputLog([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "", false)
|
||||
runInstallForHooksWithFailure(t, jobManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "", false)
|
||||
}
|
||||
|
||||
func TestInstallRelease_HookOutputLogsOnSuccess(t *testing.T) {
|
||||
// Should output on success with expected namespace if hook-succeeded is set
|
||||
runInstallForHooksWithSuccess(t, podManifestWithOutputLogs([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "spaced", true)
|
||||
runInstallForHooksWithSuccess(t, podManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "sneaky-namespace", true)
|
||||
runInstallForHooksWithSuccess(t, jobManifestWithOutputLog([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "spaced", true)
|
||||
runInstallForHooksWithSuccess(t, jobManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded}), "rem-namespace", true)
|
||||
|
||||
// Should not output on success if hook-failed is set
|
||||
runInstallForHooksWithSuccess(t, podManifestWithOutputLogs([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "", false)
|
||||
runInstallForHooksWithSuccess(t, podManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "", false)
|
||||
runInstallForHooksWithSuccess(t, jobManifestWithOutputLog([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "", false)
|
||||
runInstallForHooksWithSuccess(t, jobManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnFailed}), "", false)
|
||||
}
|
||||
|
||||
func TestInstallRelease_HooksOutputLogsOnSuccessAndFailure(t *testing.T) {
|
||||
// Should output on success with expected namespace if hook-succeeded and hook-failed is set
|
||||
runInstallForHooksWithSuccess(t, podManifestWithOutputLogs([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "spaced", true)
|
||||
runInstallForHooksWithSuccess(t, podManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "sneaky-namespace", true)
|
||||
runInstallForHooksWithSuccess(t, jobManifestWithOutputLog([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "spaced", true)
|
||||
runInstallForHooksWithSuccess(t, jobManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "rem-namespace", true)
|
||||
|
||||
// Should output on failure if hook-succeeded and hook-failed is set
|
||||
runInstallForHooksWithFailure(t, podManifestWithOutputLogs([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "spaced", true)
|
||||
runInstallForHooksWithFailure(t, podManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "sneaky-namespace", true)
|
||||
runInstallForHooksWithFailure(t, jobManifestWithOutputLog([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "spaced", true)
|
||||
runInstallForHooksWithFailure(t, jobManifestWithOutputLogWithNamespace([]release.HookOutputLogPolicy{release.HookOutputOnSucceeded, release.HookOutputOnFailed}), "rem-namespace", true)
|
||||
}
|
||||
|
||||
func runInstallForHooksWithSuccess(t *testing.T, manifest, expectedNamespace string, shouldOutput bool) {
|
||||
var expectedOutput string
|
||||
if shouldOutput {
|
||||
expectedOutput = fmt.Sprintf("attempted to output logs for namespace: %s", expectedNamespace)
|
||||
}
|
||||
is := assert.New(t)
|
||||
instAction := installAction(t)
|
||||
instAction.ReleaseName = "failed-hooks"
|
||||
outBuffer := &bytes.Buffer{}
|
||||
instAction.cfg.KubeClient = &kubefake.PrintingKubeClient{Out: io.Discard, LogOutput: outBuffer}
|
||||
|
||||
templates := []*chart.File{
|
||||
{Name: "templates/hello", Data: []byte("hello: world")},
|
||||
{Name: "templates/hooks", Data: []byte(manifest)},
|
||||
}
|
||||
vals := map[string]interface{}{}
|
||||
|
||||
res, err := instAction.Run(buildChartWithTemplates(templates), vals)
|
||||
is.NoError(err)
|
||||
is.Equal(expectedOutput, outBuffer.String())
|
||||
is.Equal(release.StatusDeployed, res.Info.Status)
|
||||
}
|
||||
|
||||
func runInstallForHooksWithFailure(t *testing.T, manifest, expectedNamespace string, shouldOutput bool) {
|
||||
var expectedOutput string
|
||||
if shouldOutput {
|
||||
expectedOutput = fmt.Sprintf("attempted to output logs for namespace: %s", expectedNamespace)
|
||||
}
|
||||
is := assert.New(t)
|
||||
instAction := installAction(t)
|
||||
instAction.ReleaseName = "failed-hooks"
|
||||
failingClient := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
|
||||
failingClient.WatchUntilReadyError = fmt.Errorf("failed watch")
|
||||
instAction.cfg.KubeClient = failingClient
|
||||
outBuffer := &bytes.Buffer{}
|
||||
failingClient.PrintingKubeClient = kubefake.PrintingKubeClient{Out: io.Discard, LogOutput: outBuffer}
|
||||
|
||||
templates := []*chart.File{
|
||||
{Name: "templates/hello", Data: []byte("hello: world")},
|
||||
{Name: "templates/hooks", Data: []byte(manifest)},
|
||||
}
|
||||
vals := map[string]interface{}{}
|
||||
|
||||
res, err := instAction.Run(buildChartWithTemplates(templates), vals)
|
||||
is.Error(err)
|
||||
is.Contains(res.Info.Description, "failed pre-install")
|
||||
is.Equal(expectedOutput, outBuffer.String())
|
||||
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.")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue