diff --git a/cmd/helm/repo_add.go b/cmd/helm/repo_add.go index 83fcdf50c..7db3c73f7 100644 --- a/cmd/helm/repo_add.go +++ b/cmd/helm/repo_add.go @@ -62,6 +62,7 @@ type repoAddOptions struct { // Deprecated, but cannot be removed until Helm 4 deprecatedNoUpdate bool + repoProxyUrl string } func newRepoAddCmd(out io.Writer) *cobra.Command { @@ -94,7 +95,7 @@ func newRepoAddCmd(out io.Writer) *cobra.Command { f.BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the repository") f.BoolVar(&o.allowDeprecatedRepos, "allow-deprecated-repos", false, "by default, this command will not allow adding official repos that have been permanently deleted. This disables that behavior") f.BoolVar(&o.passCredentialsAll, "pass-credentials", false, "pass credentials to all domains") - + f.StringVar(&o.repoProxyUrl, "repo-proxy-url", "", "proxy for accessing the repository and its URL format follows the global option --proxy-url") return cmd } @@ -165,15 +166,17 @@ func (o *repoAddOptions) run(out io.Writer) error { } c := repo.Entry{ - Name: o.name, - URL: o.url, - Username: o.username, - Password: o.password, - PassCredentialsAll: o.passCredentialsAll, - CertFile: o.certFile, - KeyFile: o.keyFile, - CAFile: o.caFile, - InsecureSkipTLSverify: o.insecureSkipTLSverify, + Name: o.name, + URL: o.url, + Username: o.username, + Password: o.password, + PassCredentialsAll: o.passCredentialsAll, + CertFile: o.certFile, + KeyFile: o.keyFile, + CAFile: o.caFile, + InsecureSkipTLSverify: o.insecureSkipTLSverify, + RepoProxyUrl: o.repoProxyUrl, + DoNotUseEnvSettingsProxyUrl: settings.ProxyUrl, } // Check if the repo name is legal diff --git a/cmd/helm/root.go b/cmd/helm/root.go index c5f8cc708..d459d6566 100644 --- a/cmd/helm/root.go +++ b/cmd/helm/root.go @@ -71,7 +71,7 @@ Environment variables: | $HELM_KUBETLS_SERVER_NAME | set the server name used to validate the Kubernetes API server certificate | | $HELM_BURST_LIMIT | set the default burst limit in the case the server contains many CRDs (default 100, -1 to disable) | | $HELM_QPS | set the Queries Per Second in cases where a high number of calls exceed the option for higher burst values | - +| $HELM_PROXY_URL | set the proxy url use this parameter to specify a proxy server URL for accessing the Helm repository | Helm stores cache, configuration, and data based on the following configuration order: - If a HELM_*_HOME environment variable is set, it will be used diff --git a/pkg/action/install.go b/pkg/action/install.go index 6dce3ccbb..a27d7751b 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -806,7 +806,9 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) ( } else { dl.Options = append(dl.Options, getter.WithBasicAuth(c.Username, c.Password)) } - + if settings.ProxyUrl != "" { + dl.Options = append(dl.Options, getter.WithProxyUrl(settings.ProxyUrl)) + } if err := os.MkdirAll(settings.RepositoryCache, 0755); err != nil { return "", err } diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 438ba1515..9143a669c 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -88,6 +88,8 @@ type EnvSettings struct { BurstLimit int // QPS is queries per second which may be used to avoid throttling. QPS float32 + // Proxy proxy url + ProxyUrl string } func New() *EnvSettings { @@ -108,6 +110,7 @@ func New() *EnvSettings { RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")), BurstLimit: envIntOr("HELM_BURST_LIMIT", defaultBurstLimit), QPS: envFloat32Or("HELM_QPS", defaultQPS), + ProxyUrl: os.Getenv("HELM_PROXY_URL"), } env.Debug, _ = strconv.ParseBool(os.Getenv("HELM_DEBUG")) @@ -159,6 +162,7 @@ func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&s.RepositoryCache, "repository-cache", s.RepositoryCache, "path to the file containing cached repository indexes") fs.IntVar(&s.BurstLimit, "burst-limit", s.BurstLimit, "client-side default throttling limit") fs.Float32Var(&s.QPS, "qps", s.QPS, "queries per second used when communicating with the Kubernetes API, not including bursting") + fs.StringVar(&s.ProxyUrl, "proxy-url", s.ProxyUrl, "use this parameter to specify a proxy server URL for accessing the Helm repository. The proxy URL must be in the format [scheme://][user[:password]@]host[:port], where scheme can be http, https, or socks5.") } func envOr(name, def string) string { @@ -237,6 +241,7 @@ func (s *EnvSettings) EnvVars() map[string]string { "HELM_KUBECAFILE": s.KubeCaFile, "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": strconv.FormatBool(s.KubeInsecureSkipTLSVerify), "HELM_KUBETLS_SERVER_NAME": s.KubeTLSServerName, + "HELM_PROXY_URL": s.ProxyUrl, } if s.KubeConfig != "" { envvars["KUBECONFIG"] = s.KubeConfig diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index a95894e00..3e3073cd1 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -275,6 +275,9 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er getter.WithPassCredentialsAll(r.Config.PassCredentialsAll), ) } + if r.Config.RepoProxyUrl != "" { + c.Options = append(c.Options, getter.WithProxyUrl(r.Config.RepoProxyUrl)) + } } // Next, we need to load the index, and actually look up the chart. diff --git a/pkg/getter/getter.go b/pkg/getter/getter.go index 45ab4da7e..4d990bf24 100644 --- a/pkg/getter/getter.go +++ b/pkg/getter/getter.go @@ -46,6 +46,7 @@ type options struct { registryClient *registry.Client timeout time.Duration transport *http.Transport + proxyUrl string } // Option allows specifying various settings configurable by the user for overriding the defaults @@ -121,7 +122,11 @@ func WithRegistryClient(client *registry.Client) Option { opts.registryClient = client } } - +func WithProxyUrl(proxyUrl string) Option { + return func(opts *options) { + opts.proxyUrl = proxyUrl + } +} func WithUntar() Option { return func(opts *options) { opts.unTar = true @@ -170,10 +175,10 @@ type Providers []Provider // ByScheme returns a Provider that handles the given scheme. // // If no provider handles this scheme, this will return an error. -func (p Providers) ByScheme(scheme string) (Getter, error) { +func (p Providers) ByScheme(scheme string, options ...Option) (Getter, error) { for _, pp := range p { if pp.Provides(scheme) { - return pp.New() + return pp.New(options...) } } return nil, errors.Errorf("scheme %q not supported", scheme) diff --git a/pkg/getter/httpgetter.go b/pkg/getter/httpgetter.go index b53e558e3..cf49475d4 100644 --- a/pkg/getter/httpgetter.go +++ b/pkg/getter/httpgetter.go @@ -117,9 +117,17 @@ func (g *HTTPGetter) httpClient() (*http.Client, error) { } g.once.Do(func() { + var proxyFunc func(*http.Request) (*url.URL, error) + if g.opts.proxyUrl != "" { + proxyFunc = func(request *http.Request) (*url.URL, error) { + return url.Parse(g.opts.proxyUrl) + } + } else { + proxyFunc = http.ProxyFromEnvironment + } g.transport = &http.Transport{ DisableCompression: true, - Proxy: http.ProxyFromEnvironment, + Proxy: proxyFunc, } }) diff --git a/pkg/getter/ocigetter.go b/pkg/getter/ocigetter.go index 0547cdcbb..e151345c7 100644 --- a/pkg/getter/ocigetter.go +++ b/pkg/getter/ocigetter.go @@ -20,6 +20,7 @@ import ( "fmt" "net" "net/http" + "net/url" "strings" "sync" "time" @@ -104,6 +105,14 @@ func (g *OCIGetter) newRegistryClient() (*registry.Client, error) { } g.once.Do(func() { + var proxyFunc func(*http.Request) (*url.URL, error) + if g.opts.proxyUrl != "" { + proxyFunc = func(request *http.Request) (*url.URL, error) { + return url.Parse(g.opts.proxyUrl) + } + } else { + proxyFunc = http.ProxyFromEnvironment + } g.transport = &http.Transport{ // From https://github.com/google/go-containerregistry/blob/31786c6cbb82d6ec4fb8eb79cd9387905130534e/pkg/v1/remote/options.go#L87 DisableCompression: true, @@ -119,7 +128,7 @@ func (g *OCIGetter) newRegistryClient() (*registry.Client, error) { IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, - Proxy: http.ProxyFromEnvironment, + Proxy: proxyFunc, } }) diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go index 970e96da2..a780ef880 100644 --- a/pkg/repo/chartrepo.go +++ b/pkg/repo/chartrepo.go @@ -48,6 +48,10 @@ type Entry struct { CAFile string `json:"caFile"` InsecureSkipTLSverify bool `json:"insecure_skip_tls_verify"` PassCredentialsAll bool `json:"pass_credentials_all"` + RepoProxyUrl string `json:"repo_proxy_url"` + // This field is for handling proxy URL priority in NewChartRepository, + // and will not actually store any data. + DoNotUseEnvSettingsProxyUrl string `json:"-"` } // ChartRepository represents a chart repository @@ -65,8 +69,13 @@ func NewChartRepository(cfg *Entry, getters getter.Providers) (*ChartRepository, if err != nil { return nil, errors.Errorf("invalid chart URL format: %s", cfg.URL) } - - client, err := getters.ByScheme(u.Scheme) + var proxyUrl string + if cfg.RepoProxyUrl != "" { + proxyUrl = cfg.RepoProxyUrl + } else if cfg.DoNotUseEnvSettingsProxyUrl != "" { + proxyUrl = cfg.DoNotUseEnvSettingsProxyUrl + } + client, err := getters.ByScheme(u.Scheme, getter.WithProxyUrl(proxyUrl)) if err != nil { return nil, errors.Errorf("could not find protocol handler for: %s", u.Scheme) }