feat(helm): add ability for --dry-run to do lookup functions

When a helm command is run with the --dry-run flag, it will try to connect to the cluster
if the value is 'server' to be able to render lookup functions.
Closes helm#8137

Signed-off-by: Tapas Kapadia <tapaskapadia10@gmail.com>
pull/9426/head
Tapas Kapadia 2 years ago
commit 4b7248e361

@ -17,7 +17,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@3.5.0 uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@3.5.0
with: with:
go-version: '1.18' go-version: '1.20'
- name: Install golangci-lint - name: Install golangci-lint
run: | run: |
curl -sSLO https://github.com/golangci/golangci-lint/releases/download/v$GOLANGCI_LINT_VERSION/golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz curl -sSLO https://github.com/golangci/golangci-lint/releases/download/v$GOLANGCI_LINT_VERSION/golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz
@ -26,8 +26,8 @@ jobs:
sudo mv golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64/golangci-lint /usr/local/bin/golangci-lint sudo mv golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64/golangci-lint /usr/local/bin/golangci-lint
rm -rf golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64* rm -rf golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64*
env: env:
GOLANGCI_LINT_VERSION: '1.46.2' GOLANGCI_LINT_VERSION: '1.51.2'
GOLANGCI_LINT_SHA256: '242cd4f2d6ac0556e315192e8555784d13da5d1874e51304711570769c4f2b9b' GOLANGCI_LINT_SHA256: '4de479eb9d9bc29da51aec1834e7c255b333723d38dbd56781c68e5dddc6a90b'
- name: Test style - name: Test style
run: make test-style run: make test-style
- name: Run unit tests - name: Run unit tests

@ -23,7 +23,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@3.5.0 uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@3.5.0
with: with:
go-version: '1.18' go-version: '1.20'
- name: Run unit tests - name: Run unit tests
run: make test-coverage run: make test-coverage
@ -54,7 +54,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@3.5.0 uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@3.5.0
with: with:
go-version: '1.18' go-version: '1.20'
- name: Run unit tests - name: Run unit tests
run: make test-coverage run: make test-coverage

@ -18,7 +18,6 @@ package main
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
@ -77,7 +76,7 @@ func TestCreateStarterCmd(t *testing.T) {
t.Logf("Created %s", dest) t.Logf("Created %s", dest)
} }
tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl") tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl")
if err := ioutil.WriteFile(tplpath, []byte("test"), 0644); err != nil { if err := os.WriteFile(tplpath, []byte("test"), 0644); err != nil {
t.Fatalf("Could not write template: %s", err) t.Fatalf("Could not write template: %s", err)
} }
@ -140,7 +139,7 @@ func TestCreateStarterAbsoluteCmd(t *testing.T) {
t.Logf("Created %s", dest) t.Logf("Created %s", dest)
} }
tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl") tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl")
if err := ioutil.WriteFile(tplpath, []byte("test"), 0644); err != nil { if err := os.WriteFile(tplpath, []byte("test"), 0644); err != nil {
t.Fatalf("Could not write template: %s", err) t.Fatalf("Could not write template: %s", err)
} }

@ -18,7 +18,7 @@ package main // import "helm.sh/helm/v3/cmd/helm"
import ( import (
"fmt" "fmt"
"io/ioutil" "io"
"log" "log"
"os" "os"
"strings" "strings"
@ -106,10 +106,10 @@ func loadReleasesInMemory(actionConfig *action.Configuration) {
return return
} }
actionConfig.KubeClient = &kubefake.PrintingKubeClient{Out: ioutil.Discard} actionConfig.KubeClient = &kubefake.PrintingKubeClient{Out: io.Discard}
for _, path := range filePaths { for _, path := range filePaths {
b, err := ioutil.ReadFile(path) b, err := os.ReadFile(path)
if err != nil { if err != nil {
log.Fatal("Unable to read memory driver data", err) log.Fatal("Unable to read memory driver data", err)
} }

@ -18,7 +18,7 @@ package main
import ( import (
"bytes" "bytes"
"io/ioutil" "io"
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
@ -92,7 +92,7 @@ func executeActionCommandStdinC(store *storage.Storage, in *os.File, cmd string)
actionConfig := &action.Configuration{ actionConfig := &action.Configuration{
Releases: store, Releases: store,
KubeClient: &kubefake.PrintingKubeClient{Out: ioutil.Discard}, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard},
Capabilities: chartutil.DefaultCapabilities, Capabilities: chartutil.DefaultCapabilities,
Log: func(format string, v ...interface{}) {}, Log: func(format string, v ...interface{}) {},
} }

@ -136,6 +136,15 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return compInstall(args, toComplete, client) return compInstall(args, toComplete, client)
}, },
RunE: func(_ *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSverify)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}
client.SetRegistryClient(registryClient)
if client.DryRunOption == "unchanged" {
client.DryRunOption = "none"
}
rel, err := runInstall(args, client, valueOpts, out) rel, err := runInstall(args, client, valueOpts, out)
if err != nil { if err != nil {
return errors.Wrap(err, "INSTALLATION FAILED") return errors.Wrap(err, "INSTALLATION FAILED")
@ -154,7 +163,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) { func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) {
f.BoolVar(&client.CreateNamespace, "create-namespace", false, "create the release namespace if not present") f.BoolVar(&client.CreateNamespace, "create-namespace", false, "create the release namespace if not present")
f.StringVar(&client.DryRunOption, "dry-run", "none", "simulate an install. If --dry-run is set with no option being specified or as 'client', it will not attempt cluster connections. Setting option as 'server' allows attempting cluster connections.") f.StringVar(&client.DryRunOption, "dry-run", "unchanged", "simulate an install. If --dry-run is set with no option being specified or as '--dry-run=client', it will not attempt cluster connections. Setting '--dry-run=server' allows attempting cluster connections.")
f.Lookup("dry-run").NoOptDefVal = "client" f.Lookup("dry-run").NoOptDefVal = "client"
f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy") f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy")
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install") f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install")

@ -19,7 +19,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"os" "os"
"os/exec" "os/exec"
@ -311,7 +310,7 @@ func addPluginCommands(plugin *plugin.Plugin, baseCmd *cobra.Command, cmds *plug
// loadFile takes a yaml file at the given path, parses it and returns a pluginCommand object // loadFile takes a yaml file at the given path, parses it and returns a pluginCommand object
func loadFile(path string) (*pluginCommand, error) { func loadFile(path string) (*pluginCommand, error) {
cmds := new(pluginCommand) cmds := new(pluginCommand)
b, err := ioutil.ReadFile(path) b, err := os.ReadFile(path)
if err != nil { if err != nil {
return cmds, fmt.Errorf("file (%s) not provided by plugin. No plugin auto-completion possible", path) return cmds, fmt.Errorf("file (%s) not provided by plugin. No plugin auto-completion possible", path)
} }

@ -19,7 +19,6 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -87,7 +86,7 @@ func newPackageCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
if client.DependencyUpdate { if client.DependencyUpdate {
downloadManager := &downloader.Manager{ downloadManager := &downloader.Manager{
Out: ioutil.Discard, Out: io.Discard,
ChartPath: path, ChartPath: path,
Keyring: client.Keyring, Keyring: client.Keyring,
Getters: p, Getters: p,

@ -64,6 +64,12 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client.Version = ">0.0.0-0" client.Version = ">0.0.0-0"
} }
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSverify)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}
client.SetRegistryClient(registryClient)
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
output, err := client.Run(args[i]) output, err := client.Run(args[i])
if err != nil { if err != nil {

@ -34,8 +34,15 @@ If the chart has an associated provenance file,
it will also be uploaded. it will also be uploaded.
` `
type registryPushOptions struct {
certFile string
keyFile string
caFile string
insecureSkipTLSverify bool
}
func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewPushWithOpts(action.WithPushConfig(cfg)) o := &registryPushOptions{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "push [chart] [remote]", Use: "push [chart] [remote]",
@ -60,8 +67,17 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
registryClient, err := newRegistryClient(o.certFile, o.keyFile, o.caFile, o.insecureSkipTLSverify)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}
cfg.RegistryClient = registryClient
chartRef := args[0] chartRef := args[0]
remote := args[1] remote := args[1]
client := action.NewPushWithOpts(action.WithPushConfig(cfg),
action.WithTLSClientConfig(o.certFile, o.keyFile, o.caFile),
action.WithInsecureSkipTLSVerify(o.insecureSkipTLSverify),
action.WithPushOptWriter(out))
client.Settings = settings client.Settings = settings
output, err := client.Run(chartRef, remote) output, err := client.Run(chartRef, remote)
if err != nil { if err != nil {
@ -72,5 +88,11 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
}, },
} }
f := cmd.Flags()
f.StringVar(&o.certFile, "cert-file", "", "identify registry client using this SSL certificate file")
f.StringVar(&o.keyFile, "key-file", "", "identify registry client using this SSL key file")
f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart upload")
return cmd return cmd
} }

@ -21,7 +21,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"strings" "strings"
@ -36,9 +35,18 @@ const registryLoginDesc = `
Authenticate to a remote registry. Authenticate to a remote registry.
` `
type registryLoginOptions struct {
username string
password string
passwordFromStdinOpt bool
certFile string
keyFile string
caFile string
insecure bool
}
func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
var usernameOpt, passwordOpt string o := &registryLoginOptions{}
var passwordFromStdinOpt, insecureOpt bool
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "login [host]", Use: "login [host]",
@ -49,20 +57,27 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
hostname := args[0] hostname := args[0]
username, password, err := getUsernamePassword(usernameOpt, passwordOpt, passwordFromStdinOpt) username, password, err := getUsernamePassword(o.username, o.password, o.passwordFromStdinOpt)
if err != nil { if err != nil {
return err return err
} }
return action.NewRegistryLogin(cfg).Run(out, hostname, username, password, insecureOpt) return action.NewRegistryLogin(cfg).Run(out, hostname, username, password,
action.WithCertFile(o.certFile),
action.WithKeyFile(o.keyFile),
action.WithCAFile(o.caFile),
action.WithInsecure(o.insecure))
}, },
} }
f := cmd.Flags() f := cmd.Flags()
f.StringVarP(&usernameOpt, "username", "u", "", "registry username") f.StringVarP(&o.username, "username", "u", "", "registry username")
f.StringVarP(&passwordOpt, "password", "p", "", "registry password or identity token") f.StringVarP(&o.password, "password", "p", "", "registry password or identity token")
f.BoolVarP(&passwordFromStdinOpt, "password-stdin", "", false, "read password or identity token from stdin") f.BoolVarP(&o.passwordFromStdinOpt, "password-stdin", "", false, "read password or identity token from stdin")
f.BoolVarP(&insecureOpt, "insecure", "", false, "allow connections to TLS registry without certs") f.BoolVarP(&o.insecure, "insecure", "", false, "allow connections to TLS registry without certs")
f.StringVar(&o.certFile, "cert-file", "", "identify registry client using this SSL certificate file")
f.StringVar(&o.keyFile, "key-file", "", "identify registry client using this SSL key file")
f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
return cmd return cmd
} }
@ -74,7 +89,7 @@ func getUsernamePassword(usernameOpt string, passwordOpt string, passwordFromStd
password := passwordOpt password := passwordOpt
if passwordFromStdinOpt { if passwordFromStdinOpt {
passwordFromStdin, err := ioutil.ReadAll(os.Stdin) passwordFromStdin, err := io.ReadAll(os.Stdin)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }

@ -20,7 +20,6 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -134,7 +133,7 @@ func (o *repoAddOptions) run(out io.Writer) error {
return err return err
} }
b, err := ioutil.ReadFile(o.repoFile) b, err := os.ReadFile(o.repoFile)
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
return err return err
} }

@ -18,7 +18,7 @@ package main
import ( import (
"fmt" "fmt"
"io/ioutil" "io"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -102,7 +102,7 @@ func TestRepoAdd(t *testing.T) {
} }
os.Setenv(xdg.CacheHomeEnvVar, rootDir) os.Setenv(xdg.CacheHomeEnvVar, rootDir)
if err := o.run(ioutil.Discard); err != nil { if err := o.run(io.Discard); err != nil {
t.Error(err) t.Error(err)
} }
@ -126,11 +126,11 @@ func TestRepoAdd(t *testing.T) {
o.forceUpdate = true o.forceUpdate = true
if err := o.run(ioutil.Discard); err != nil { if err := o.run(io.Discard); err != nil {
t.Errorf("Repository was not updated: %s", err) t.Errorf("Repository was not updated: %s", err)
} }
if err := o.run(ioutil.Discard); err != nil { if err := o.run(io.Discard); err != nil {
t.Errorf("Duplicate repository name was added") t.Errorf("Duplicate repository name was added")
} }
} }
@ -159,7 +159,7 @@ func TestRepoAddCheckLegalName(t *testing.T) {
wantErrorMsg := fmt.Sprintf("repository name (%s) contains '/', please specify a different name without '/'", testRepoName) wantErrorMsg := fmt.Sprintf("repository name (%s) contains '/', please specify a different name without '/'", testRepoName)
if err := o.run(ioutil.Discard); err != nil { if err := o.run(io.Discard); err != nil {
if wantErrorMsg != err.Error() { if wantErrorMsg != err.Error() {
t.Fatalf("Actual error %s, not equal to expected error %s", err, wantErrorMsg) t.Fatalf("Actual error %s, not equal to expected error %s", err, wantErrorMsg)
} }
@ -211,14 +211,14 @@ func repoAddConcurrent(t *testing.T, testName, repoFile string) {
forceUpdate: false, forceUpdate: false,
repoFile: repoFile, repoFile: repoFile,
} }
if err := o.run(ioutil.Discard); err != nil { if err := o.run(io.Discard); err != nil {
t.Error(err) t.Error(err)
} }
}(fmt.Sprintf("%s-%d", testName, i)) }(fmt.Sprintf("%s-%d", testName, i))
} }
wg.Wait() wg.Wait()
b, err := ioutil.ReadFile(repoFile) b, err := os.ReadFile(repoFile)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

@ -19,7 +19,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -119,7 +118,7 @@ func TestUpdateCustomCacheCmd(t *testing.T) {
repoFile: filepath.Join(ts.Root(), "repositories.yaml"), repoFile: filepath.Join(ts.Root(), "repositories.yaml"),
repoCache: cachePath, repoCache: cachePath,
} }
b := ioutil.Discard b := io.Discard
if err := o.run(b); err != nil { if err := o.run(b); err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -17,7 +17,7 @@ package require
import ( import (
"fmt" "fmt"
"io/ioutil" "io"
"strings" "strings"
"testing" "testing"
@ -71,7 +71,7 @@ func runTestCases(t *testing.T, testCases []testCase) {
Args: tc.validateFunc, Args: tc.validateFunc,
} }
cmd.SetArgs(tc.args) cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(io.Discard)
err := cmd.Execute() err := cmd.Execute()
if tc.wantError == "" { if tc.wantError == "" {

@ -152,12 +152,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
flags.ParseErrorsWhitelist.UnknownFlags = true flags.ParseErrorsWhitelist.UnknownFlags = true
flags.Parse(args) flags.Parse(args)
registryClient, err := registry.NewClient( registryClient, err := newDefaultRegistryClient()
registry.ClientOptDebug(settings.Debug),
registry.ClientOptEnableCache(true),
registry.ClientOptWriter(os.Stderr),
registry.ClientOptCredentialsFile(settings.RegistryConfig),
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -261,3 +256,43 @@ func checkForExpiredRepos(repofile string) {
} }
} }
func newRegistryClient(certFile, keyFile, caFile string, insecureSkipTLSverify bool) (*registry.Client, error) {
if certFile != "" && keyFile != "" || caFile != "" || insecureSkipTLSverify {
registryClient, err := newRegistryClientWithTLS(certFile, keyFile, caFile, insecureSkipTLSverify)
if err != nil {
return nil, err
}
return registryClient, nil
}
registryClient, err := newDefaultRegistryClient()
if err != nil {
return nil, err
}
return registryClient, nil
}
func newDefaultRegistryClient() (*registry.Client, error) {
// Create a new registry client
registryClient, err := registry.NewClient(
registry.ClientOptDebug(settings.Debug),
registry.ClientOptEnableCache(true),
registry.ClientOptWriter(os.Stderr),
registry.ClientOptCredentialsFile(settings.RegistryConfig),
)
if err != nil {
return nil, err
}
return registryClient, nil
}
func newRegistryClientWithTLS(certFile, keyFile, caFile string, insecureSkipTLSverify bool) (*registry.Client, error) {
// Create a new registry client
registryClient, err := registry.NewRegistryClientWithTLS(os.Stderr, certFile, keyFile, caFile, insecureSkipTLSverify,
settings.RegistryConfig, settings.Debug,
)
if err != nil {
return nil, err
}
return registryClient, nil
}

@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
/*Package search provides client-side repository searching. /*
Package search provides client-side repository searching.
This supports building an in-memory search index based on the contents of This supports building an in-memory search index based on the contents of
multiple repositories, and then using string matching or regular expressions multiple repositories, and then using string matching or regular expressions

@ -21,7 +21,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -259,7 +258,7 @@ func compListChartsOfRepo(repoName string, prefix string) []string {
var charts []string var charts []string
path := filepath.Join(settings.RepositoryCache, helmpath.CacheChartsFile(repoName)) path := filepath.Join(settings.RepositoryCache, helmpath.CacheChartsFile(repoName))
content, err := ioutil.ReadFile(path) content, err := os.ReadFile(path)
if err == nil { if err == nil {
scanner := bufio.NewScanner(bytes.NewReader(content)) scanner := bufio.NewScanner(bytes.NewReader(content))
for scanner.Scan() { for scanner.Scan() {

@ -84,6 +84,10 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
ValidArgsFunction: validArgsFunc, ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowAll client.OutputFormat = action.ShowAll
err := addRegistryClient(client)
if err != nil {
return err
}
output, err := runShow(args, client) output, err := runShow(args, client)
if err != nil { if err != nil {
return err return err
@ -101,6 +105,10 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
ValidArgsFunction: validArgsFunc, ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowValues client.OutputFormat = action.ShowValues
err := addRegistryClient(client)
if err != nil {
return err
}
output, err := runShow(args, client) output, err := runShow(args, client)
if err != nil { if err != nil {
return err return err
@ -118,6 +126,10 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
ValidArgsFunction: validArgsFunc, ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowChart client.OutputFormat = action.ShowChart
err := addRegistryClient(client)
if err != nil {
return err
}
output, err := runShow(args, client) output, err := runShow(args, client)
if err != nil { if err != nil {
return err return err
@ -135,6 +147,10 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
ValidArgsFunction: validArgsFunc, ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowReadme client.OutputFormat = action.ShowReadme
err := addRegistryClient(client)
if err != nil {
return err
}
output, err := runShow(args, client) output, err := runShow(args, client)
if err != nil { if err != nil {
return err return err
@ -152,6 +168,10 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
ValidArgsFunction: validArgsFunc, ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowCRDs client.OutputFormat = action.ShowCRDs
err := addRegistryClient(client)
if err != nil {
return err
}
output, err := runShow(args, client) output, err := runShow(args, client)
if err != nil { if err != nil {
return err return err
@ -204,3 +224,12 @@ func runShow(args []string, client *action.Show) (string, error) {
} }
return client.Run(cp) return client.Run(cp)
} }
func addRegistryClient(client *action.Show) error {
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSverify)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}
client.SetRegistryClient(registryClient)
return nil
}

@ -73,6 +73,16 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client.KubeVersion = parsedKubeVersion client.KubeVersion = parsedKubeVersion
} }
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSverify)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}
client.SetRegistryClient(registryClient)
if client.DryRunOption == "unchanged" {
client.DryRunOption = "true"
}
client.DryRun = true
client.ReleaseName = "release-name" client.ReleaseName = "release-name"
client.Replace = true // Skip the name check client.Replace = true // Skip the name check
client.ClientOnly = !validate client.ClientOnly = !validate

@ -90,6 +90,15 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
client.Namespace = settings.Namespace() client.Namespace = settings.Namespace()
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSverify)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}
client.SetRegistryClient(registryClient)
if client.DryRunOption == "unchanged" {
client.DryRunOption = "none"
}
// Fixes #7002 - Support reading values from STDIN for `upgrade` command // Fixes #7002 - Support reading values from STDIN for `upgrade` command
// Must load values AFTER determining if we have to call install so that values loaded from stdin are are not read twice // Must load values AFTER determining if we have to call install so that values loaded from stdin are are not read twice
if client.Install { if client.Install {
@ -220,7 +229,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&createNamespace, "create-namespace", false, "if --install is set, create the release namespace if not present") f.BoolVar(&createNamespace, "create-namespace", false, "if --install is set, create the release namespace if not present")
f.BoolVarP(&client.Install, "install", "i", false, "if a release by this name doesn't already exist, run an install") f.BoolVarP(&client.Install, "install", "i", false, "if a release by this name doesn't already exist, run an install")
f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored") f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored")
f.StringVar(&client.DryRunOption, "dry-run", "none", "simulate an install. If --dry-run is set with no option being specified or as 'client', it will not attempt cluster connections. Setting option as 'server' allows attempting cluster connections.") f.StringVar(&client.DryRunOption, "dry-run", "unchanged", "simulate an install. If --dry-run is set with no option being specified or as '--dry-run=client', it will not attempt cluster connections. Setting '--dry-run=server' allows attempting cluster connections.")
f.Lookup("dry-run").NoOptDefVal = "client" f.Lookup("dry-run").NoOptDefVal = "client"
f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
f.MarkDeprecated("recreate-pods", "functionality will no longer be updated. Consult the documentation for other methods to recreate pods") f.MarkDeprecated("recreate-pods", "functionality will no longer be updated. Consult the documentation for other methods to recreate pods")

@ -18,7 +18,6 @@ package main
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -359,7 +358,7 @@ func TestUpgradeInstallWithValuesFromStdin(t *testing.T) {
func prepareMockRelease(releaseName string, t *testing.T) (func(n string, v int, ch *chart.Chart) *release.Release, *chart.Chart, string) { func prepareMockRelease(releaseName string, t *testing.T) (func(n string, v int, ch *chart.Chart) *release.Release, *chart.Chart, string) {
tmpChart := ensure.TempDir(t) tmpChart := ensure.TempDir(t)
configmapData, err := ioutil.ReadFile("testdata/testcharts/upgradetest/templates/configmap.yaml") configmapData, err := os.ReadFile("testdata/testcharts/upgradetest/templates/configmap.yaml")
if err != nil { if err != nil {
t.Fatalf("Error loading template yaml %v", err) t.Fatalf("Error loading template yaml %v", err)
} }

@ -1,6 +1,6 @@
module helm.sh/helm/v3 module helm.sh/helm/v3
go 1.17 go 1.19
require ( require (
github.com/BurntSushi/toml v1.2.1 github.com/BurntSushi/toml v1.2.1
@ -14,6 +14,7 @@ require (
github.com/cyphar/filepath-securejoin v0.2.3 github.com/cyphar/filepath-securejoin v0.2.3
github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2
github.com/evanphx/json-patch v5.6.0+incompatible github.com/evanphx/json-patch v5.6.0+incompatible
github.com/foxcpp/go-mockdns v1.0.0
github.com/gobwas/glob v0.2.3 github.com/gobwas/glob v0.2.3
github.com/gofrs/flock v0.8.1 github.com/gofrs/flock v0.8.1
github.com/gosuri/uitable v0.0.4 github.com/gosuri/uitable v0.0.4
@ -107,6 +108,7 @@ require (
github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // 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/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/locker v1.0.1 // indirect github.com/moby/locker v1.0.1 // indirect

833
go.sum

File diff suppressed because it is too large Load Diff

@ -18,7 +18,6 @@ package fileutil
import ( import (
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -28,7 +27,7 @@ import (
// AtomicWriteFile atomically (as atomic as os.Rename allows) writes a file to a // AtomicWriteFile atomically (as atomic as os.Rename allows) writes a file to a
// disk. // disk.
func AtomicWriteFile(filename string, reader io.Reader, mode os.FileMode) error { func AtomicWriteFile(filename string, reader io.Reader, mode os.FileMode) error {
tempFile, err := ioutil.TempFile(filepath.Split(filename)) tempFile, err := os.CreateTemp(filepath.Split(filename))
if err != nil { if err != nil {
return err return err
} }

@ -18,7 +18,6 @@ package fileutil
import ( import (
"bytes" "bytes"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
@ -37,7 +36,7 @@ func TestAtomicWriteFile(t *testing.T) {
t.Errorf("AtomicWriteFile error: %s", err) t.Errorf("AtomicWriteFile error: %s", err)
} }
got, err := ioutil.ReadFile(testpath) got, err := os.ReadFile(testpath)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
/*Package ignore provides tools for writing ignore files (a la .gitignore). /*
Package ignore provides tools for writing ignore files (a la .gitignore).
This provides both an ignore parser and a file-aware processor. This provides both an ignore parser and a file-aware processor.

@ -17,7 +17,6 @@ limitations under the License.
package ensure package ensure
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
@ -44,7 +43,7 @@ func HelmHome(t *testing.T) func() {
// TempDir ensures a scratch test directory for unit testing purposes. // TempDir ensures a scratch test directory for unit testing purposes.
func TempDir(t *testing.T) string { func TempDir(t *testing.T) string {
t.Helper() t.Helper()
d, err := ioutil.TempDir("", "helm") d, err := os.MkdirTemp("", "helm")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -63,7 +62,7 @@ func TempDir(t *testing.T) string {
func TempFile(t *testing.T, name string, data []byte) string { func TempFile(t *testing.T, name string, data []byte) string {
path := TempDir(t) path := TempDir(t)
filename := filepath.Join(path, name) filename := filepath.Join(path, name)
if err := ioutil.WriteFile(filename, data, 0755); err != nil { if err := os.WriteFile(filename, data, 0755); err != nil {
t.Fatal(err) t.Fatal(err)
} }
return path return path

@ -19,7 +19,7 @@ package test
import ( import (
"bytes" "bytes"
"flag" "flag"
"io/ioutil" "os"
"path/filepath" "path/filepath"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -53,7 +53,7 @@ func AssertGoldenString(t TestingT, actual, filename string) {
func AssertGoldenFile(t TestingT, actualFileName string, expectedFilename string) { func AssertGoldenFile(t TestingT, actualFileName string, expectedFilename string) {
t.Helper() t.Helper()
actual, err := ioutil.ReadFile(actualFileName) actual, err := os.ReadFile(actualFileName)
if err != nil { if err != nil {
t.Fatalf("%v", err) t.Fatalf("%v", err)
} }
@ -73,7 +73,7 @@ func compare(actual []byte, filename string) error {
return err return err
} }
expected, err := ioutil.ReadFile(filename) expected, err := os.ReadFile(filename)
if err != nil { if err != nil {
return errors.Wrapf(err, "unable to read testdata %s", filename) return errors.Wrapf(err, "unable to read testdata %s", filename)
} }
@ -88,7 +88,7 @@ func update(filename string, in []byte) error {
if !*updateGolden { if !*updateGolden {
return nil return nil
} }
return ioutil.WriteFile(filename, normalize(in), 0666) return os.WriteFile(filename, normalize(in), 0666)
} }
func normalize(in []byte) []byte { func normalize(in []byte) []byte {

@ -32,7 +32,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package fs package fs
import ( import (
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@ -137,7 +136,7 @@ func TestCopyDir(t *testing.T) {
t.Fatalf("expected %s to be a directory", dn) t.Fatalf("expected %s to be a directory", dn)
} }
got, err := ioutil.ReadFile(fn) got, err := os.ReadFile(fn)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -337,7 +336,7 @@ func TestCopyFile(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
got, err := ioutil.ReadFile(destf) got, err := os.ReadFile(destf)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -396,11 +395,11 @@ func TestCopyFileSymlink(t *testing.T) {
// Creating symlinks on Windows require an additional permission // Creating symlinks on Windows require an additional permission
// regular users aren't granted usually. So we copy the file // regular users aren't granted usually. So we copy the file
// content as a fall back instead of creating a real symlink. // content as a fall back instead of creating a real symlink.
srcb, err := ioutil.ReadFile(symlink) srcb, err := os.ReadFile(symlink)
if err != nil { if err != nil {
t.Fatalf("%+v", err) t.Fatalf("%+v", err)
} }
dstb, err := ioutil.ReadFile(dst) dstb, err := os.ReadFile(dst)
if err != nil { if err != nil {
t.Fatalf("%+v", err) t.Fatalf("%+v", err)
} }

@ -19,14 +19,16 @@ package tlsutil
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"io/ioutil" "os"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
// NewClientTLS returns tls.Config appropriate for client auth. // NewClientTLS returns tls.Config appropriate for client auth.
func NewClientTLS(certFile, keyFile, caFile string) (*tls.Config, error) { func NewClientTLS(certFile, keyFile, caFile string, insecureSkipTLSverify bool) (*tls.Config, error) {
config := tls.Config{} config := tls.Config{
InsecureSkipVerify: insecureSkipTLSverify,
}
if certFile != "" && keyFile != "" { if certFile != "" && keyFile != "" {
cert, err := CertFromFilePair(certFile, keyFile) cert, err := CertFromFilePair(certFile, keyFile)
@ -52,7 +54,7 @@ func NewClientTLS(certFile, keyFile, caFile string) (*tls.Config, error) {
// Returns an error if the file could not be read, a certificate could not // Returns an error if the file could not be read, a certificate could not
// be parsed, or if the file does not contain any certificates // be parsed, or if the file does not contain any certificates
func CertPoolFromFile(filename string) (*x509.CertPool, error) { func CertPoolFromFile(filename string) (*x509.CertPool, error) {
b, err := ioutil.ReadFile(filename) b, err := os.ReadFile(filename)
if err != nil { if err != nil {
return nil, errors.Errorf("can't read CA file: %v", filename) return nil, errors.Errorf("can't read CA file: %v", filename)
} }

@ -65,8 +65,9 @@ func TestNewClientTLS(t *testing.T) {
certFile := testfile(t, testCertFile) certFile := testfile(t, testCertFile)
keyFile := testfile(t, testKeyFile) keyFile := testfile(t, testKeyFile)
caCertFile := testfile(t, testCaCertFile) caCertFile := testfile(t, testCaCertFile)
insecureSkipTLSverify := false
cfg, err := NewClientTLS(certFile, keyFile, caCertFile) cfg, err := NewClientTLS(certFile, keyFile, caCertFile, insecureSkipTLSverify)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -81,7 +82,7 @@ func TestNewClientTLS(t *testing.T) {
t.Fatalf("mismatch tls RootCAs, expecting non-nil") t.Fatalf("mismatch tls RootCAs, expecting non-nil")
} }
cfg, err = NewClientTLS("", "", caCertFile) cfg, err = NewClientTLS("", "", caCertFile, insecureSkipTLSverify)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -96,7 +97,7 @@ func TestNewClientTLS(t *testing.T) {
t.Fatalf("mismatch tls RootCAs, expecting non-nil") t.Fatalf("mismatch tls RootCAs, expecting non-nil")
} }
cfg, err = NewClientTLS(certFile, keyFile, "") cfg, err = NewClientTLS(certFile, keyFile, "", insecureSkipTLSverify)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

@ -17,7 +17,7 @@ package action
import ( import (
"flag" "flag"
"io/ioutil" "io"
"testing" "testing"
fakeclientset "k8s.io/client-go/kubernetes/fake" fakeclientset "k8s.io/client-go/kubernetes/fake"
@ -44,7 +44,7 @@ func actionConfigFixture(t *testing.T) *Configuration {
return &Configuration{ return &Configuration{
Releases: storage.Init(driver.NewMemory()), Releases: storage.Init(driver.NewMemory()),
KubeClient: &kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: ioutil.Discard}}, KubeClient: &kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}},
Capabilities: chartutil.DefaultCapabilities, Capabilities: chartutil.DefaultCapabilities,
RegistryClient: registryClient, RegistryClient: registryClient,
Log: func(format string, v ...interface{}) { Log: func(format string, v ...interface{}) {

@ -20,7 +20,7 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"io/ioutil" "io"
"net/url" "net/url"
"os" "os"
"path" "path"
@ -34,6 +34,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/resource" "k8s.io/cli-runtime/pkg/resource"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
@ -137,6 +138,11 @@ func NewInstall(cfg *Configuration) *Install {
return in return in
} }
// SetRegistryClient sets the registry client for the install action
func (i *Install) SetRegistryClient(registryClient *registry.Client) {
i.ChartPathOptions.registryClient = registryClient
}
func (i *Install) installCRDs(crds []chart.CRD) error { func (i *Install) installCRDs(crds []chart.CRD) error {
// We do these one file at a time in the order they were read. // We do these one file at a time in the order they were read.
totalItems := []*resource.Info{} totalItems := []*resource.Info{}
@ -160,22 +166,38 @@ func (i *Install) installCRDs(crds []chart.CRD) error {
totalItems = append(totalItems, res...) totalItems = append(totalItems, res...)
} }
if len(totalItems) > 0 { if len(totalItems) > 0 {
// Invalidate the local cache, since it will not have the new CRDs // Give time for the CRD to be recognized.
// present. if err := i.cfg.KubeClient.Wait(totalItems, 60*time.Second); err != nil {
return err
}
// If we have already gathered the capabilities, we need to invalidate
// the cache so that the new CRDs are recognized. This should only be
// the case when an action configuration is reused for multiple actions,
// as otherwise it is later loaded by ourselves when getCapabilities
// is called later on in the installation process.
if i.cfg.Capabilities != nil {
discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient() discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient()
if err != nil { if err != nil {
return err return err
} }
i.cfg.Log("Clearing discovery cache") i.cfg.Log("Clearing discovery cache")
discoveryClient.Invalidate() discoveryClient.Invalidate()
// Give time for the CRD to be recognized.
if err := i.cfg.KubeClient.Wait(totalItems, 60*time.Second); err != nil { _, _ = discoveryClient.ServerGroups()
return err
} }
// Make sure to force a rebuild of the cache. // Invalidate the REST mapper, since it will not have the new CRDs
discoveryClient.ServerGroups() // present.
restMapper, err := i.cfg.RESTClientGetter.ToRESTMapper()
if err != nil {
return err
}
if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok {
i.cfg.Log("Clearing REST mapper cache")
resettable.Reset()
}
} }
return nil return nil
} }
@ -212,7 +234,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
} }
var interactWithRemote bool var interactWithRemote bool
if !i.DryRun || i.DryRunOption == "server" { if !i.DryRun || i.DryRunOption == "server" || i.DryRunOption == "none" || i.DryRunOption == "false" {
interactWithRemote = true interactWithRemote = true
} }
@ -235,7 +257,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
i.cfg.Capabilities.KubeVersion = *i.KubeVersion i.cfg.Capabilities.KubeVersion = *i.KubeVersion
} }
i.cfg.Capabilities.APIVersions = append(i.cfg.Capabilities.APIVersions, i.APIVersions...) i.cfg.Capabilities.APIVersions = append(i.cfg.Capabilities.APIVersions, i.APIVersions...)
i.cfg.KubeClient = &kubefake.PrintingKubeClient{Out: ioutil.Discard} i.cfg.KubeClient = &kubefake.PrintingKubeClient{Out: io.Discard}
mem := driver.NewMemory() mem := driver.NewMemory()
mem.SetNamespace(i.Namespace) mem.SetNamespace(i.Namespace)
@ -688,8 +710,6 @@ OUTER:
// //
// If 'verify' was set on ChartPathOptions, this will attempt to also verify the chart. // If 'verify' was set on ChartPathOptions, this will attempt to also verify the chart.
func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (string, error) { func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (string, error) {
// If there is no registry client and the name is in an OCI registry return
// an error and a lookup will not occur.
if registry.IsOCI(name) && c.registryClient == nil { if registry.IsOCI(name) && c.registryClient == nil {
return "", fmt.Errorf("unable to lookup chart %q, missing registry client", name) return "", fmt.Errorf("unable to lookup chart %q, missing registry client", name)
} }

@ -19,7 +19,7 @@ package action
import ( import (
"context" "context"
"fmt" "fmt"
"io/ioutil" "io"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@ -132,7 +132,7 @@ func TestInstallReleaseClientOnly(t *testing.T) {
instAction.Run(buildChart(), nil) // disregard output instAction.Run(buildChart(), nil) // disregard output
is.Equal(instAction.cfg.Capabilities, chartutil.DefaultCapabilities) is.Equal(instAction.cfg.Capabilities, chartutil.DefaultCapabilities)
is.Equal(instAction.cfg.KubeClient, &kubefake.PrintingKubeClient{Out: ioutil.Discard}) is.Equal(instAction.cfg.KubeClient, &kubefake.PrintingKubeClient{Out: io.Discard})
} }
func TestInstallRelease_NoName(t *testing.T) { func TestInstallRelease_NoName(t *testing.T) {

@ -17,7 +17,6 @@ limitations under the License.
package action package action
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -91,7 +90,7 @@ func lintChart(path string, vals map[string]interface{}, namespace string, stric
linter := support.Linter{} linter := support.Linter{}
if strings.HasSuffix(path, ".tgz") || strings.HasSuffix(path, ".tar.gz") { if strings.HasSuffix(path, ".tgz") || strings.HasSuffix(path, ".tar.gz") {
tempDir, err := ioutil.TempDir("", "helm-lint") tempDir, err := os.MkdirTemp("", "helm-lint")
if err != nil { if err != nil {
return linter, errors.Wrap(err, "unable to create temp dir to extract tarball") return linter, errors.Wrap(err, "unable to create temp dir to extract tarball")
} }

@ -19,7 +19,6 @@ package action
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"syscall" "syscall"
@ -137,7 +136,7 @@ func (p *Package) Clearsign(filename string) error {
return err return err
} }
return ioutil.WriteFile(filename+".prov", []byte(sig), 0644) return os.WriteFile(filename+".prov", []byte(sig), 0644)
} }
// promptUser implements provenance.PassphraseFetcher // promptUser implements provenance.PassphraseFetcher

@ -17,7 +17,6 @@ limitations under the License.
package action package action
import ( import (
"io/ioutil"
"os" "os"
"path" "path"
"testing" "testing"
@ -71,7 +70,7 @@ func TestPassphraseFileFetcher_WithInvalidStdin(t *testing.T) {
directory := ensure.TempDir(t) directory := ensure.TempDir(t)
defer os.RemoveAll(directory) defer os.RemoveAll(directory)
stdin, err := ioutil.TempFile(directory, "non-existing") stdin, err := os.CreateTemp(directory, "non-existing")
if err != nil { if err != nil {
t.Fatal("Unable to create test file", err) t.Fatal("Unable to create test file", err)
} }

@ -18,7 +18,6 @@ package action
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -72,6 +71,11 @@ func NewPullWithOpts(opts ...PullOpt) *Pull {
return p return p
} }
// SetRegistryClient sets the registry client on the pull configuration object.
func (p *Pull) SetRegistryClient(client *registry.Client) {
p.cfg.RegistryClient = client
}
// Run executes 'helm pull' against the given release. // Run executes 'helm pull' against the given release.
func (p *Pull) Run(chartRef string) (string, error) { func (p *Pull) Run(chartRef string) (string, error) {
var out strings.Builder var out strings.Builder
@ -95,6 +99,7 @@ func (p *Pull) Run(chartRef string) (string, error) {
if registry.IsOCI(chartRef) { if registry.IsOCI(chartRef) {
c.Options = append(c.Options, c.Options = append(c.Options,
getter.WithRegistryClient(p.cfg.RegistryClient)) getter.WithRegistryClient(p.cfg.RegistryClient))
c.RegistryClient = p.cfg.RegistryClient
} }
if p.Verify { if p.Verify {
@ -108,7 +113,7 @@ func (p *Pull) Run(chartRef string) (string, error) {
dest := p.DestDir dest := p.DestDir
if p.Untar { if p.Untar {
var err error var err error
dest, err = ioutil.TempDir("", "helm-") dest, err = os.MkdirTemp("", "helm-")
if err != nil { if err != nil {
return out.String(), errors.Wrap(err, "failed to untar") return out.String(), errors.Wrap(err, "failed to untar")
} }

@ -17,6 +17,7 @@ limitations under the License.
package action package action
import ( import (
"io"
"strings" "strings"
"helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/cli"
@ -31,6 +32,11 @@ import (
type Push struct { type Push struct {
Settings *cli.EnvSettings Settings *cli.EnvSettings
cfg *Configuration cfg *Configuration
certFile string
keyFile string
caFile string
insecureSkipTLSverify bool
out io.Writer
} }
// PushOpt is a type of function that sets options for a push action. // PushOpt is a type of function that sets options for a push action.
@ -43,6 +49,29 @@ func WithPushConfig(cfg *Configuration) PushOpt {
} }
} }
// WithTLSClientConfig sets the certFile, keyFile, and caFile fields on the push configuration object.
func WithTLSClientConfig(certFile, keyFile, caFile string) PushOpt {
return func(p *Push) {
p.certFile = certFile
p.keyFile = keyFile
p.caFile = caFile
}
}
// WithInsecureSkipTLSVerify determines if a TLS Certificate will be checked
func WithInsecureSkipTLSVerify(insecureSkipTLSVerify bool) PushOpt {
return func(p *Push) {
p.insecureSkipTLSverify = insecureSkipTLSVerify
}
}
// WithOptWriter sets the registryOut field on the push configuration object.
func WithPushOptWriter(out io.Writer) PushOpt {
return func(p *Push) {
p.out = out
}
}
// NewPushWithOpts creates a new push, with configuration options. // NewPushWithOpts creates a new push, with configuration options.
func NewPushWithOpts(opts ...PushOpt) *Push { func NewPushWithOpts(opts ...PushOpt) *Push {
p := &Push{} p := &Push{}
@ -59,10 +88,14 @@ func (p *Push) Run(chartRef string, remote string) (string, error) {
c := uploader.ChartUploader{ c := uploader.ChartUploader{
Out: &out, Out: &out,
Pushers: pusher.All(p.Settings), Pushers: pusher.All(p.Settings),
Options: []pusher.Option{}, Options: []pusher.Option{
pusher.WithTLSClientConfig(p.certFile, p.keyFile, p.caFile),
pusher.WithInsecureSkipTLSVerify(p.insecureSkipTLSverify),
},
} }
if registry.IsOCI(remote) { if registry.IsOCI(remote) {
// Don't use the default registry client if tls options are set.
c.Options = append(c.Options, pusher.WithRegistryClient(p.cfg.RegistryClient)) c.Options = append(c.Options, pusher.WithRegistryClient(p.cfg.RegistryClient))
} }

@ -25,6 +25,44 @@ import (
// RegistryLogin performs a registry login operation. // RegistryLogin performs a registry login operation.
type RegistryLogin struct { type RegistryLogin struct {
cfg *Configuration cfg *Configuration
certFile string
keyFile string
caFile string
insecure bool
}
type RegistryLoginOpt func(*RegistryLogin) error
// WithCertFile specifies the path to the certificate file to use for TLS.
func WithCertFile(certFile string) RegistryLoginOpt {
return func(r *RegistryLogin) error {
r.certFile = certFile
return nil
}
}
// WithKeyFile specifies whether to very certificates when communicating.
func WithInsecure(insecure bool) RegistryLoginOpt {
return func(r *RegistryLogin) error {
r.insecure = insecure
return nil
}
}
// WithKeyFile specifies the path to the key file to use for TLS.
func WithKeyFile(keyFile string) RegistryLoginOpt {
return func(r *RegistryLogin) error {
r.keyFile = keyFile
return nil
}
}
// WithCAFile specifies the path to the CA file to use for TLS.
func WithCAFile(caFile string) RegistryLoginOpt {
return func(r *RegistryLogin) error {
r.caFile = caFile
return nil
}
} }
// NewRegistryLogin creates a new RegistryLogin object with the given configuration. // NewRegistryLogin creates a new RegistryLogin object with the given configuration.
@ -35,9 +73,16 @@ func NewRegistryLogin(cfg *Configuration) *RegistryLogin {
} }
// Run executes the registry login operation // Run executes the registry login operation
func (a *RegistryLogin) Run(out io.Writer, hostname string, username string, password string, insecure bool) error { func (a *RegistryLogin) Run(out io.Writer, hostname string, username string, password string, opts ...RegistryLoginOpt) error {
for _, opt := range opts {
if err := opt(a); err != nil {
return err
}
}
return a.cfg.RegistryClient.Login( return a.cfg.RegistryClient.Login(
hostname, hostname,
registry.LoginOptBasicAuth(username, password), registry.LoginOptBasicAuth(username, password),
registry.LoginOptInsecure(insecure)) registry.LoginOptInsecure(a.insecure),
registry.LoginOptTLSClientConfig(a.certFile, a.keyFile, a.caFile))
} }

@ -28,6 +28,7 @@ import (
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/registry"
) )
// ShowOutputFormat is the format of the output of `helm show` // ShowOutputFormat is the format of the output of `helm show`
@ -82,6 +83,11 @@ func NewShowWithConfig(output ShowOutputFormat, cfg *Configuration) *Show {
return sh return sh
} }
// SetRegistryClient sets the registry client to use when pulling a chart from a registry.
func (s *Show) SetRegistryClient(client *registry.Client) {
s.ChartPathOptions.registryClient = client
}
// Run executes 'helm show' against the given release. // Run executes 'helm show' against the given release.
func (s *Show) Run(chartpath string) (string, error) { func (s *Show) Run(chartpath string) (string, error) {
if s.chart == nil { if s.chart == nil {

@ -32,6 +32,7 @@ import (
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/kube" "helm.sh/helm/v3/pkg/kube"
"helm.sh/helm/v3/pkg/postrender" "helm.sh/helm/v3/pkg/postrender"
"helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/releaseutil" "helm.sh/helm/v3/pkg/releaseutil"
"helm.sh/helm/v3/pkg/storage/driver" "helm.sh/helm/v3/pkg/storage/driver"
@ -123,6 +124,11 @@ func NewUpgrade(cfg *Configuration) *Upgrade {
return up return up
} }
// SetRegistryClient sets the registry client to use when fetching charts.
func (u *Upgrade) SetRegistryClient(client *registry.Client) {
u.ChartPathOptions.registryClient = client
}
// Run executes the upgrade on the given release. // Run executes the upgrade on the given release.
func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, error) { func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, error) {
ctx := context.Background() ctx := context.Background()

@ -19,7 +19,6 @@ package loader
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -102,7 +101,7 @@ func LoadDir(dir string) (*chart.Chart, error) {
return fmt.Errorf("cannot load irregular file %s as it has file mode type bits set", name) return fmt.Errorf("cannot load irregular file %s as it has file mode type bits set", name)
} }
data, err := ioutil.ReadFile(name) data, err := os.ReadFile(name)
if err != nil { if err != nil {
return errors.Wrapf(err, "error reading %s", n) return errors.Wrapf(err, "error reading %s", n)
} }

@ -21,7 +21,6 @@ import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"io" "io"
"io/ioutil"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
@ -90,13 +89,13 @@ func TestLoadDirWithSymlink(t *testing.T) {
func TestBomTestData(t *testing.T) { func TestBomTestData(t *testing.T) {
testFiles := []string{"frobnitz_with_bom/.helmignore", "frobnitz_with_bom/templates/template.tpl", "frobnitz_with_bom/Chart.yaml"} testFiles := []string{"frobnitz_with_bom/.helmignore", "frobnitz_with_bom/templates/template.tpl", "frobnitz_with_bom/Chart.yaml"}
for _, file := range testFiles { for _, file := range testFiles {
data, err := ioutil.ReadFile("testdata/" + file) data, err := os.ReadFile("testdata/" + file)
if err != nil || !bytes.HasPrefix(data, utf8bom) { if err != nil || !bytes.HasPrefix(data, utf8bom) {
t.Errorf("Test file has no BOM or is invalid: testdata/%s", file) t.Errorf("Test file has no BOM or is invalid: testdata/%s", file)
} }
} }
archive, err := ioutil.ReadFile("testdata/frobnitz_with_bom.tgz") archive, err := os.ReadFile("testdata/frobnitz_with_bom.tgz")
if err != nil { if err != nil {
t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err)
} }

@ -17,7 +17,6 @@ limitations under the License.
package chartutil package chartutil
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -29,7 +28,7 @@ import (
// LoadChartfile loads a Chart.yaml file into a *chart.Metadata. // LoadChartfile loads a Chart.yaml file into a *chart.Metadata.
func LoadChartfile(filename string) (*chart.Metadata, error) { func LoadChartfile(filename string) (*chart.Metadata, error) {
b, err := ioutil.ReadFile(filename) b, err := os.ReadFile(filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -55,7 +54,7 @@ func SaveChartfile(filename string, cf *chart.Metadata) error {
if err != nil { if err != nil {
return err return err
} }
return ioutil.WriteFile(filename, out, 0644) return os.WriteFile(filename, out, 0644)
} }
// IsChartDir validate a chart directory. // IsChartDir validate a chart directory.
@ -73,7 +72,7 @@ func IsChartDir(dirName string) (bool, error) {
return false, errors.Errorf("no %s exists in directory %q", ChartfileName, dirName) return false, errors.Errorf("no %s exists in directory %q", ChartfileName, dirName)
} }
chartYamlContent, err := ioutil.ReadFile(chartYaml) chartYamlContent, err := os.ReadFile(chartYaml)
if err != nil { if err != nil {
return false, errors.Errorf("cannot read %s in directory %q", ChartfileName, dirName) return false, errors.Errorf("cannot read %s in directory %q", ChartfileName, dirName)
} }

@ -35,11 +35,11 @@ func TestLoadChartfile(t *testing.T) {
func verifyChartfile(t *testing.T, f *chart.Metadata, name string) { func verifyChartfile(t *testing.T, f *chart.Metadata, name string) {
if f == nil { if f == nil { //nolint:staticcheck
t.Fatal("Failed verifyChartfile because f is nil") t.Fatal("Failed verifyChartfile because f is nil")
} }
if f.APIVersion != chart.APIVersionV1 { if f.APIVersion != chart.APIVersionV1 { //nolint:staticcheck
t.Errorf("Expected API Version %q, got %q", chart.APIVersionV1, f.APIVersion) t.Errorf("Expected API Version %q, got %q", chart.APIVersionV1, f.APIVersion)
} }

@ -19,7 +19,6 @@ package chartutil
import ( import (
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@ -673,7 +672,7 @@ func writeFile(name string, content []byte) error {
if err := os.MkdirAll(filepath.Dir(name), 0755); err != nil { if err := os.MkdirAll(filepath.Dir(name), 0755); err != nil {
return err return err
} }
return ioutil.WriteFile(name, content, 0644) return os.WriteFile(name, content, 0644)
} }
func validateChartName(name string) error { func validateChartName(name string) error {

@ -18,7 +18,6 @@ package chartutil
import ( import (
"bytes" "bytes"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
@ -100,7 +99,7 @@ func TestCreateFrom(t *testing.T) {
} }
// Check each file to make sure <CHARTNAME> has been replaced // Check each file to make sure <CHARTNAME> has been replaced
b, err := ioutil.ReadFile(filepath.Join(dir, f)) b, err := os.ReadFile(filepath.Join(dir, f))
if err != nil { if err != nil {
t.Errorf("Unable to read file %s: %s", f, err) t.Errorf("Unable to read file %s: %s", f, err)
} }
@ -131,7 +130,7 @@ func TestCreate_Overwrite(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
data, err := ioutil.ReadFile(tplname) data, err := os.ReadFile(tplname)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
/*Package chartutil contains tools for working with charts. /*
Package chartutil contains tools for working with charts.
Charts are described in the chart package (pkg/chart). Charts are described in the chart package (pkg/chart).
This package provides utilities for serializing and deserializing charts. This package provides utilities for serializing and deserializing charts.

@ -18,7 +18,6 @@ package chartutil
import ( import (
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -72,7 +71,7 @@ func Expand(dir string, r io.Reader) error {
return err return err
} }
if err := ioutil.WriteFile(outpath, file.Data, 0644); err != nil { if err := os.WriteFile(outpath, file.Data, 0644); err != nil {
return err return err
} }
} }

@ -17,7 +17,7 @@ limitations under the License.
package chartutil package chartutil
import ( import (
"io/ioutil" "os"
"testing" "testing"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
@ -28,7 +28,7 @@ func TestValidateAgainstSingleSchema(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Error reading YAML file: %s", err) t.Fatalf("Error reading YAML file: %s", err)
} }
schema, err := ioutil.ReadFile("./testdata/test-values.schema.json") schema, err := os.ReadFile("./testdata/test-values.schema.json")
if err != nil { if err != nil {
t.Fatalf("Error reading YAML file: %s", err) t.Fatalf("Error reading YAML file: %s", err)
} }
@ -43,7 +43,7 @@ func TestValidateAgainstInvalidSingleSchema(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Error reading YAML file: %s", err) t.Fatalf("Error reading YAML file: %s", err)
} }
schema, err := ioutil.ReadFile("./testdata/test-values-invalid.schema.json") schema, err := os.ReadFile("./testdata/test-values-invalid.schema.json")
if err != nil { if err != nil {
t.Fatalf("Error reading YAML file: %s", err) t.Fatalf("Error reading YAML file: %s", err)
} }
@ -67,7 +67,7 @@ func TestValidateAgainstSingleSchemaNegative(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Error reading YAML file: %s", err) t.Fatalf("Error reading YAML file: %s", err)
} }
schema, err := ioutil.ReadFile("./testdata/test-values.schema.json") schema, err := os.ReadFile("./testdata/test-values.schema.json")
if err != nil { if err != nil {
t.Fatalf("Error reading YAML file: %s", err) t.Fatalf("Error reading YAML file: %s", err)
} }

@ -19,7 +19,7 @@ package chartutil
import ( import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "os"
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -114,7 +114,7 @@ func ReadValues(data []byte) (vals Values, err error) {
// ReadValuesFile will parse a YAML file into a map of values. // ReadValuesFile will parse a YAML file into a map of values.
func ReadValuesFile(filename string) (Values, error) { func ReadValuesFile(filename string) (Values, error) {
data, err := ioutil.ReadFile(filename) data, err := os.ReadFile(filename)
if err != nil { if err != nil {
return map[string]interface{}{}, err return map[string]interface{}{}, err
} }

@ -17,7 +17,7 @@ limitations under the License.
package values package values
import ( import (
"io/ioutil" "io"
"net/url" "net/url"
"os" "os"
"strings" "strings"
@ -119,7 +119,7 @@ func mergeMaps(a, b map[string]interface{}) map[string]interface{} {
// readFile load a file from stdin, the local directory, or a remote file with a url. // readFile load a file from stdin, the local directory, or a remote file with a url.
func readFile(filePath string, p getter.Providers) ([]byte, error) { func readFile(filePath string, p getter.Providers) ([]byte, error) {
if strings.TrimSpace(filePath) == "-" { if strings.TrimSpace(filePath) == "-" {
return ioutil.ReadAll(os.Stdin) return io.ReadAll(os.Stdin)
} }
u, err := url.Parse(filePath) u, err := url.Parse(filePath)
if err != nil { if err != nil {
@ -129,7 +129,7 @@ func readFile(filePath string, p getter.Providers) ([]byte, error) {
// FIXME: maybe someone handle other protocols like ftp. // FIXME: maybe someone handle other protocols like ftp.
g, err := p.ByScheme(u.Scheme) g, err := p.ByScheme(u.Scheme)
if err != nil { if err != nil {
return ioutil.ReadFile(filePath) return os.ReadFile(filePath)
} }
data, err := g.Get(filePath, getter.WithURL(filePath)) data, err := g.Get(filePath, getter.WithURL(filePath))
if err != nil { if err != nil {

@ -186,9 +186,9 @@ func (c *ChartDownloader) getOciURI(ref, version string, u *url.URL) (*url.URL,
// //
// - For fully qualified URLs, the version will be ignored (since URLs aren't versioned) // - For fully qualified URLs, the version will be ignored (since URLs aren't versioned)
// - For a chart reference // - For a chart reference
// * If version is non-empty, this will return the URL for that version // - If version is non-empty, this will return the URL for that version
// * If version is empty, this will return the URL for the latest version // - If version is empty, this will return the URL for the latest version
// * If no version can be found, an error is returned // - If no version can be found, an error is returned
func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, error) { func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, error) {
u, err := url.Parse(ref) u, err := url.Parse(ref)
if err != nil { if err != nil {

@ -13,7 +13,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
/*Package downloader provides a library for downloading charts. /*
Package downloader provides a library for downloading charts.
This package contains various tools for downloading charts from repository This package contains various tools for downloading charts from repository
servers, and then storing them in Helm-specific directory structures. This servers, and then storing them in Helm-specific directory structures. This

@ -20,7 +20,6 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"net/url" "net/url"
"os" "os"
@ -845,7 +844,7 @@ func writeLock(chartpath string, lock *chart.Lock, legacyLockfile bool) error {
lockfileName = "requirements.lock" lockfileName = "requirements.lock"
} }
dest := filepath.Join(chartpath, lockfileName) dest := filepath.Join(chartpath, lockfileName)
return ioutil.WriteFile(dest, data, 0644) return os.WriteFile(dest, data, 0644)
} }
// archive a dep chart from local directory and save it into destPath // archive a dep chart from local directory and save it into destPath

@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
/*Package engine implements the Go text template engine as needed for Helm. /*
Package engine implements the Go text template engine as needed for Helm.
When Helm renders templates it does so with additional functions and different When Helm renders templates it does so with additional functions and different
modes (e.g., strict, lint mode). This package handles the helm specific modes (e.g., strict, lint mode). This package handles the helm specific

@ -100,6 +100,7 @@ func (f files) Glob(pattern string) files {
// 'indent' template function. // 'indent' template function.
// //
// data: // data:
//
// {{ .Files.Glob("config/**").AsConfig() | indent 4 }} // {{ .Files.Glob("config/**").AsConfig() | indent 4 }}
func (f files) AsConfig() string { func (f files) AsConfig() string {
if f == nil { if f == nil {
@ -129,6 +130,7 @@ func (f files) AsConfig() string {
// 'indent' template function. // 'indent' template function.
// //
// data: // data:
//
// {{ .Files.Glob("secrets/*").AsSecrets() }} // {{ .Files.Glob("secrets/*").AsSecrets() }}
func (f files) AsSecrets() string { func (f files) AsSecrets() string {
if f == nil { if f == nil {

@ -40,7 +40,6 @@ import (
// //
// These are late-bound in Engine.Render(). The // These are late-bound in Engine.Render(). The
// version included in the FuncMap is a placeholder. // version included in the FuncMap is a placeholder.
//
func funcMap() template.FuncMap { func funcMap() template.FuncMap {
f := sprig.TxtFuncMap() f := sprig.TxtFuncMap()
delete(f, "env") delete(f, "env")

@ -13,7 +13,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
/*Package gates provides a general tool for working with experimental feature gates. /*
Package gates provides a general tool for working with experimental feature gates.
This provides convenience methods where the user can determine if certain experimental features are enabled. This provides convenience methods where the user can determine if certain experimental features are enabled.
*/ */

@ -13,7 +13,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
/*Package getter provides a generalize tool for fetching data by scheme. /*
Package getter provides a generalize tool for fetching data by scheme.
This provides a method by which the plugin system can load arbitrary protocol This provides a method by which the plugin system can load arbitrary protocol
handlers based upon a URL scheme. handlers based upon a URL scheme.

@ -123,8 +123,8 @@ func (g *HTTPGetter) httpClient() (*http.Client, error) {
} }
}) })
if (g.opts.certFile != "" && g.opts.keyFile != "") || g.opts.caFile != "" { if (g.opts.certFile != "" && g.opts.keyFile != "") || g.opts.caFile != "" || g.opts.insecureSkipVerifyTLS {
tlsConf, err := tlsutil.NewClientTLS(g.opts.certFile, g.opts.keyFile, g.opts.caFile) tlsConf, err := tlsutil.NewClientTLS(g.opts.certFile, g.opts.keyFile, g.opts.caFile, g.opts.insecureSkipVerifyTLS)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "can't create TLS config for client") return nil, errors.Wrap(err, "can't create TLS config for client")
} }

@ -285,9 +285,10 @@ func TestDownload(t *testing.T) {
func TestDownloadTLS(t *testing.T) { func TestDownloadTLS(t *testing.T) {
cd := "../../testdata" cd := "../../testdata"
ca, pub, priv := filepath.Join(cd, "rootca.crt"), filepath.Join(cd, "crt.pem"), filepath.Join(cd, "key.pem") ca, pub, priv := filepath.Join(cd, "rootca.crt"), filepath.Join(cd, "crt.pem"), filepath.Join(cd, "key.pem")
insecureSkipTLSverify := false
tlsSrv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) tlsSrv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
tlsConf, err := tlsutil.NewClientTLS(pub, priv, ca) tlsConf, err := tlsutil.NewClientTLS(pub, priv, ca, insecureSkipTLSverify)
if err != nil { if err != nil {
t.Fatal(errors.Wrap(err, "can't create TLS config for client")) t.Fatal(errors.Wrap(err, "can't create TLS config for client"))
} }
@ -434,10 +435,10 @@ func verifyInsecureSkipVerify(t *testing.T, g *HTTPGetter, caseName string, expe
t.Fatal(err) t.Fatal(err)
} }
if returnVal == nil { if returnVal == nil { //nolint:staticcheck
t.Fatalf("Expected non nil value for http client") t.Fatalf("Expected non nil value for http client")
} }
transport := (returnVal.Transport).(*http.Transport) transport := (returnVal.Transport).(*http.Transport) //nolint:staticcheck
gotValue := false gotValue := false
if transport.TLSClientConfig != nil { if transport.TLSClientConfig != nil {
gotValue = transport.TLSClientConfig.InsecureSkipVerify gotValue = transport.TLSClientConfig.InsecureSkipVerify
@ -458,11 +459,11 @@ func TestDefaultHTTPTransportReuse(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if httpClient1 == nil { if httpClient1 == nil { //nolint:staticcheck
t.Fatalf("Expected non nil value for http client") t.Fatalf("Expected non nil value for http client")
} }
transport1 := (httpClient1.Transport).(*http.Transport) transport1 := (httpClient1.Transport).(*http.Transport) //nolint:staticcheck
httpClient2, err := g.httpClient() httpClient2, err := g.httpClient()
@ -470,11 +471,11 @@ func TestDefaultHTTPTransportReuse(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if httpClient2 == nil { if httpClient2 == nil { //nolint:staticcheck
t.Fatalf("Expected non nil value for http client") t.Fatalf("Expected non nil value for http client")
} }
transport2 := (httpClient2.Transport).(*http.Transport) transport2 := (httpClient2.Transport).(*http.Transport) //nolint:staticcheck
if transport1 != transport2 { if transport1 != transport2 {
t.Fatalf("Expected default transport to be reused") t.Fatalf("Expected default transport to be reused")
@ -492,11 +493,11 @@ func TestHTTPTransportOption(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if httpClient1 == nil { if httpClient1 == nil { //nolint:staticcheck
t.Fatalf("Expected non nil value for http client") t.Fatalf("Expected non nil value for http client")
} }
transport1 := (httpClient1.Transport).(*http.Transport) transport1 := (httpClient1.Transport).(*http.Transport) //nolint:staticcheck
if transport1 != transport { if transport1 != transport {
t.Fatalf("Expected transport option to be applied") t.Fatalf("Expected transport option to be applied")
@ -508,11 +509,11 @@ func TestHTTPTransportOption(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if httpClient2 == nil { if httpClient2 == nil { //nolint:staticcheck
t.Fatalf("Expected non nil value for http client") t.Fatalf("Expected non nil value for http client")
} }
transport2 := (httpClient2.Transport).(*http.Transport) transport2 := (httpClient2.Transport).(*http.Transport) //nolint:staticcheck
if transport1 != transport2 { if transport1 != transport2 {
t.Fatalf("Expected applied transport to be reused") t.Fatalf("Expected applied transport to be reused")

@ -18,14 +18,22 @@ package getter
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"net"
"net/http"
"strings" "strings"
"sync"
"time"
"helm.sh/helm/v3/internal/tlsutil"
"helm.sh/helm/v3/internal/urlutil"
"helm.sh/helm/v3/pkg/registry" "helm.sh/helm/v3/pkg/registry"
) )
// OCIGetter is the default HTTP(/S) backend handler // OCIGetter is the default HTTP(/S) backend handler
type OCIGetter struct { type OCIGetter struct {
opts options opts options
transport *http.Transport
once sync.Once
} }
// Get performs a Get from repo.Getter and returns the body. // Get performs a Get from repo.Getter and returns the body.
@ -38,6 +46,15 @@ func (g *OCIGetter) Get(href string, options ...Option) (*bytes.Buffer, error) {
func (g *OCIGetter) get(href string) (*bytes.Buffer, error) { func (g *OCIGetter) get(href string) (*bytes.Buffer, error) {
client := g.opts.registryClient client := g.opts.registryClient
// if the user has already provided a configured registry client, use it,
// this is particularly true when user has his own way of handling the client credentials.
if client == nil {
c, err := g.newRegistryClient()
if err != nil {
return nil, err
}
client = c
}
ref := strings.TrimPrefix(href, fmt.Sprintf("%s://", registry.OCIScheme)) ref := strings.TrimPrefix(href, fmt.Sprintf("%s://", registry.OCIScheme))
@ -63,22 +80,73 @@ func (g *OCIGetter) get(href string) (*bytes.Buffer, error) {
// NewOCIGetter constructs a valid http/https client as a Getter // NewOCIGetter constructs a valid http/https client as a Getter
func NewOCIGetter(ops ...Option) (Getter, error) { func NewOCIGetter(ops ...Option) (Getter, error) {
registryClient, err := registry.NewClient( var client OCIGetter
registry.ClientOptEnableCache(true),
for _, opt := range ops {
opt(&client.opts)
}
return &client, nil
}
func (g *OCIGetter) newRegistryClient() (*registry.Client, error) {
if g.opts.transport != nil {
client, err := registry.NewClient(
registry.ClientOptHTTPClient(&http.Client{
Transport: g.opts.transport,
Timeout: g.opts.timeout,
}),
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
return client, nil
}
client := OCIGetter{ g.once.Do(func() {
opts: options{ g.transport = &http.Transport{
registryClient: registryClient, // From https://github.com/google/go-containerregistry/blob/31786c6cbb82d6ec4fb8eb79cd9387905130534e/pkg/v1/remote/options.go#L87
}, DisableCompression: true,
DialContext: (&net.Dialer{
// By default we wrap the transport in retries, so reduce the
// default dial timeout to 5s to avoid 5x 30s of connection
// timeouts when doing the "ping" on certain http registries.
Timeout: 5 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
} }
})
for _, opt := range ops { if (g.opts.certFile != "" && g.opts.keyFile != "") || g.opts.caFile != "" || g.opts.insecureSkipVerifyTLS {
opt(&client.opts) tlsConf, err := tlsutil.NewClientTLS(g.opts.certFile, g.opts.keyFile, g.opts.caFile, g.opts.insecureSkipVerifyTLS)
if err != nil {
return nil, fmt.Errorf("can't create TLS config for client: %w", err)
} }
return &client, nil sni, err := urlutil.ExtractHostname(g.opts.url)
if err != nil {
return nil, err
}
tlsConf.ServerName = sni
g.transport.TLSClientConfig = tlsConf
}
client, err := registry.NewClient(
registry.ClientOptHTTPClient(&http.Client{
Transport: g.transport,
Timeout: g.opts.timeout,
}),
)
if err != nil {
return nil, err
}
return client, nil
} }

@ -16,15 +16,126 @@ limitations under the License.
package getter package getter
import ( import (
"net/http"
"path/filepath"
"testing" "testing"
"time"
"helm.sh/helm/v3/pkg/registry"
) )
func TestNewOCIGetter(t *testing.T) { func TestOCIGetter(t *testing.T) {
testfn := func(ops *options) { g, err := NewOCIGetter(WithURL("oci://example.com"))
if ops.registryClient == nil { if err != nil {
t.Fatalf("the OCIGetter's registryClient should not be null") t.Fatal(err)
}
if _, ok := g.(*OCIGetter); !ok {
t.Fatal("Expected NewOCIGetter to produce an *OCIGetter")
}
cd := "../../testdata"
join := filepath.Join
ca, pub, priv := join(cd, "rootca.crt"), join(cd, "crt.pem"), join(cd, "key.pem")
timeout := time.Second * 5
transport := &http.Transport{}
insecureSkipTLSverify := false
// Test with options
g, err = NewOCIGetter(
WithBasicAuth("I", "Am"),
WithTLSClientConfig(pub, priv, ca),
WithTimeout(timeout),
WithTransport(transport),
WithInsecureSkipVerifyTLS(insecureSkipTLSverify),
)
if err != nil {
t.Fatal(err)
}
og, ok := g.(*OCIGetter)
if !ok {
t.Fatal("expected NewOCIGetter to produce an *OCIGetter")
}
if og.opts.username != "I" {
t.Errorf("Expected NewOCIGetter to contain %q as the username, got %q", "I", og.opts.username)
}
if og.opts.password != "Am" {
t.Errorf("Expected NewOCIGetter to contain %q as the password, got %q", "Am", og.opts.password)
}
if og.opts.certFile != pub {
t.Errorf("Expected NewOCIGetter to contain %q as the public key file, got %q", pub, og.opts.certFile)
} }
if og.opts.keyFile != priv {
t.Errorf("Expected NewOCIGetter to contain %q as the private key file, got %q", priv, og.opts.keyFile)
}
if og.opts.caFile != ca {
t.Errorf("Expected NewOCIGetter to contain %q as the CA file, got %q", ca, og.opts.caFile)
} }
NewOCIGetter(testfn) if og.opts.timeout != timeout {
t.Errorf("Expected NewOCIGetter to contain %s as Timeout flag, got %s", timeout, og.opts.timeout)
}
if og.opts.transport != transport {
t.Errorf("Expected NewOCIGetter to contain %p as Transport, got %p", transport, og.opts.transport)
}
// Test if setting registryClient is being passed to the ops
registryClient, err := registry.NewClient()
if err != nil {
t.Fatal(err)
}
g, err = NewOCIGetter(
WithRegistryClient(registryClient),
)
if err != nil {
t.Fatal(err)
}
og, ok = g.(*OCIGetter)
if !ok {
t.Fatal("expected NewOCIGetter to produce an *OCIGetter")
}
if og.opts.registryClient != registryClient {
t.Errorf("Expected NewOCIGetter to contain %p as RegistryClient, got %p", registryClient, og.opts.registryClient)
}
}
func TestOCIHTTPTransportReuse(t *testing.T) {
g := OCIGetter{}
_, err := g.newRegistryClient()
if err != nil {
t.Fatal(err)
}
if g.transport == nil {
t.Fatalf("Expected non nil value for transport")
}
transport1 := g.transport
_, err = g.newRegistryClient()
if err != nil {
t.Fatal(err)
}
if g.transport == nil {
t.Fatalf("Expected non nil value for transport")
}
transport2 := g.transport
if transport1 != transport2 {
t.Fatalf("Expected default transport to be reused")
}
} }

@ -19,7 +19,6 @@ package kube
import ( import (
"bytes" "bytes"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"strings" "strings"
"testing" "testing"
@ -37,7 +36,7 @@ var unstructuredSerializer = resource.UnstructuredPlusDefaultContentConfig().Neg
var codec = scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) var codec = scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
func objBody(obj runtime.Object) io.ReadCloser { func objBody(obj runtime.Object) io.ReadCloser {
return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj)))) return io.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj))))
} }
func newPod(name string) v1.Pod { func newPod(name string) v1.Pod {
@ -87,7 +86,7 @@ func notFoundBody() *metav1.Status {
func newResponse(code int, obj runtime.Object) (*http.Response, error) { func newResponse(code int, obj runtime.Object) (*http.Response, error) {
header := http.Header{} header := http.Header{}
header.Set("Content-Type", runtime.ContentTypeJSON) header.Set("Content-Type", runtime.ContentTypeJSON)
body := ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj)))) body := io.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj))))
return &http.Response{StatusCode: code, Header: header, Body: body}, nil return &http.Response{StatusCode: code, Header: header, Body: body}, nil
} }
@ -123,7 +122,7 @@ func TestUpdate(t *testing.T) {
case p == "/namespaces/default/pods/otter" && m == "GET": case p == "/namespaces/default/pods/otter" && m == "GET":
return newResponse(200, &listA.Items[1]) return newResponse(200, &listA.Items[1])
case p == "/namespaces/default/pods/otter" && m == "PATCH": case p == "/namespaces/default/pods/otter" && m == "PATCH":
data, err := ioutil.ReadAll(req.Body) data, err := io.ReadAll(req.Body)
if err != nil { if err != nil {
t.Fatalf("could not dump request: %s", err) t.Fatalf("could not dump request: %s", err)
} }
@ -136,7 +135,7 @@ func TestUpdate(t *testing.T) {
case p == "/namespaces/default/pods/dolphin" && m == "GET": case p == "/namespaces/default/pods/dolphin" && m == "GET":
return newResponse(404, notFoundBody()) return newResponse(404, notFoundBody())
case p == "/namespaces/default/pods/starfish" && m == "PATCH": case p == "/namespaces/default/pods/starfish" && m == "PATCH":
data, err := ioutil.ReadAll(req.Body) data, err := io.ReadAll(req.Body)
if err != nil { if err != nil {
t.Fatalf("could not dump request: %s", err) t.Fatalf("could not dump request: %s", err)
} }

@ -393,8 +393,10 @@ func (c *ReadyChecker) statefulSetReady(sts *appsv1.StatefulSet) bool {
c.log("StatefulSet is not ready: %s/%s. %d out of %d expected pods are ready", sts.Namespace, sts.Name, sts.Status.ReadyReplicas, replicas) c.log("StatefulSet is not ready: %s/%s. %d out of %d expected pods are ready", sts.Namespace, sts.Name, sts.Status.ReadyReplicas, replicas)
return false return false
} }
// This check only makes sense when all partitions are being upgraded otherwise during a
if sts.Status.CurrentRevision != sts.Status.UpdateRevision { // partioned rolling upgrade, this condition will never evaluate to true, leading to
// error.
if partition == 0 && sts.Status.CurrentRevision != sts.Status.UpdateRevision {
c.log("StatefulSet is not ready: %s/%s. currentRevision %s does not yet match updateRevision %s", sts.Namespace, sts.Name, sts.Status.CurrentRevision, sts.Status.UpdateRevision) c.log("StatefulSet is not ready: %s/%s. currentRevision %s does not yet match updateRevision %s", sts.Namespace, sts.Name, sts.Status.CurrentRevision, sts.Status.UpdateRevision)
return false return false
} }

@ -189,6 +189,13 @@ func Test_ReadyChecker_statefulSetReady(t *testing.T) {
}, },
want: false, want: false,
}, },
{
name: "statefulset is ready when current revision for current replicas does not match update revision for updated replicas when using partition !=0",
args: args{
sts: newStatefulSetWithUpdateRevision("foo", 3, 2, 3, 3, "foo-bbbbbbb"),
},
want: true,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

@ -22,5 +22,6 @@ const ResourcePolicyAnno = "helm.sh/resource-policy"
// KeepPolicy is the resource policy type for keep // KeepPolicy is the resource policy type for keep
// //
// This resource policy type allows resources to skip being deleted // This resource policy type allows resources to skip being deleted
//
// during an uninstallRelease action. // during an uninstallRelease action.
const KeepPolicy = "keep" const KeepPolicy = "keep"

@ -18,7 +18,6 @@ package rules // import "helm.sh/helm/v3/pkg/lint/rules"
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -200,7 +199,7 @@ func validateChartType(cf *chart.Metadata) error {
// in a generic form of a map[string]interface{}, so that the type // in a generic form of a map[string]interface{}, so that the type
// of the values can be checked // of the values can be checked
func loadChartFileForTypeCheck(filename string) (map[string]interface{}, error) { func loadChartFileForTypeCheck(filename string) (map[string]interface{}, error) {
b, err := ioutil.ReadFile(filename) b, err := os.ReadFile(filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -17,7 +17,6 @@ limitations under the License.
package rules package rules
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -76,7 +75,7 @@ func validateValuesFile(valuesPath string, overrides map[string]interface{}) err
ext := filepath.Ext(valuesPath) ext := filepath.Ext(valuesPath)
schemaPath := valuesPath[:len(valuesPath)-len(ext)] + ".schema.json" schemaPath := valuesPath[:len(valuesPath)-len(ext)] + ".schema.json"
schema, err := ioutil.ReadFile(schemaPath) schema, err := os.ReadFile(schemaPath)
if len(schema) == 0 { if len(schema) == 0 {
return nil return nil
} }

@ -17,7 +17,6 @@ limitations under the License.
package rules package rules
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
@ -168,7 +167,7 @@ func TestValidateValuesFile(t *testing.T) {
func createTestingSchema(t *testing.T, dir string) string { func createTestingSchema(t *testing.T, dir string) string {
t.Helper() t.Helper()
schemafile := filepath.Join(dir, "values.schema.json") schemafile := filepath.Join(dir, "values.schema.json")
if err := ioutil.WriteFile(schemafile, []byte(testSchema), 0700); err != nil { if err := os.WriteFile(schemafile, []byte(testSchema), 0700); err != nil {
t.Fatalf("Failed to write schema to tmpdir: %s", err) t.Fatalf("Failed to write schema to tmpdir: %s", err)
} }
return schemafile return schemafile

@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
/*Package support contains tools for linting charts. /*
Package support contains tools for linting charts.
Linting is the process of testing charts for errors or warnings regarding Linting is the process of testing charts for errors or warnings regarding
formatting, compilation, or standards compliance. formatting, compilation, or standards compliance.

@ -16,7 +16,6 @@ limitations under the License.
package installer // import "helm.sh/helm/v3/pkg/plugin/installer" package installer // import "helm.sh/helm/v3/pkg/plugin/installer"
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
@ -29,7 +28,7 @@ var _ Installer = new(LocalInstaller)
func TestLocalInstaller(t *testing.T) { func TestLocalInstaller(t *testing.T) {
// Make a temp dir // Make a temp dir
tdir := t.TempDir() tdir := t.TempDir()
if err := ioutil.WriteFile(filepath.Join(tdir, "plugin.yaml"), []byte{}, 0644); err != nil { if err := os.WriteFile(filepath.Join(tdir, "plugin.yaml"), []byte{}, 0644); err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -17,7 +17,6 @@ package plugin // import "helm.sh/helm/v3/pkg/plugin"
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@ -216,7 +215,7 @@ func detectDuplicates(plugs []*Plugin) error {
// LoadDir loads a plugin from the given directory. // LoadDir loads a plugin from the given directory.
func LoadDir(dirname string) (*Plugin, error) { func LoadDir(dirname string) (*Plugin, error) {
pluginfile := filepath.Join(dirname, PluginFileName) pluginfile := filepath.Join(dirname, PluginFileName)
data, err := ioutil.ReadFile(pluginfile) data, err := os.ReadFile(pluginfile)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "failed to read plugin at %q", pluginfile) return nil, errors.Wrapf(err, "failed to read plugin at %q", pluginfile)
} }

@ -18,7 +18,6 @@ package postrender
import ( import (
"bytes" "bytes"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
@ -167,7 +166,7 @@ func setupTestingScript(t *testing.T) (filepath string, cleanup func()) {
tempdir := ensure.TempDir(t) tempdir := ensure.TempDir(t)
f, err := ioutil.TempFile(tempdir, "post-render-test.sh") f, err := os.CreateTemp(tempdir, "post-render-test.sh")
if err != nil { if err != nil {
t.Fatalf("unable to create tempfile for testing: %s", err) t.Fatalf("unable to create tempfile for testing: %s", err)
} }

@ -13,7 +13,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
/*Package provenance provides tools for establishing the authenticity of a chart. /*
Package provenance provides tools for establishing the authenticity of a chart.
In Helm, provenance is established via several factors. The primary factor is the In Helm, provenance is established via several factors. The primary factor is the
cryptographic signature of a chart. Chart authors may sign charts, which in turn cryptographic signature of a chart. Chart authors may sign charts, which in turn

@ -20,7 +20,6 @@ import (
"crypto" "crypto"
"encoding/hex" "encoding/hex"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -42,9 +41,13 @@ var defaultPGPConfig = packet.Config{
// SumCollection represents a collection of file and image checksums. // SumCollection represents a collection of file and image checksums.
// //
// Files are of the form: // Files are of the form:
//
// FILENAME: "sha256:SUM" // FILENAME: "sha256:SUM"
//
// Images are of the form: // Images are of the form:
//
// "IMAGE:TAG": "sha256:SUM" // "IMAGE:TAG": "sha256:SUM"
//
// Docker optionally supports sha512, and if this is the case, the hash marker // Docker optionally supports sha512, and if this is the case, the hash marker
// will be 'sha512' instead of 'sha256'. // will be 'sha512' instead of 'sha256'.
type SumCollection struct { type SumCollection struct {
@ -293,7 +296,7 @@ func (s *Signatory) Verify(chartpath, sigpath string) (*Verification, error) {
} }
func (s *Signatory) decodeSignature(filename string) (*clearsign.Block, error) { func (s *Signatory) decodeSignature(filename string) (*clearsign.Block, error) {
data, err := ioutil.ReadFile(filename) data, err := os.ReadFile(filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -19,7 +19,6 @@ import (
"crypto" "crypto"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -277,7 +276,7 @@ func TestDecodeSignature(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
f, err := ioutil.TempFile("", "helm-test-sig-") f, err := os.CreateTemp("", "helm-test-sig-")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -334,7 +333,7 @@ func TestVerify(t *testing.T) {
// readSumFile reads a file containing a sum generated by the UNIX shasum tool. // readSumFile reads a file containing a sum generated by the UNIX shasum tool.
func readSumFile(sumfile string) (string, error) { func readSumFile(sumfile string) (string, error) {
data, err := ioutil.ReadFile(sumfile) data, err := os.ReadFile(sumfile)
if err != nil { if err != nil {
return "", err return "", err
} }

@ -17,13 +17,16 @@ package pusher
import ( import (
"fmt" "fmt"
"io/ioutil" "net"
"net/http"
"os" "os"
"path" "path"
"strings" "strings"
"time"
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/v3/internal/tlsutil"
"helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/registry" "helm.sh/helm/v3/pkg/registry"
) )
@ -59,8 +62,15 @@ func (pusher *OCIPusher) push(chartRef, href string) error {
} }
client := pusher.opts.registryClient client := pusher.opts.registryClient
if client == nil {
c, err := pusher.newRegistryClient()
if err != nil {
return err
}
client = c
}
chartBytes, err := ioutil.ReadFile(chartRef) chartBytes, err := os.ReadFile(chartRef)
if err != nil { if err != nil {
return err return err
} }
@ -68,7 +78,7 @@ func (pusher *OCIPusher) push(chartRef, href string) error {
var pushOpts []registry.PushOption var pushOpts []registry.PushOption
provRef := fmt.Sprintf("%s.prov", chartRef) provRef := fmt.Sprintf("%s.prov", chartRef)
if _, err := os.Stat(provRef); err == nil { if _, err := os.Stat(provRef); err == nil {
provBytes, err := ioutil.ReadFile(provRef) provBytes, err := os.ReadFile(provRef)
if err != nil { if err != nil {
return err return err
} }
@ -85,22 +95,55 @@ func (pusher *OCIPusher) push(chartRef, href string) error {
// NewOCIPusher constructs a valid OCI client as a Pusher // NewOCIPusher constructs a valid OCI client as a Pusher
func NewOCIPusher(ops ...Option) (Pusher, error) { func NewOCIPusher(ops ...Option) (Pusher, error) {
var client OCIPusher
for _, opt := range ops {
opt(&client.opts)
}
return &client, nil
}
func (pusher *OCIPusher) newRegistryClient() (*registry.Client, error) {
if (pusher.opts.certFile != "" && pusher.opts.keyFile != "") || pusher.opts.caFile != "" || pusher.opts.insecureSkipTLSverify {
tlsConf, err := tlsutil.NewClientTLS(pusher.opts.certFile, pusher.opts.keyFile, pusher.opts.caFile, pusher.opts.insecureSkipTLSverify)
if err != nil {
return nil, errors.Wrap(err, "can't create TLS config for client")
}
registryClient, err := registry.NewClient( registryClient, err := registry.NewClient(
registry.ClientOptHTTPClient(&http.Client{
// From https://github.com/google/go-containerregistry/blob/31786c6cbb82d6ec4fb8eb79cd9387905130534e/pkg/v1/remote/options.go#L87
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
// By default we wrap the transport in retries, so reduce the
// default dial timeout to 5s to avoid 5x 30s of connection
// timeouts when doing the "ping" on certain http registries.
Timeout: 5 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: tlsConf,
},
}),
registry.ClientOptEnableCache(true), registry.ClientOptEnableCache(true),
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
return registryClient, nil
client := OCIPusher{
opts: options{
registryClient: registryClient,
},
} }
for _, opt := range ops { registryClient, err := registry.NewClient(
opt(&client.opts) registry.ClientOptEnableCache(true),
)
if err != nil {
return nil, err
} }
return registryClient, nil
return &client, nil
} }

@ -16,21 +16,71 @@ limitations under the License.
package pusher package pusher
import ( import (
"path/filepath"
"testing" "testing"
"helm.sh/helm/v3/pkg/registry"
) )
func TestNewOCIPusher(t *testing.T) { func TestNewOCIPusher(t *testing.T) {
testfn := func(ops *options) { p, err := NewOCIPusher()
if ops.registryClient == nil { if err != nil {
t.Fatalf("the OCIPusher's registryClient should not be null") t.Fatal(err)
}
if _, ok := p.(*OCIPusher); !ok {
t.Fatal("Expected NewOCIPusher to produce an *OCIPusher")
}
cd := "../../testdata"
join := filepath.Join
ca, pub, priv := join(cd, "rootca.crt"), join(cd, "crt.pem"), join(cd, "key.pem")
insecureSkipTLSverify := false
// Test with options
p, err = NewOCIPusher(
WithTLSClientConfig(pub, priv, ca),
WithInsecureSkipTLSVerify(insecureSkipTLSverify),
)
if err != nil {
t.Fatal(err)
}
op, ok := p.(*OCIPusher)
if !ok {
t.Fatal("Expected NewOCIPusher to produce an *OCIPusher")
} }
if op.opts.certFile != pub {
t.Errorf("Expected NewOCIPusher to contain %q as the public key file, got %q", pub, op.opts.certFile)
}
if op.opts.keyFile != priv {
t.Errorf("Expected NewOCIPusher to contain %q as the private key file, got %q", priv, op.opts.keyFile)
}
if op.opts.caFile != ca {
t.Errorf("Expected NewOCIPusher to contain %q as the CA file, got %q", ca, op.opts.caFile)
} }
p, err := NewOCIPusher(testfn) // Test if setting registryClient is being passed to the ops
if p == nil { registryClient, err := registry.NewClient()
t.Error("NewOCIPusher returned nil") if err != nil {
t.Fatal(err)
} }
p, err = NewOCIPusher(
WithRegistryClient(registryClient),
)
if err != nil { if err != nil {
t.Error(err) t.Fatal(err)
}
op, ok = p.(*OCIPusher)
if !ok {
t.Fatal("expected NewOCIPusher to produce an *OCIPusher")
}
if op.opts.registryClient != registryClient {
t.Errorf("Expected NewOCIPusher to contain %p as RegistryClient, got %p", registryClient, op.opts.registryClient)
} }
} }

@ -28,6 +28,10 @@ import (
// Pushers may or may not ignore these parameters as they are passed in. // Pushers may or may not ignore these parameters as they are passed in.
type options struct { type options struct {
registryClient *registry.Client registryClient *registry.Client
certFile string
keyFile string
caFile string
insecureSkipTLSverify bool
} }
// Option allows specifying various settings configurable by the user for overriding the defaults // Option allows specifying various settings configurable by the user for overriding the defaults
@ -41,6 +45,22 @@ func WithRegistryClient(client *registry.Client) Option {
} }
} }
// WithTLSClientConfig sets the client auth with the provided credentials.
func WithTLSClientConfig(certFile, keyFile, caFile string) Option {
return func(opts *options) {
opts.certFile = certFile
opts.keyFile = keyFile
opts.caFile = caFile
}
}
// WithInsecureSkipTLSVerify determines if a TLS Certificate will be checked
func WithInsecureSkipTLSVerify(insecureSkipTLSVerify bool) Option {
return func(opts *options) {
opts.insecureSkipTLSverify = insecureSkipTLSVerify
}
}
// Pusher is an interface to support upload to the specified URL. // Pusher is an interface to support upload to the specified URL.
type Pusher interface { type Pusher interface {
// Push file content by url string // Push file content by url string

@ -21,7 +21,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"sort" "sort"
"strings" "strings"
@ -61,6 +60,7 @@ type (
authorizer auth.Client authorizer auth.Client
registryAuthorizer *registryauth.Client registryAuthorizer *registryauth.Client
resolver remotes.Resolver resolver remotes.Resolver
httpClient *http.Client
} }
// ClientOption allows specifying various settings configurable by the user for overriding the defaults // ClientOption allows specifying various settings configurable by the user for overriding the defaults
@ -71,7 +71,7 @@ type (
// NewClient returns a new registry client with config // NewClient returns a new registry client with config
func NewClient(options ...ClientOption) (*Client, error) { func NewClient(options ...ClientOption) (*Client, error) {
client := &Client{ client := &Client{
out: ioutil.Discard, out: io.Discard,
} }
for _, option := range options { for _, option := range options {
option(client) option(client)
@ -90,6 +90,9 @@ func NewClient(options ...ClientOption) (*Client, error) {
headers := http.Header{} headers := http.Header{}
headers.Set("User-Agent", version.GetUserAgent()) headers.Set("User-Agent", version.GetUserAgent())
opts := []auth.ResolverOption{auth.WithResolverHeaders(headers)} opts := []auth.ResolverOption{auth.WithResolverHeaders(headers)}
if client.httpClient != nil {
opts = append(opts, auth.WithResolverClient(client.httpClient))
}
resolver, err := client.authorizer.ResolverWithOpts(opts...) resolver, err := client.authorizer.ResolverWithOpts(opts...)
if err != nil { if err != nil {
return nil, err return nil, err
@ -104,6 +107,7 @@ func NewClient(options ...ClientOption) (*Client, error) {
} }
if client.registryAuthorizer == nil { if client.registryAuthorizer == nil {
client.registryAuthorizer = &registryauth.Client{ client.registryAuthorizer = &registryauth.Client{
Client: client.httpClient,
Header: http.Header{ Header: http.Header{
"User-Agent": {version.GetUserAgent()}, "User-Agent": {version.GetUserAgent()},
}, },
@ -166,6 +170,13 @@ func ClientOptCredentialsFile(credentialsFile string) ClientOption {
} }
} }
// ClientOptHTTPClient returns a function that sets the httpClient setting on a client options set
func ClientOptHTTPClient(httpClient *http.Client) ClientOption {
return func(client *Client) {
client.httpClient = httpClient
}
}
type ( type (
// LoginOption allows specifying various settings on login // LoginOption allows specifying various settings on login
LoginOption func(*loginOperation) LoginOption func(*loginOperation)
@ -174,6 +185,9 @@ type (
username string username string
password string password string
insecure bool insecure bool
certFile string
keyFile string
caFile string
} }
) )
@ -189,6 +203,7 @@ func (c *Client) Login(host string, options ...LoginOption) error {
auth.WithLoginUsername(operation.username), auth.WithLoginUsername(operation.username),
auth.WithLoginSecret(operation.password), auth.WithLoginSecret(operation.password),
auth.WithLoginUserAgent(version.GetUserAgent()), auth.WithLoginUserAgent(version.GetUserAgent()),
auth.WithLoginTLS(operation.certFile, operation.keyFile, operation.caFile),
} }
if operation.insecure { if operation.insecure {
authorizerLoginOpts = append(authorizerLoginOpts, auth.WithLoginInsecure()) authorizerLoginOpts = append(authorizerLoginOpts, auth.WithLoginInsecure())
@ -215,6 +230,15 @@ func LoginOptInsecure(insecure bool) LoginOption {
} }
} }
// LoginOptTLSClientConfig returns a function that sets the TLS settings on login.
func LoginOptTLSClientConfig(certFile, keyFile, caFile string) LoginOption {
return func(operation *loginOperation) {
operation.certFile = certFile
operation.keyFile = keyFile
operation.caFile = caFile
}
}
type ( type (
// LogoutOption allows specifying various settings on logout // LogoutOption allows specifying various settings on logout
LogoutOption func(*logoutOperation) LogoutOption func(*logoutOperation)

@ -17,90 +17,21 @@ limitations under the License.
package registry package registry
import ( import (
"bytes"
"context"
"fmt" "fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"os" "os"
"path/filepath"
"strings"
"testing" "testing"
"time"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/distribution/distribution/v3/configuration"
"github.com/distribution/distribution/v3/registry"
_ "github.com/distribution/distribution/v3/registry/auth/htpasswd"
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
"github.com/phayes/freeport"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"golang.org/x/crypto/bcrypt"
)
var (
testWorkspaceDir = "helm-registry-test"
testHtpasswdFileBasename = "authtest.htpasswd"
testUsername = "myuser"
testPassword = "mypass"
) )
type RegistryClientTestSuite struct { type RegistryClientTestSuite struct {
suite.Suite TestSuite
Out io.Writer
DockerRegistryHost string
CompromisedRegistryHost string
WorkspaceDir string
RegistryClient *Client
} }
func (suite *RegistryClientTestSuite) SetupSuite() { func (suite *RegistryClientTestSuite) SetupSuite() {
suite.WorkspaceDir = testWorkspaceDir
os.RemoveAll(suite.WorkspaceDir)
os.Mkdir(suite.WorkspaceDir, 0700)
var out bytes.Buffer
suite.Out = &out
credentialsFile := filepath.Join(suite.WorkspaceDir, CredentialsFileBasename)
// init test client // init test client
var err error dockerRegistry := setup(&suite.TestSuite, false, false)
suite.RegistryClient, err = NewClient(
ClientOptDebug(true),
ClientOptEnableCache(true),
ClientOptWriter(suite.Out),
ClientOptCredentialsFile(credentialsFile),
)
suite.Nil(err, "no error creating registry client")
// create htpasswd file (w BCrypt, which is required)
pwBytes, err := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost)
suite.Nil(err, "no error generating bcrypt password for test htpasswd file")
htpasswdPath := filepath.Join(suite.WorkspaceDir, testHtpasswdFileBasename)
err = ioutil.WriteFile(htpasswdPath, []byte(fmt.Sprintf("%s:%s\n", testUsername, string(pwBytes))), 0644)
suite.Nil(err, "no error creating test htpasswd file")
// Registry config
config := &configuration.Configuration{}
port, err := freeport.GetFreePort()
suite.Nil(err, "no error finding free port for test registry")
suite.DockerRegistryHost = fmt.Sprintf("localhost:%d", port)
config.HTTP.Addr = fmt.Sprintf("127.0.0.1:%d", port)
config.HTTP.DrainTimeout = time.Duration(10) * time.Second
config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}}
config.Auth = configuration.Auth{
"htpasswd": configuration.Parameters{
"realm": "localhost",
"path": htpasswdPath,
},
}
dockerRegistry, err := registry.NewRegistry(context.Background(), config)
suite.Nil(err, "no error creating test registry")
suite.CompromisedRegistryHost = initCompromisedRegistryTestServer()
// Start Docker registry // Start Docker registry
go dockerRegistry.ListenAndServe() go dockerRegistry.ListenAndServe()
@ -133,182 +64,15 @@ func (suite *RegistryClientTestSuite) Test_0_Login() {
} }
func (suite *RegistryClientTestSuite) Test_1_Push() { func (suite *RegistryClientTestSuite) Test_1_Push() {
// Bad bytes testPush(&suite.TestSuite)
ref := fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost)
_, err := suite.RegistryClient.Push([]byte("hello"), ref)
suite.NotNil(err, "error pushing non-chart bytes")
// Load a test chart
chartData, err := ioutil.ReadFile("../repo/repotest/testdata/examplechart-0.1.0.tgz")
suite.Nil(err, "no error loading test chart")
meta, err := extractChartMeta(chartData)
suite.Nil(err, "no error extracting chart meta")
// non-strict ref (chart name)
ref = fmt.Sprintf("%s/testrepo/boop:%s", suite.DockerRegistryHost, meta.Version)
_, err = suite.RegistryClient.Push(chartData, ref)
suite.NotNil(err, "error pushing non-strict ref (bad basename)")
// non-strict ref (chart name), with strict mode disabled
_, err = suite.RegistryClient.Push(chartData, ref, PushOptStrictMode(false))
suite.Nil(err, "no error pushing non-strict ref (bad basename), with strict mode disabled")
// non-strict ref (chart version)
ref = fmt.Sprintf("%s/testrepo/%s:latest", suite.DockerRegistryHost, meta.Name)
_, err = suite.RegistryClient.Push(chartData, ref)
suite.NotNil(err, "error pushing non-strict ref (bad tag)")
// non-strict ref (chart version), with strict mode disabled
_, err = suite.RegistryClient.Push(chartData, ref, PushOptStrictMode(false))
suite.Nil(err, "no error pushing non-strict ref (bad tag), with strict mode disabled")
// basic push, good ref
chartData, err = ioutil.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz")
suite.Nil(err, "no error loading test chart")
meta, err = extractChartMeta(chartData)
suite.Nil(err, "no error extracting chart meta")
ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version)
_, err = suite.RegistryClient.Push(chartData, ref)
suite.Nil(err, "no error pushing good ref")
_, err = suite.RegistryClient.Pull(ref)
suite.Nil(err, "no error pulling a simple chart")
// Load another test chart
chartData, err = ioutil.ReadFile("../downloader/testdata/signtest-0.1.0.tgz")
suite.Nil(err, "no error loading test chart")
meta, err = extractChartMeta(chartData)
suite.Nil(err, "no error extracting chart meta")
// Load prov file
provData, err := ioutil.ReadFile("../downloader/testdata/signtest-0.1.0.tgz.prov")
suite.Nil(err, "no error loading test prov")
// push with prov
ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version)
result, err := suite.RegistryClient.Push(chartData, ref, PushOptProvData(provData))
suite.Nil(err, "no error pushing good ref with prov")
_, err = suite.RegistryClient.Pull(ref)
suite.Nil(err, "no error pulling a simple chart")
// Validate the output
// Note: these digests/sizes etc may change if the test chart/prov files are modified,
// or if the format of the OCI manifest changes
suite.Equal(ref, result.Ref)
suite.Equal(meta.Name, result.Chart.Meta.Name)
suite.Equal(meta.Version, result.Chart.Meta.Version)
suite.Equal(int64(512), result.Manifest.Size)
suite.Equal(int64(99), result.Config.Size)
suite.Equal(int64(973), result.Chart.Size)
suite.Equal(int64(695), result.Prov.Size)
suite.Equal(
"sha256:af4c20a1df1431495e673c14ecfa3a2ba24839a7784349d6787cd67957392e83",
result.Manifest.Digest)
suite.Equal(
"sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580",
result.Config.Digest)
suite.Equal(
"sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55",
result.Chart.Digest)
suite.Equal(
"sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256",
result.Prov.Digest)
} }
func (suite *RegistryClientTestSuite) Test_2_Pull() { func (suite *RegistryClientTestSuite) Test_2_Pull() {
// bad/missing ref testPull(&suite.TestSuite)
ref := fmt.Sprintf("%s/testrepo/no-existy:1.2.3", suite.DockerRegistryHost)
_, err := suite.RegistryClient.Pull(ref)
suite.NotNil(err, "error on bad/missing ref")
// Load test chart (to build ref pushed in previous test)
chartData, err := ioutil.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz")
suite.Nil(err, "no error loading test chart")
meta, err := extractChartMeta(chartData)
suite.Nil(err, "no error extracting chart meta")
ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version)
// Simple pull, chart only
_, err = suite.RegistryClient.Pull(ref)
suite.Nil(err, "no error pulling a simple chart")
// Simple pull with prov (no prov uploaded)
_, err = suite.RegistryClient.Pull(ref, PullOptWithProv(true))
suite.NotNil(err, "error pulling a chart with prov when no prov exists")
// Simple pull with prov, ignoring missing prov
_, err = suite.RegistryClient.Pull(ref,
PullOptWithProv(true),
PullOptIgnoreMissingProv(true))
suite.Nil(err,
"no error pulling a chart with prov when no prov exists, ignoring missing")
// Load test chart (to build ref pushed in previous test)
chartData, err = ioutil.ReadFile("../downloader/testdata/signtest-0.1.0.tgz")
suite.Nil(err, "no error loading test chart")
meta, err = extractChartMeta(chartData)
suite.Nil(err, "no error extracting chart meta")
ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version)
// Load prov file
provData, err := ioutil.ReadFile("../downloader/testdata/signtest-0.1.0.tgz.prov")
suite.Nil(err, "no error loading test prov")
// no chart and no prov causes error
_, err = suite.RegistryClient.Pull(ref,
PullOptWithChart(false),
PullOptWithProv(false))
suite.NotNil(err, "error on both no chart and no prov")
// full pull with chart and prov
result, err := suite.RegistryClient.Pull(ref, PullOptWithProv(true))
suite.Nil(err, "no error pulling a chart with prov")
// Validate the output
// Note: these digests/sizes etc may change if the test chart/prov files are modified,
// or if the format of the OCI manifest changes
suite.Equal(ref, result.Ref)
suite.Equal(meta.Name, result.Chart.Meta.Name)
suite.Equal(meta.Version, result.Chart.Meta.Version)
suite.Equal(int64(512), result.Manifest.Size)
suite.Equal(int64(99), result.Config.Size)
suite.Equal(int64(973), result.Chart.Size)
suite.Equal(int64(695), result.Prov.Size)
suite.Equal(
"sha256:af4c20a1df1431495e673c14ecfa3a2ba24839a7784349d6787cd67957392e83",
result.Manifest.Digest)
suite.Equal(
"sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580",
result.Config.Digest)
suite.Equal(
"sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55",
result.Chart.Digest)
suite.Equal(
"sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256",
result.Prov.Digest)
suite.Equal("{\"schemaVersion\":2,\"config\":{\"mediaType\":\"application/vnd.cncf.helm.config.v1+json\",\"digest\":\"sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580\",\"size\":99},\"layers\":[{\"mediaType\":\"application/vnd.cncf.helm.chart.provenance.v1.prov\",\"digest\":\"sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256\",\"size\":695},{\"mediaType\":\"application/vnd.cncf.helm.chart.content.v1.tar+gzip\",\"digest\":\"sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55\",\"size\":973}]}",
string(result.Manifest.Data))
suite.Equal("{\"name\":\"signtest\",\"version\":\"0.1.0\",\"description\":\"A Helm chart for Kubernetes\",\"apiVersion\":\"v1\"}",
string(result.Config.Data))
suite.Equal(chartData, result.Chart.Data)
suite.Equal(provData, result.Prov.Data)
} }
func (suite *RegistryClientTestSuite) Test_3_Tags() { func (suite *RegistryClientTestSuite) Test_3_Tags() {
testTags(&suite.TestSuite)
// Load test chart (to build ref pushed in previous test)
chartData, err := ioutil.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz")
suite.Nil(err, "no error loading test chart")
meta, err := extractChartMeta(chartData)
suite.Nil(err, "no error extracting chart meta")
ref := fmt.Sprintf("%s/testrepo/%s", suite.DockerRegistryHost, meta.Name)
// Query for tags and validate length
tags, err := suite.RegistryClient.Tags(ref)
suite.Nil(err, "no error retrieving tags")
suite.Equal(1, len(tags))
} }
func (suite *RegistryClientTestSuite) Test_4_Logout() { func (suite *RegistryClientTestSuite) Test_4_Logout() {
@ -331,43 +95,3 @@ func (suite *RegistryClientTestSuite) Test_5_ManInTheMiddle() {
func TestRegistryClientTestSuite(t *testing.T) { func TestRegistryClientTestSuite(t *testing.T) {
suite.Run(t, new(RegistryClientTestSuite)) suite.Run(t, new(RegistryClientTestSuite))
} }
func initCompromisedRegistryTestServer() string {
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "manifests") {
w.Header().Set("Content-Type", "application/vnd.oci.image.manifest.v1+json")
w.WriteHeader(200)
// layers[0] is the blob []byte("a")
w.Write([]byte(
fmt.Sprintf(`{ "schemaVersion": 2, "config": {
"mediaType": "%s",
"digest": "sha256:a705ee2789ab50a5ba20930f246dbd5cc01ff9712825bb98f57ee8414377f133",
"size": 181
},
"layers": [
{
"mediaType": "%s",
"digest": "sha256:ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb",
"size": 1
}
]
}`, ConfigMediaType, ChartLayerMediaType)))
} else if r.URL.Path == "/v2/testrepo/supposedlysafechart/blobs/sha256:a705ee2789ab50a5ba20930f246dbd5cc01ff9712825bb98f57ee8414377f133" {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write([]byte("{\"name\":\"mychart\",\"version\":\"0.1.0\",\"description\":\"A Helm chart for Kubernetes\\n" +
"an 'application' or a 'library' chart.\",\"apiVersion\":\"v2\",\"appVersion\":\"1.16.0\",\"type\":" +
"\"application\"}"))
} else if r.URL.Path == "/v2/testrepo/supposedlysafechart/blobs/sha256:ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb" {
w.Header().Set("Content-Type", ChartLayerMediaType)
w.WriteHeader(200)
w.Write([]byte("b"))
} else {
w.WriteHeader(500)
}
}))
u, _ := url.Parse(s.URL)
return fmt.Sprintf("localhost:%s", u.Port())
}

@ -0,0 +1,82 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package registry
import (
"os"
"testing"
"github.com/stretchr/testify/suite"
)
type TLSRegistryClientTestSuite struct {
TestSuite
}
func (suite *TLSRegistryClientTestSuite) SetupSuite() {
// init test client
dockerRegistry := setup(&suite.TestSuite, true, false)
// Start Docker registry
go dockerRegistry.ListenAndServe()
}
func (suite *TLSRegistryClientTestSuite) TearDownSuite() {
teardown(&suite.TestSuite)
os.RemoveAll(suite.WorkspaceDir)
}
func (suite *TLSRegistryClientTestSuite) Test_0_Login() {
err := suite.RegistryClient.Login(suite.DockerRegistryHost,
LoginOptBasicAuth("badverybad", "ohsobad"),
LoginOptTLSClientConfig(tlsCert, tlsKey, tlsCA))
suite.NotNil(err, "error logging into registry with bad credentials")
err = suite.RegistryClient.Login(suite.DockerRegistryHost,
LoginOptBasicAuth(testUsername, testPassword),
LoginOptTLSClientConfig(tlsCert, tlsKey, tlsCA))
suite.Nil(err, "no error logging into registry with good credentials")
err = suite.RegistryClient.Login(suite.DockerRegistryHost,
LoginOptBasicAuth(testUsername, testPassword),
LoginOptTLSClientConfig(tlsCert, tlsKey, tlsCA))
suite.Nil(err, "no error logging into registry with good credentials, insecure mode")
}
func (suite *TLSRegistryClientTestSuite) Test_1_Push() {
testPush(&suite.TestSuite)
}
func (suite *TLSRegistryClientTestSuite) Test_2_Pull() {
testPull(&suite.TestSuite)
}
func (suite *TLSRegistryClientTestSuite) Test_3_Tags() {
testTags(&suite.TestSuite)
}
func (suite *TLSRegistryClientTestSuite) Test_4_Logout() {
err := suite.RegistryClient.Logout("this-host-aint-real:5000")
suite.NotNil(err, "error logging out of registry that has no entry")
err = suite.RegistryClient.Logout(suite.DockerRegistryHost)
suite.Nil(err, "no error logging out of registry")
}
func TestTLSRegistryClientTestSuite(t *testing.T) {
suite.Run(t, new(TLSRegistryClientTestSuite))
}

@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDhzCCAm+gAwIBAgIUdI/ees1mQ4N++1jpF5xI5fq6TSUwDQYJKoZIhvcNAQEL
BQAwUjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjENMAsG
A1UECgwEaGVsbTEaMBgGA1UEAwwRcmVnaXN0cnktdGVzdC5jb20wIBcNMjIwOTIw
MDgyMDQ2WhgPMzAyMjAxMjEwODIwNDZaMFIxCzAJBgNVBAYTAlVTMQswCQYDVQQI
DAJDQTELMAkGA1UEBwwCU0YxDTALBgNVBAoMBGhlbG0xGjAYBgNVBAMMEXJlZ2lz
dHJ5LXRlc3QuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0mxP
WVkpDo3PnXalJhy9rSYuK8OIxcO1kBroEnILYrNWn5zpKioaBXZEYcaU6crc5N4j
wQRC16wucyQAQh/d3ty7j5Wyy79CgH5AAKDbCacii4BgGUJ2xY6UXuKvwdsROAXN
wEtXT5f3yO8bVboYrZRxJ4UuTUFndtuz2b230JFs2FzTv4QdLaPHo/S4FTW5xRn5
Irhmcmkns+XY4AduscYtzydvIuuOS3CVmB8/sClo62F5DpBl68b+/WFwqLrkX5Sn
ZWKx/fJPIxln5SavPXHEEcI14ZGNUhsv+4+sABHzVjBPK8oKjoNo8QmxDWdeWPgR
sPj/H2oldE6KfgyoQQIDAQABo1MwUTAdBgNVHQ4EFgQUkkmPK6SIj4PY8YOw+Yer
hKCOS7owHwYDVR0jBBgwFoAUkkmPK6SIj4PY8YOw+YerhKCOS7owDwYDVR0TAQH/
BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEADSz9s8rcObLrUo8DpVRptWUxK3NH
hvD7bYGQ9eJO9B4ojKSBKJRchP0m5kpVLorMRZDRw17T2GouKQn3g+Wcy+8CygxW
1JDO/1iCZ8QX3vfwIfHTaKuY6eYcJyVmxL58bRI3qQNRZIU4s18tKFIazBluxS3g
5Wp8kOCBssttsM+lEgC/cj7skl9CBKhUFupHPzXzha+1upJUK51Egc7M7nsrnpaZ
2SY+PBEhSY5Wcuzb5m9tw7PJnkdRDS/dUOY6kSzJXgNMVV0GnN+Smucqmvrez0M5
vHFMiQjlRxViVLJDNOCJYIjWNygAOvhJyRU2cTodIhZ/jbYqpNGAPc5Eyg==
-----END CERTIFICATE-----

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDsTCCApmgAwIBAgIBATANBgkqhkiG9w0BAQsFADBSMQswCQYDVQQGEwJVUzEL
MAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMQ0wCwYDVQQKDARoZWxtMRowGAYDVQQD
DBFyZWdpc3RyeS10ZXN0LmNvbTAgFw0yMjA5MjAwODI4MzBaGA8yMTIyMDgyNzA4
MjgzMFowWTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjEN
MAsGA1UECgwEaGVsbTEhMB8GA1UEAwwYY2xpZW50LnJlZ2lzdHJ5LXRlc3QuY29t
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnvxfrJn8PeerlHJLnMVo
p1yOT/kvFAoNhObhtDUosDLjQBt+vICfjWoTNIabIiBRTwkVt5CdGvx1oKsbH3iT
VErL6N6MagIJdnOfBjxtlTL/TFtJ7U/VSUSxZwa+SV6HS4cmIntC/FV3MHjBlFJn
klSdDXa5YdYE2xuSPse+zlGRfmPTNmHsiNWphGC54U6WZ1UI0G22+L/yO8BuEkSq
47iCN6ZIw8ds+azl/woIEDJsVSgEapNsanBrJFnBUJBXh4lwpMB37U+6Ds1kUUuz
GXhVWz1pmRBt+vXWN802MqRg2RnCjTb2gWbmg7En4uFCTzx/GhRlJiV47O15n0g+
tQIDAQABo4GIMIGFMB8GA1UdIwQYMBaAFJJJjyukiI+D2PGDsPmHq4Sgjku6MAkG
A1UdEwQCMAAwCwYDVR0PBAQDAgTwMCsGA1UdEQQkMCKCCWxvY2FsaG9zdIIKMHg3
ZjAwMDAwMYIJMTI3LjAuMC4xMB0GA1UdDgQWBBT+cCGLyj5wOIMG7TVqPyxPQsBi
+DANBgkqhkiG9w0BAQsFAAOCAQEATIDXr3LmD1S+13lVG263rn21cDT3m4VycQCu
oGNDuxtFwd/Zn/XnZLk2r1msz6YXWUqErJ8C7Ea7fFdimoJR5V3m7LYrYRPeLYVn
aVqyNN4LD48Su3VO5sjTyFxXJJJ9C5HX8LU/Pw/517qzLOFrmsO/fXN/XE52erBE
+K6vX4lyxnZyPfl3A/X/33G2tsGtHFK1uBILpn29fpeC/Pgm3Nj8ZqQ8rtcLZbog
heqdKkHKWdL3i1deplwxT7xVnqsWszU6Znzm/C/VQSB4Isn4puQDKqVPwGobHgxY
1zZr5mueot8mX9Qmg8IcWOVZ2u7nz8lw6+wpabkyjjdTC6iizg==
-----END CERTIFICATE-----

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCe/F+smfw956uU
ckucxWinXI5P+S8UCg2E5uG0NSiwMuNAG368gJ+NahM0hpsiIFFPCRW3kJ0a/HWg
qxsfeJNUSsvo3oxqAgl2c58GPG2VMv9MW0ntT9VJRLFnBr5JXodLhyYie0L8VXcw
eMGUUmeSVJ0Ndrlh1gTbG5I+x77OUZF+Y9M2YeyI1amEYLnhTpZnVQjQbbb4v/I7
wG4SRKrjuII3pkjDx2z5rOX/CggQMmxVKARqk2xqcGskWcFQkFeHiXCkwHftT7oO
zWRRS7MZeFVbPWmZEG369dY3zTYypGDZGcKNNvaBZuaDsSfi4UJPPH8aFGUmJXjs
7XmfSD61AgMBAAECggEAKYp/5TWG9xXlezAyGZBrO++vL65IYtANoEBDkTainwds
4X9NqithhS3GPt89Abm4BRK2nfQnWLnGcmjC+YIj3M5+YSZlQf2uQ0kKsDJx354n
nufrdRp6/F36jJTye3E7oLx7dl8GrbAXKI8k5YByl4WMU8xFvA6TzjxyBf1jGb1E
8JBZpnqwSHgtH0zGPqgcIsqmQjiMJ+wHNZxdvtjPPC8exy/yLL9Hhj2UaqZSMMRi
afaAFXBLNvJ6Y/SUjRaL9liAyTQ0kJ+xR6TMDJ7ix0toGlylsK/3YesXEgAyui6c
UC3dmSC4UDJW+fGLrj/hVBLdpMRpgrWzwXnRyr0RMQKBgQDDnJqAtULhlo0W4E29
Oo7XYFEcilzxB3hxEQSmts53GeQZHo1gI4wthyMzAgY3uOCIUtB2lPkNLV+dU86A
Cy1WTRL2vbwdM1qHz2tls4LNa+k+XTMWX7aqfCzOydBpV3Yehmnzb4NvFn9+QHjp
5omwwOaG7dhJCVet3CUJctoeOwKBgQDQETAVd4xfwQ/cBbKgoQhrkHOr+gTWcKYP
WD86EFDbRVboYDevU/dAj5Vwm5763zRsBFyL6/ZVUr9Wa1HHy0paE5YfdewMrRje
LhHeTbrLJ4Q3I0ix3bawv/04B66hw+Yaom0bQV3gBrNk+Cn8VFAo6IKNy7A0pK3i
KQmwoO+XzwKBgC3EqInQ33M07JIbrVTHLMDL8m6BGTn0C4Q4/SOcxjYrwqj18xI5
fwTwB5ZZtOa4xSBgcBIuzQ7+PM7s2vYup073/aXpwuf6KgZ4y6IiHErAIvTKjbeA
cZb2Mu23XqInKqX9wTCKOPB3DSGXKDNiE3ldyRJs+BwuqWsuhSPu0YYdAoGADjd+
b5kRkGFisgf5opweNStTnAajWfusfRPsjg0bWUAtpgcdBu/XzyOAdIdNn5qsvEy3
/h+LX10eEcuXdO1hETKRaWjnTh5tupCvS99HyiXTFOlmSDD8EKuto6xytD7sdBlx
FxGqVmpey6FhTQp9x63LbeDjE1XFQ9TGArmcZWUCgYEAprSfhSemz9tP5tKKdYTc
LM5eWqK0aB1sN/hCZVx86VcNBxRbV+POEASTYO9AyVMjthGRe6UnCjwdXKTJ/ToX
KdtXINYeeK3hzANeCvtqg81qxi+8nmNLimtcjvFsB5g44LOFYyXqAD5FeQYTog1n
t/TLHYY+S8BbJ9cXfObXqyE=
-----END PRIVATE KEY-----

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDsTCCApmgAwIBAgIBATANBgkqhkiG9w0BAQsFADBSMQswCQYDVQQGEwJVUzEL
MAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMQ0wCwYDVQQKDARoZWxtMRowGAYDVQQD
DBFyZWdpc3RyeS10ZXN0LmNvbTAgFw0yMjA5MjAwODI3NDZaGA8yMTIyMDgyNzA4
Mjc0NlowWTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjEN
MAsGA1UECgwEaGVsbTEhMB8GA1UEAwwYc2VydmVyLnJlZ2lzdHJ5LXRlc3QuY29t
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxve7spJ44uC/f6BCUEKQ
PA9Sqc+ulTXyptZROLa90o7GK9P1WW8hcDRIYaIU3Rh+o6E0QYwBwvspoEAKYP0q
kp16pD1Ezf5VTikVElq20qvYOaAjvxFltIAmrxoCokkwEIsgEY6RYHZedimKWtdg
kG7R0aNnwgognoz6j4GD/Z/HejCY54jckQczDdaxWrcbBdQ0h/WNjLwHmlids4H9
ni4cas4An5TZ3cOA9ah+8PSRNYgSLFR34KuydLd8xx5E2fG8OuU5zCNaDQ4puYKP
u+D6GNCdwi+w+Ac/3MTAX8ORLrB/8BCIMwnYi7g7En4a47ck21VqhfE+CH10AR07
nQIDAQABo4GIMIGFMB8GA1UdIwQYMBaAFJJJjyukiI+D2PGDsPmHq4Sgjku6MAkG
A1UdEwQCMAAwCwYDVR0PBAQDAgTwMCsGA1UdEQQkMCKCCWxvY2FsaG9zdIIKMHg3
ZjAwMDAwMYIJMTI3LjAuMC4xMB0GA1UdDgQWBBRoIiJ5S3EJmcNUmjT+dxWO+14k
ADANBgkqhkiG9w0BAQsFAAOCAQEAb6UOBss8IA3uT76LIK9TSNSyn6BoYlTFGwgx
O2Cp4kqyKb370qAWV1QVVefQP1uftXpsdqhtwEL4jUptYO5yP4Udtg0QV0SsyMsg
jXgaeuC7589lcJpmTvPj/XlnAZE6vmTrVPG4c1wEC+qCTSHAu3EBRN8hHKZFmLON
254/6x2HlSTqwKzzJY5YEL8pP1kAIww40YMd5G5gFqCNdcg2FKB3ZWo9cFzCU3VK
HoeOUG286GuEN6AG/YT2DIFAZpP+SUgjY8mj1CxoIv9LMNyF1Tm8kzQDU0IA2dfW
1AY0edoHL2kLoUUKet/d7tayP9gnt0sOUrY2oZXrp+TvSHVTlw==
-----END CERTIFICATE-----

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDG97uyknji4L9/
oEJQQpA8D1Kpz66VNfKm1lE4tr3SjsYr0/VZbyFwNEhhohTdGH6joTRBjAHC+ymg
QApg/SqSnXqkPUTN/lVOKRUSWrbSq9g5oCO/EWW0gCavGgKiSTAQiyARjpFgdl52
KYpa12CQbtHRo2fCCiCejPqPgYP9n8d6MJjniNyRBzMN1rFatxsF1DSH9Y2MvAea
WJ2zgf2eLhxqzgCflNndw4D1qH7w9JE1iBIsVHfgq7J0t3zHHkTZ8bw65TnMI1oN
Dim5go+74PoY0J3CL7D4Bz/cxMBfw5EusH/wEIgzCdiLuDsSfhrjtyTbVWqF8T4I
fXQBHTudAgMBAAECggEAD13Tr7tzPaZ487znUjaJ2DGgwz+obpqvhmYX+MbYSzo+
oOTqVoFoNje7fVrcvKSnJzEMjaFoA2yNbvRzOMFkt9UUwzl+JmClqvcuSvAZnZSr
CuxMxnVsAvBAzJY4LNt1LFnqXKDDpo0Nx5d2uYRXz1/XsZaqrUhF86jUsx+gF4bM
LYe6SjXWtf1sumgE1gbil8NDLbqHPMvimQhLu1WgVxiarlye2NMyHxk6MTqwYOX3
iinf3cuRFYuFyD1IHorreVAdOH0zuYvqLFylBbRqEfeOozVytX73yKfRK4lPobc+
Q1n/mPzwyc9aVWKRo4WId0mA2rhP8sL7BvMFRwYnSwKBgQDdUqlel4/Fj2WfcsKa
SMjmqM66tFDxH27Vp55RoS/Fr+RZSVYda7cdbMJaGVswbZevwsCS46l2BJJdJXHt
UE1viKkKiIxGJzpH9Q1vyUEf+21eESnkr7HKoUrSpopwqOlc1dYPvn47aJukcGee
vwMkiaG5IUaR5MCfLA8xQ89UPwKBgQDmJGWtrwcUIdEvRI1wg8Unj0chAyz+/KIR
9jkVIyu4SUfThQp6GsCHsvc5TGN6yieGLIfrVb7qb8F2gDPdg8L/13zqAorpcK6E
AagYLDgKWV4O2oGT4AGQrcz/66BYAfeD868r442bhyEkD7zLqZSbHlPTpy8bPKuC
nen88JGJIwKBgD/OawHYVByywKt9XFk6jqDhHeh5v7QkScHS9zO1cp5dnUmYePk2
aq5TAp0THlUR419KmFZAyEQ8AS5Vc0jlk82J6qIcx8QZ3xWLsnn93Yao59jsvdUu
SeWPJpEgbl0YdV7MT1BurNnXyLdZqKX9j5xjCXrj+wJonpfFDgQ39nflAoGAd1bo
YuggA5CFqL0jmvS5h4oEmFnNO2xFnorPjuZuBWH6nPSgOjElJTjoeg3iiAnL9Qei
c6ZDGc5Zw9k3C+cHdyOG4tHutp534Hv7bo1/gd5Vp94m00eViDCX3R2SSBC9CO+U
Jm4ZQE0SImEGxZVqOgW/8kD/bGBJj7HTZBZbYYECgYEAoGwLnE2TiMLfXIKXsmII
h9+rZrPfFyDCM27+QIADpCv7Ae2cIGanqSbyPJrFWD4CRXBv+92L2LyG7yA9C498
uyMJ98DVp4SAaNWFha+JCz5TO6KCXOuwGrQTSUitqxQ2rMv2WpXnO2T8puvXW8dD
mxfiHuvNMNHfA9Bd4tsbbPE=
-----END PRIVATE KEY-----

@ -21,6 +21,7 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"net/http"
"strings" "strings"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
@ -29,6 +30,7 @@ import (
orascontext "oras.land/oras-go/pkg/context" orascontext "oras.land/oras-go/pkg/context"
"oras.land/oras-go/pkg/registry" "oras.land/oras-go/pkg/registry"
"helm.sh/helm/v3/internal/tlsutil"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chart/loader"
) )
@ -129,3 +131,27 @@ func parseReference(raw string) (registry.Reference, error) {
return registry.ParseReference(raw) return registry.ParseReference(raw)
} }
// NewRegistryClientWithTLS is a helper function to create a new registry client with TLS enabled.
func NewRegistryClientWithTLS(out io.Writer, certFile, keyFile, caFile string, insecureSkipTLSverify bool, registryConfig string, debug bool) (*Client, error) {
tlsConf, err := tlsutil.NewClientTLS(certFile, keyFile, caFile, insecureSkipTLSverify)
if err != nil {
return nil, fmt.Errorf("can't create TLS config for client: %s", err)
}
// Create a new registry client
registryClient, err := NewClient(
ClientOptDebug(debug),
ClientOptEnableCache(true),
ClientOptWriter(out),
ClientOptCredentialsFile(registryConfig),
ClientOptHTTPClient(&http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConf,
},
}),
)
if err != nil {
return nil, err
}
return registryClient, nil
}

@ -0,0 +1,391 @@
/*
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 registry
import (
"bytes"
"context"
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"github.com/distribution/distribution/v3/configuration"
"github.com/distribution/distribution/v3/registry"
_ "github.com/distribution/distribution/v3/registry/auth/htpasswd"
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
"github.com/foxcpp/go-mockdns"
"github.com/phayes/freeport"
"github.com/stretchr/testify/suite"
"golang.org/x/crypto/bcrypt"
"helm.sh/helm/v3/internal/tlsutil"
)
const (
tlsServerKey = "./testdata/tls/server-key.pem"
tlsServerCert = "./testdata/tls/server-cert.pem"
tlsCA = "./testdata/tls/ca-cert.pem"
tlsKey = "./testdata/tls/client-key.pem"
tlsCert = "./testdata/tls/client-cert.pem"
)
var (
testWorkspaceDir = "helm-registry-test"
testHtpasswdFileBasename = "authtest.htpasswd"
testUsername = "myuser"
testPassword = "mypass"
)
type TestSuite struct {
suite.Suite
Out io.Writer
DockerRegistryHost string
CompromisedRegistryHost string
WorkspaceDir string
RegistryClient *Client
// A mock DNS server needed for TLS connection testing.
srv *mockdns.Server
}
func setup(suite *TestSuite, tlsEnabled bool, insecure bool) *registry.Registry {
suite.WorkspaceDir = testWorkspaceDir
os.RemoveAll(suite.WorkspaceDir)
os.Mkdir(suite.WorkspaceDir, 0700)
var (
out bytes.Buffer
err error
)
suite.Out = &out
credentialsFile := filepath.Join(suite.WorkspaceDir, CredentialsFileBasename)
// init test client
if tlsEnabled {
var tlsConf *tls.Config
tlsConf, err = tlsutil.NewClientTLS(tlsCert, tlsKey, tlsCA, insecure)
httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConf,
},
}
suite.Nil(err, "no error loading tlsconfog")
suite.RegistryClient, err = NewClient(
ClientOptDebug(true),
ClientOptEnableCache(true),
ClientOptWriter(suite.Out),
ClientOptCredentialsFile(credentialsFile),
ClientOptHTTPClient(httpClient),
)
} else {
suite.RegistryClient, err = NewClient(
ClientOptDebug(true),
ClientOptEnableCache(true),
ClientOptWriter(suite.Out),
ClientOptCredentialsFile(credentialsFile),
)
}
suite.Nil(err, "no error creating registry client")
// create htpasswd file (w BCrypt, which is required)
pwBytes, err := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost)
suite.Nil(err, "no error generating bcrypt password for test htpasswd file")
htpasswdPath := filepath.Join(suite.WorkspaceDir, testHtpasswdFileBasename)
err = os.WriteFile(htpasswdPath, []byte(fmt.Sprintf("%s:%s\n", testUsername, string(pwBytes))), 0644)
suite.Nil(err, "no error creating test htpasswd file")
// Registry config
config := &configuration.Configuration{}
port, err := freeport.GetFreePort()
suite.Nil(err, "no error finding free port for test registry")
if tlsEnabled {
// docker has "MatchLocalhost is a host match function which returns true for
// localhost, and is used to enforce http for localhost requests."
// That function does not handle matching of ip addresses in octal,
// decimal or hex form.
suite.DockerRegistryHost = fmt.Sprintf("0x7f000001:%d", port)
// As of Go 1.20, Go may lookup "0x7f000001" as a DNS entry and fail.
// Using a mock DNS server to handle the address.
suite.srv, _ = mockdns.NewServer(map[string]mockdns.Zone{
"0x7f000001.": {
A: []string{"127.0.0.1"},
},
}, false)
suite.srv.PatchNet(net.DefaultResolver)
} else {
suite.DockerRegistryHost = fmt.Sprintf("localhost:%d", port)
}
config.HTTP.Addr = fmt.Sprintf(":%d", port)
// config.HTTP.Addr = fmt.Sprintf("127.0.0.1:%d", port)
config.HTTP.DrainTimeout = time.Duration(10) * time.Second
config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}}
config.Auth = configuration.Auth{
"htpasswd": configuration.Parameters{
"realm": "localhost",
"path": htpasswdPath,
},
}
// config tls
if tlsEnabled {
// TLS config
// this set tlsConf.ClientAuth = tls.RequireAndVerifyClientCert in the
// server tls config
config.HTTP.TLS.Certificate = tlsServerCert
config.HTTP.TLS.Key = tlsServerKey
config.HTTP.TLS.ClientCAs = []string{tlsCA}
}
dockerRegistry, err := registry.NewRegistry(context.Background(), config)
suite.Nil(err, "no error creating test registry")
suite.CompromisedRegistryHost = initCompromisedRegistryTestServer()
return dockerRegistry
}
func teardown(suite *TestSuite) {
if suite.srv != nil {
mockdns.UnpatchNet(net.DefaultResolver)
suite.srv.Close()
}
}
func initCompromisedRegistryTestServer() string {
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "manifests") {
w.Header().Set("Content-Type", "application/vnd.oci.image.manifest.v1+json")
w.WriteHeader(200)
// layers[0] is the blob []byte("a")
w.Write([]byte(
fmt.Sprintf(`{ "schemaVersion": 2, "config": {
"mediaType": "%s",
"digest": "sha256:a705ee2789ab50a5ba20930f246dbd5cc01ff9712825bb98f57ee8414377f133",
"size": 181
},
"layers": [
{
"mediaType": "%s",
"digest": "sha256:ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb",
"size": 1
}
]
}`, ConfigMediaType, ChartLayerMediaType)))
} else if r.URL.Path == "/v2/testrepo/supposedlysafechart/blobs/sha256:a705ee2789ab50a5ba20930f246dbd5cc01ff9712825bb98f57ee8414377f133" {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write([]byte("{\"name\":\"mychart\",\"version\":\"0.1.0\",\"description\":\"A Helm chart for Kubernetes\\n" +
"an 'application' or a 'library' chart.\",\"apiVersion\":\"v2\",\"appVersion\":\"1.16.0\",\"type\":" +
"\"application\"}"))
} else if r.URL.Path == "/v2/testrepo/supposedlysafechart/blobs/sha256:ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb" {
w.Header().Set("Content-Type", ChartLayerMediaType)
w.WriteHeader(200)
w.Write([]byte("b"))
} else {
w.WriteHeader(500)
}
}))
u, _ := url.Parse(s.URL)
return fmt.Sprintf("localhost:%s", u.Port())
}
func testPush(suite *TestSuite) {
// Bad bytes
ref := fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost)
_, err := suite.RegistryClient.Push([]byte("hello"), ref)
suite.NotNil(err, "error pushing non-chart bytes")
// Load a test chart
chartData, err := os.ReadFile("../repo/repotest/testdata/examplechart-0.1.0.tgz")
suite.Nil(err, "no error loading test chart")
meta, err := extractChartMeta(chartData)
suite.Nil(err, "no error extracting chart meta")
// non-strict ref (chart name)
ref = fmt.Sprintf("%s/testrepo/boop:%s", suite.DockerRegistryHost, meta.Version)
_, err = suite.RegistryClient.Push(chartData, ref)
suite.NotNil(err, "error pushing non-strict ref (bad basename)")
// non-strict ref (chart name), with strict mode disabled
_, err = suite.RegistryClient.Push(chartData, ref, PushOptStrictMode(false))
suite.Nil(err, "no error pushing non-strict ref (bad basename), with strict mode disabled")
// non-strict ref (chart version)
ref = fmt.Sprintf("%s/testrepo/%s:latest", suite.DockerRegistryHost, meta.Name)
_, err = suite.RegistryClient.Push(chartData, ref)
suite.NotNil(err, "error pushing non-strict ref (bad tag)")
// non-strict ref (chart version), with strict mode disabled
_, err = suite.RegistryClient.Push(chartData, ref, PushOptStrictMode(false))
suite.Nil(err, "no error pushing non-strict ref (bad tag), with strict mode disabled")
// basic push, good ref
chartData, err = os.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz")
suite.Nil(err, "no error loading test chart")
meta, err = extractChartMeta(chartData)
suite.Nil(err, "no error extracting chart meta")
ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version)
_, err = suite.RegistryClient.Push(chartData, ref)
suite.Nil(err, "no error pushing good ref")
_, err = suite.RegistryClient.Pull(ref)
suite.Nil(err, "no error pulling a simple chart")
// Load another test chart
chartData, err = os.ReadFile("../downloader/testdata/signtest-0.1.0.tgz")
suite.Nil(err, "no error loading test chart")
meta, err = extractChartMeta(chartData)
suite.Nil(err, "no error extracting chart meta")
// Load prov file
provData, err := os.ReadFile("../downloader/testdata/signtest-0.1.0.tgz.prov")
suite.Nil(err, "no error loading test prov")
// push with prov
ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version)
result, err := suite.RegistryClient.Push(chartData, ref, PushOptProvData(provData))
suite.Nil(err, "no error pushing good ref with prov")
_, err = suite.RegistryClient.Pull(ref)
suite.Nil(err, "no error pulling a simple chart")
// Validate the output
// Note: these digests/sizes etc may change if the test chart/prov files are modified,
// or if the format of the OCI manifest changes
suite.Equal(ref, result.Ref)
suite.Equal(meta.Name, result.Chart.Meta.Name)
suite.Equal(meta.Version, result.Chart.Meta.Version)
suite.Equal(int64(512), result.Manifest.Size)
suite.Equal(int64(99), result.Config.Size)
suite.Equal(int64(973), result.Chart.Size)
suite.Equal(int64(695), result.Prov.Size)
suite.Equal(
"sha256:af4c20a1df1431495e673c14ecfa3a2ba24839a7784349d6787cd67957392e83",
result.Manifest.Digest)
suite.Equal(
"sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580",
result.Config.Digest)
suite.Equal(
"sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55",
result.Chart.Digest)
suite.Equal(
"sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256",
result.Prov.Digest)
}
func testPull(suite *TestSuite) {
// bad/missing ref
ref := fmt.Sprintf("%s/testrepo/no-existy:1.2.3", suite.DockerRegistryHost)
_, err := suite.RegistryClient.Pull(ref)
suite.NotNil(err, "error on bad/missing ref")
// Load test chart (to build ref pushed in previous test)
chartData, err := os.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz")
suite.Nil(err, "no error loading test chart")
meta, err := extractChartMeta(chartData)
suite.Nil(err, "no error extracting chart meta")
ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version)
// Simple pull, chart only
_, err = suite.RegistryClient.Pull(ref)
suite.Nil(err, "no error pulling a simple chart")
// Simple pull with prov (no prov uploaded)
_, err = suite.RegistryClient.Pull(ref, PullOptWithProv(true))
suite.NotNil(err, "error pulling a chart with prov when no prov exists")
// Simple pull with prov, ignoring missing prov
_, err = suite.RegistryClient.Pull(ref,
PullOptWithProv(true),
PullOptIgnoreMissingProv(true))
suite.Nil(err,
"no error pulling a chart with prov when no prov exists, ignoring missing")
// Load test chart (to build ref pushed in previous test)
chartData, err = os.ReadFile("../downloader/testdata/signtest-0.1.0.tgz")
suite.Nil(err, "no error loading test chart")
meta, err = extractChartMeta(chartData)
suite.Nil(err, "no error extracting chart meta")
ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version)
// Load prov file
provData, err := os.ReadFile("../downloader/testdata/signtest-0.1.0.tgz.prov")
suite.Nil(err, "no error loading test prov")
// no chart and no prov causes error
_, err = suite.RegistryClient.Pull(ref,
PullOptWithChart(false),
PullOptWithProv(false))
suite.NotNil(err, "error on both no chart and no prov")
// full pull with chart and prov
result, err := suite.RegistryClient.Pull(ref, PullOptWithProv(true))
suite.Nil(err, "no error pulling a chart with prov")
// Validate the output
// Note: these digests/sizes etc may change if the test chart/prov files are modified,
// or if the format of the OCI manifest changes
suite.Equal(ref, result.Ref)
suite.Equal(meta.Name, result.Chart.Meta.Name)
suite.Equal(meta.Version, result.Chart.Meta.Version)
suite.Equal(int64(512), result.Manifest.Size)
suite.Equal(int64(99), result.Config.Size)
suite.Equal(int64(973), result.Chart.Size)
suite.Equal(int64(695), result.Prov.Size)
suite.Equal(
"sha256:af4c20a1df1431495e673c14ecfa3a2ba24839a7784349d6787cd67957392e83",
result.Manifest.Digest)
suite.Equal(
"sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580",
result.Config.Digest)
suite.Equal(
"sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55",
result.Chart.Digest)
suite.Equal(
"sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256",
result.Prov.Digest)
suite.Equal("{\"schemaVersion\":2,\"config\":{\"mediaType\":\"application/vnd.cncf.helm.config.v1+json\",\"digest\":\"sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580\",\"size\":99},\"layers\":[{\"mediaType\":\"application/vnd.cncf.helm.chart.provenance.v1.prov\",\"digest\":\"sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256\",\"size\":695},{\"mediaType\":\"application/vnd.cncf.helm.chart.content.v1.tar+gzip\",\"digest\":\"sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55\",\"size\":973}]}",
string(result.Manifest.Data))
suite.Equal("{\"name\":\"signtest\",\"version\":\"0.1.0\",\"description\":\"A Helm chart for Kubernetes\",\"apiVersion\":\"v1\"}",
string(result.Config.Data))
suite.Equal(chartData, result.Chart.Data)
suite.Equal(provData, result.Prov.Data)
}
func testTags(suite *TestSuite) {
// Load test chart (to build ref pushed in previous test)
chartData, err := os.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz")
suite.Nil(err, "no error loading test chart")
meta, err := extractChartMeta(chartData)
suite.Nil(err, "no error extracting chart meta")
ref := fmt.Sprintf("%s/testrepo/%s", suite.DockerRegistryHost, meta.Name)
// Query for tags and validate length
tags, err := suite.RegistryClient.Tags(ref)
suite.Nil(err, "no error retrieving tags")
suite.Equal(1, len(tags))
}

@ -21,7 +21,7 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io"
"log" "log"
"net/url" "net/url"
"os" "os"
@ -131,7 +131,7 @@ func (r *ChartRepository) DownloadIndexFile() (string, error) {
return "", err return "", err
} }
index, err := ioutil.ReadAll(resp) index, err := io.ReadAll(resp)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -148,12 +148,12 @@ func (r *ChartRepository) DownloadIndexFile() (string, error) {
} }
chartsFile := filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name)) chartsFile := filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name))
os.MkdirAll(filepath.Dir(chartsFile), 0755) os.MkdirAll(filepath.Dir(chartsFile), 0755)
ioutil.WriteFile(chartsFile, []byte(charts.String()), 0644) os.WriteFile(chartsFile, []byte(charts.String()), 0644)
// Create the index file in the cache directory // Create the index file in the cache directory
fname := filepath.Join(r.CachePath, helmpath.CacheIndexFile(r.Config.Name)) fname := filepath.Join(r.CachePath, helmpath.CacheIndexFile(r.Config.Name))
os.MkdirAll(filepath.Dir(fname), 0755) os.MkdirAll(filepath.Dir(fname), 0755)
return fname, ioutil.WriteFile(fname, index, 0644) return fname, os.WriteFile(fname, index, 0644)
} }
// Index generates an index for the chart repository and writes an index.yaml file. // Index generates an index for the chart repository and writes an index.yaml file.
@ -170,7 +170,7 @@ func (r *ChartRepository) saveIndexFile() error {
if err != nil { if err != nil {
return err return err
} }
return ioutil.WriteFile(filepath.Join(r.Config.Name, indexPath), index, 0644) return os.WriteFile(filepath.Join(r.Config.Name, indexPath), index, 0644)
} }
func (r *ChartRepository) generateIndex() error { func (r *ChartRepository) generateIndex() error {

@ -18,7 +18,6 @@ package repo
import ( import (
"bytes" "bytes"
"io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "os"
@ -151,7 +150,7 @@ func TestIndexCustomSchemeDownload(t *testing.T) {
repo.CachePath = ensure.TempDir(t) repo.CachePath = ensure.TempDir(t)
defer os.RemoveAll(repo.CachePath) defer os.RemoveAll(repo.CachePath)
tempIndexFile, err := ioutil.TempFile("", "test-repo") tempIndexFile, err := os.CreateTemp("", "test-repo")
if err != nil { if err != nil {
t.Fatalf("Failed to create temp index file: %v", err) t.Fatalf("Failed to create temp index file: %v", err)
} }
@ -266,7 +265,7 @@ func verifyIndex(t *testing.T, actual *IndexFile) {
// startLocalServerForTests Start the local helm server // startLocalServerForTests Start the local helm server
func startLocalServerForTests(handler http.Handler) (*httptest.Server, error) { func startLocalServerForTests(handler http.Handler) (*httptest.Server, error) {
if handler == nil { if handler == nil {
fileBytes, err := ioutil.ReadFile("testdata/local-index.yaml") fileBytes, err := os.ReadFile("testdata/local-index.yaml")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -281,7 +280,7 @@ func startLocalServerForTests(handler http.Handler) (*httptest.Server, error) {
// startLocalTLSServerForTests Start the local helm server with TLS // startLocalTLSServerForTests Start the local helm server with TLS
func startLocalTLSServerForTests(handler http.Handler) (*httptest.Server, error) { func startLocalTLSServerForTests(handler http.Handler) (*httptest.Server, error) {
if handler == nil { if handler == nil {
fileBytes, err := ioutil.ReadFile("testdata/local-index.yaml") fileBytes, err := os.ReadFile("testdata/local-index.yaml")
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
/*Package repo implements the Helm Chart Repository. /*
Package repo implements the Helm Chart Repository.
A chart repository is an HTTP server that provides information on charts. A local A chart repository is an HTTP server that provides information on charts. A local
repository cache is an on-disk representation of a chart repository. repository cache is an on-disk representation of a chart repository.

@ -18,7 +18,6 @@ package repo
import ( import (
"bytes" "bytes"
"io/ioutil"
"log" "log"
"os" "os"
"path" "path"
@ -104,7 +103,7 @@ func NewIndexFile() *IndexFile {
// LoadIndexFile takes a file at the given path and returns an IndexFile object // LoadIndexFile takes a file at the given path and returns an IndexFile object
func LoadIndexFile(path string) (*IndexFile, error) { func LoadIndexFile(path string) (*IndexFile, error) {
b, err := ioutil.ReadFile(path) b, err := os.ReadFile(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -19,7 +19,6 @@ package repo
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"io/ioutil"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@ -273,7 +272,7 @@ func TestDownloadIndexFile(t *testing.T) {
t.Fatalf("error finding created charts file: %#v", err) t.Fatalf("error finding created charts file: %#v", err)
} }
b, err := ioutil.ReadFile(idx) b, err := os.ReadFile(idx)
if err != nil { if err != nil {
t.Fatalf("error reading charts file: %#v", err) t.Fatalf("error reading charts file: %#v", err)
} }
@ -282,7 +281,7 @@ func TestDownloadIndexFile(t *testing.T) {
t.Run("should not decode the path in the repo url while downloading index", func(t *testing.T) { t.Run("should not decode the path in the repo url while downloading index", func(t *testing.T) {
chartRepoURLPath := "/some%2Fpath/test" chartRepoURLPath := "/some%2Fpath/test"
fileBytes, err := ioutil.ReadFile("testdata/local-index.yaml") fileBytes, err := os.ReadFile("testdata/local-index.yaml")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -326,7 +325,7 @@ func TestDownloadIndexFile(t *testing.T) {
t.Fatalf("error finding created charts file: %#v", err) t.Fatalf("error finding created charts file: %#v", err)
} }
b, err := ioutil.ReadFile(idx) b, err := os.ReadFile(idx)
if err != nil { if err != nil {
t.Fatalf("error reading charts file: %#v", err) t.Fatalf("error reading charts file: %#v", err)
} }
@ -533,7 +532,7 @@ func TestIndexWrite(t *testing.T) {
testpath := filepath.Join(dir, "test") testpath := filepath.Join(dir, "test")
i.WriteFile(testpath, 0600) i.WriteFile(testpath, 0600)
got, err := ioutil.ReadFile(testpath) got, err := os.ReadFile(testpath)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -17,7 +17,6 @@ limitations under the License.
package repo // import "helm.sh/helm/v3/pkg/repo" package repo // import "helm.sh/helm/v3/pkg/repo"
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
@ -47,7 +46,7 @@ func NewFile() *File {
// LoadFile takes a file at the given path and returns a File object // LoadFile takes a file at the given path and returns a File object
func LoadFile(path string) (*File, error) { func LoadFile(path string) (*File, error) {
r := new(File) r := new(File)
b, err := ioutil.ReadFile(path) b, err := os.ReadFile(path)
if err != nil { if err != nil {
return r, errors.Wrapf(err, "couldn't load repositories file (%s)", path) return r, errors.Wrapf(err, "couldn't load repositories file (%s)", path)
} }
@ -122,5 +121,5 @@ func (r *File) WriteFile(path string, perm os.FileMode) error {
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
return err return err
} }
return ioutil.WriteFile(path, data, perm) return os.WriteFile(path, data, perm)
} }

@ -17,7 +17,6 @@ limitations under the License.
package repo package repo
import ( import (
"io/ioutil"
"os" "os"
"strings" "strings"
"testing" "testing"
@ -115,11 +114,11 @@ func TestRepoFile_Get(t *testing.T) {
name := "second" name := "second"
entry := repo.Get(name) entry := repo.Get(name)
if entry == nil { if entry == nil { //nolint:staticcheck
t.Fatalf("Expected repo entry %q to be found", name) t.Fatalf("Expected repo entry %q to be found", name)
} }
if entry.URL != "https://example.com/second" { if entry.URL != "https://example.com/second" { //nolint:staticcheck
t.Errorf("Expected repo URL to be %q but got %q", "https://example.com/second", entry.URL) t.Errorf("Expected repo URL to be %q but got %q", "https://example.com/second", entry.URL)
} }
@ -198,7 +197,7 @@ func TestWriteFile(t *testing.T) {
}, },
) )
file, err := ioutil.TempFile("", "helm-repo") file, err := os.CreateTemp("", "helm-repo")
if err != nil { if err != nil {
t.Errorf("failed to create test-file (%v)", err) t.Errorf("failed to create test-file (%v)", err)
} }

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

Loading…
Cancel
Save