diff --git a/cmd/helm/root.go b/cmd/helm/root.go index 55b7e8892..e603d78d1 100644 --- a/cmd/helm/root.go +++ b/cmd/helm/root.go @@ -62,6 +62,7 @@ Environment variables: | $HELM_REPOSITORY_CONFIG | set the path to the repositories file. | | $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") | | $HELM_KUBEAPISERVER | set the Kubernetes API Server Endpoint for authentication | +| $HELM_KUBEAPITIMEOUT | set the timeout on requests towards the Kubernetes API Server. Requires a valid time unit (e.g. 30s, 1m). | | $HELM_KUBECAFILE | set the Kubernetes certificate authority file. | | $HELM_KUBEASGROUPS | set the Groups to use for impersonation using a comma-separated list. | | $HELM_KUBEASUSER | set the Username to impersonate for the operation. | diff --git a/cmd/helm/testdata/output/env-comp.txt b/cmd/helm/testdata/output/env-comp.txt index 8f9c53fc7..d7658d1c0 100644 --- a/cmd/helm/testdata/output/env-comp.txt +++ b/cmd/helm/testdata/output/env-comp.txt @@ -5,6 +5,7 @@ HELM_CONFIG_HOME HELM_DATA_HOME HELM_DEBUG HELM_KUBEAPISERVER +HELM_KUBEAPITIMEOUT HELM_KUBEASGROUPS HELM_KUBEASUSER HELM_KUBECAFILE diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 4f74f2642..45b119883 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -29,6 +29,7 @@ import ( "os" "strconv" "strings" + "time" "github.com/spf13/pflag" "k8s.io/cli-runtime/pkg/genericclioptions" @@ -64,6 +65,8 @@ type EnvSettings struct { KubeAsGroups []string // Kubernetes API Server Endpoint for authentication KubeAPIServer string + // The timeout on requests towards the Kubernetes API Server Endpoint. + KubeAPITimeout time.Duration // Custom certificate authority file. KubeCaFile string // KubeInsecureSkipTLSVerify indicates if server's certificate will not be checked for validity. @@ -99,6 +102,7 @@ func New() *EnvSettings { KubeAsUser: os.Getenv("HELM_KUBEASUSER"), KubeAsGroups: envCSV("HELM_KUBEASGROUPS"), KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"), + KubeAPITimeout: envDurationOr("HELM_KUBEAPITIMEOUT", 0), KubeCaFile: os.Getenv("HELM_KUBECAFILE"), KubeTLSServerName: os.Getenv("HELM_KUBETLS_SERVER_NAME"), KubeInsecureSkipTLSVerify: envBoolOr("HELM_KUBEINSECURE_SKIP_TLS_VERIFY", false), @@ -126,6 +130,7 @@ func New() *EnvSettings { WrapConfigFn: func(config *rest.Config) *rest.Config { config.Burst = env.BurstLimit config.QPS = env.QPS + config.Timeout = env.KubeAPITimeout config.Wrap(func(rt http.RoundTripper) http.RoundTripper { return &retryingRoundTripper{wrapped: rt} }) @@ -145,6 +150,7 @@ func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&s.KubeAsUser, "kube-as-user", s.KubeAsUser, "username to impersonate for the operation") fs.StringArrayVar(&s.KubeAsGroups, "kube-as-group", s.KubeAsGroups, "group to impersonate for the operation, this flag can be repeated to specify multiple groups.") fs.StringVar(&s.KubeAPIServer, "kube-apiserver", s.KubeAPIServer, "the address and the port for the Kubernetes API server") + fs.DurationVar(&s.KubeAPITimeout, "kube-apitimeout", s.KubeAPITimeout, "the timeout on requests towards the Kubernetes API server") fs.StringVar(&s.KubeCaFile, "kube-ca-file", s.KubeCaFile, "the certificate authority file for the Kubernetes API server connection") fs.StringVar(&s.KubeTLSServerName, "kube-tls-server-name", s.KubeTLSServerName, "server name to use for Kubernetes API server certificate validation. If it is not provided, the hostname used to contact the server is used") fs.BoolVar(&s.KubeInsecureSkipTLSVerify, "kube-insecure-skip-tls-verify", s.KubeInsecureSkipTLSVerify, "if true, the Kubernetes API server's certificate will not be checked for validity. This will make your HTTPS connections insecure") @@ -199,6 +205,18 @@ func envFloat32Or(name string, def float32) float32 { return float32(ret) } +func envDurationOr(name string, def time.Duration) time.Duration { + if name == "" { + return def + } + envVal := envOr(name, def.String()) + ret, err := time.ParseDuration(envVal) + if err != nil { + return def + } + return ret +} + func envCSV(name string) (ls []string) { trimmed := strings.Trim(os.Getenv(name), ", ") if trimmed != "" { @@ -207,6 +225,14 @@ func envCSV(name string) (ls []string) { return } +// An unconfigured duration is presented as an empty string instead of '0s'. +func durationToEnvString(value time.Duration) string { + if value == 0 { + return "" + } + return value.String() +} + func (s *EnvSettings) EnvVars() map[string]string { envvars := map[string]string{ "HELM_BIN": os.Args[0], @@ -229,6 +255,7 @@ func (s *EnvSettings) EnvVars() map[string]string { "HELM_KUBEASUSER": s.KubeAsUser, "HELM_KUBEASGROUPS": strings.Join(s.KubeAsGroups, ","), "HELM_KUBEAPISERVER": s.KubeAPIServer, + "HELM_KUBEAPITIMEOUT": durationToEnvString(s.KubeAPITimeout), "HELM_KUBECAFILE": s.KubeCaFile, "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": strconv.FormatBool(s.KubeInsecureSkipTLSVerify), "HELM_KUBETLS_SERVER_NAME": s.KubeTLSServerName, diff --git a/pkg/cli/environment_test.go b/pkg/cli/environment_test.go index 1692a89d5..9a078e8ef 100644 --- a/pkg/cli/environment_test.go +++ b/pkg/cli/environment_test.go @@ -21,6 +21,7 @@ import ( "reflect" "strings" "testing" + "time" "github.com/spf13/pflag" @@ -55,6 +56,7 @@ func TestEnvSettings(t *testing.T) { maxhistory int kubeAsUser string kubeAsGroups []string + kubeTimeout time.Duration kubeCaFile string kubeInsecure bool kubeTLSServer string @@ -70,7 +72,7 @@ func TestEnvSettings(t *testing.T) { }, { name: "with flags set", - args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/tmp/ca.crt --burst-limit 100 --qps 50.12 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org", + args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-apitimeout=10s --kube-ca-file=/tmp/ca.crt --burst-limit 100 --qps 50.12 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org", ns: "myns", debug: true, maxhistory: defaultMaxHistory, @@ -78,13 +80,14 @@ func TestEnvSettings(t *testing.T) { qps: 50.12, kubeAsUser: "poro", kubeAsGroups: []string{"admins", "teatime", "snackeaters"}, + kubeTimeout: 10 * time.Second, kubeCaFile: "/tmp/ca.crt", kubeTLSServer: "example.org", kubeInsecure: true, }, { name: "with envvars set", - envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "150", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org", "HELM_QPS": "60.34"}, + envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_KUBEAPITIMEOUT": "40s", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "150", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org", "HELM_QPS": "60.34"}, ns: "yourns", maxhistory: 5, burstLimit: 150, @@ -92,14 +95,15 @@ func TestEnvSettings(t *testing.T) { debug: true, kubeAsUser: "pikachu", kubeAsGroups: []string{"operators", "snackeaters", "partyanimals"}, + kubeTimeout: 40 * time.Second, kubeCaFile: "/tmp/ca.crt", kubeTLSServer: "example.org", kubeInsecure: true, }, { name: "with flags and envvars set", - args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/my/ca.crt --burst-limit 175 --qps 70 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org", - envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "200", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org", "HELM_QPS": "40"}, + args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-apitimeout=45s --kube-ca-file=/my/ca.crt --burst-limit 175 --qps 70 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org", + envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_KUBEAPITIMEOUT": "1m30s", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "200", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org", "HELM_QPS": "40"}, ns: "myns", debug: true, maxhistory: 5, @@ -107,6 +111,7 @@ func TestEnvSettings(t *testing.T) { qps: 70, kubeAsUser: "poro", kubeAsGroups: []string{"admins", "teatime", "snackeaters"}, + kubeTimeout: 45 * time.Second, kubeCaFile: "/my/ca.crt", kubeTLSServer: "example.org", kubeInsecure: true, @@ -145,6 +150,9 @@ func TestEnvSettings(t *testing.T) { if !reflect.DeepEqual(tt.kubeAsGroups, settings.KubeAsGroups) { t.Errorf("expected kAsGroups %+v, got %+v", len(tt.kubeAsGroups), len(settings.KubeAsGroups)) } + if tt.kubeTimeout != settings.KubeAPITimeout { + t.Errorf("expected KubeAPITimeout %q, got %q", tt.kubeTimeout, settings.KubeAPITimeout) + } if tt.kubeCaFile != settings.KubeCaFile { t.Errorf("expected kCaFile %q, got %q", tt.kubeCaFile, settings.KubeCaFile) }