From 80224849cdb65ee493c20be6538e9b415976bea8 Mon Sep 17 00:00:00 2001 From: Bhargav Ravuri Date: Tue, 14 Mar 2023 18:50:31 +0530 Subject: [PATCH] Backup Kube Config to Helm Config Directory Backup kubeconfig to /kubeconfig during initialization of subcommands (env, install, etc.) The backup file will have rw permissions for user. All subcommands will then use backup kubeconfig file. Signed-off-by: Bhargav Ravuri --- cmd/helm/helm.go | 12 +++ pkg/cli/environment.go | 40 +++++++++ pkg/cli/environment_test.go | 84 +++++++++++++++++++ pkg/cli/testdata/valid-kubeconfig-no-contexts | 11 +++ 4 files changed, 147 insertions(+) create mode 100644 pkg/cli/testdata/valid-kubeconfig-no-contexts diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 15b0d5c76..1ee6242e9 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -72,6 +72,18 @@ func main() { // run when each command's execute method is called cobra.OnInitialize(func() { helmDriver := os.Getenv("HELM_DRIVER") + + if settings.KubeConfig != "" { + // If KubeConfig path is not empty, backup kube config to: /kubeconfig + // When the backup is successful, the settings.KubeConfig path is updated to backup file's path. + err = settings.BackupKubeConfig() + if err != nil { + log.Fatal(err) + } + } else { + debug("kube config backup aborted due to empty KubeConfig path in settings") + } + if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), helmDriver, debug); err != nil { log.Fatal(err) } diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index dac2a4bc1..bd062d77d 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -27,6 +27,7 @@ import ( "fmt" "net/http" "os" + "path/filepath" "strconv" "strings" @@ -34,6 +35,7 @@ import ( "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/rest" + "helm.sh/helm/v3/internal/fileutil" "helm.sh/helm/v3/internal/version" "helm.sh/helm/v3/pkg/helmpath" ) @@ -235,3 +237,41 @@ func (s *EnvSettings) SetNamespace(namespace string) { func (s *EnvSettings) RESTClientGetter() genericclioptions.RESTClientGetter { return s.config } + +// BackupKubeConfig copies kube config file to /kubeconfig +// as backup. The backup file will have rw permissions for user. +// The backup file's path is updated in EnvSettings.KubeConfig. +// +// There is no safety check on EnvSettings.KubeConfig, i.e., the caller +// should call this method only when there is kube config path specified. +// Else, the actual kubeconfig file read fails. +func (s *EnvSettings) BackupKubeConfig() error { + // Form the backup file path as: /kubeconfig + // The helm-config-path is derived from envs HELM_CONFIG_HOME, + // XDG_CONFIG_HOME and default path. Defaults paths: + // - Linux : $HOME/.config/helm + // - Mac : $HOME/Library/Preferences/helm + // - Windows : %APPDATA%\helm + helmConfigPath := helmpath.ConfigPath("") + kubeConfigBackupFilename := filepath.Join(helmConfigPath, "kubeconfig") + + // Create reader from actual kube config file + kubeConfigReader, err := os.Open(s.KubeConfig) + if err != nil { + return fmt.Errorf("failed to read input kubeconfig file %q: %v", + s.KubeConfig, err) + } + defer kubeConfigReader.Close() + + // Copy actual kube config file to backup file + err = fileutil.AtomicWriteFile(kubeConfigBackupFilename, kubeConfigReader, 0600) + if err != nil { + return fmt.Errorf("failed to copy kubeconfig from %q to %q: %v", + s.KubeConfig, kubeConfigBackupFilename, err) + } + + // Update the kube config in EnvSettings to backup file path + s.KubeConfig = kubeConfigBackupFilename + + return nil +} diff --git a/pkg/cli/environment_test.go b/pkg/cli/environment_test.go index 3de6fab4c..811ce2460 100644 --- a/pkg/cli/environment_test.go +++ b/pkg/cli/environment_test.go @@ -263,3 +263,87 @@ func resetEnv() func() { } } } + +func TestEnvSettings_BackupKubeConfig(t *testing.T) { + var ( + testDataDir = `testdata/` + kubeConfigFilename = testDataDir + "kubeconfig" + ) + + type fields struct { + KubeConfig string + helmConfigHome string + } + + type toggles struct { + wantErr bool + cleanUpTestKubeConfig bool + } + + type testCase struct { + name string + fields fields + toggles toggles + } + + tests := []testCase{ + { + name: "Backup kube config", + fields: fields{ + KubeConfig: testDataDir + `valid-kubeconfig-no-contexts`, + helmConfigHome: testDataDir, + }, + toggles: toggles{ + cleanUpTestKubeConfig: true, + }, + }, + { + name: "Failure missing input kube config file", + fields: fields{ + KubeConfig: testDataDir + `missing-kubeconfig`, + helmConfigHome: testDataDir, + }, + toggles: toggles{ + wantErr: true, + }, + }, + { + name: "Failure invalid destination path", + fields: fields{ + KubeConfig: testDataDir + `valid-kubeconfig-no-contexts`, + helmConfigHome: testDataDir + `non-existing-dir/`, + }, + toggles: toggles{ + wantErr: true, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &EnvSettings{ + KubeConfig: tt.fields.KubeConfig, + } + + t.Setenv(`HELM_CONFIG_HOME`, tt.fields.helmConfigHome) + + err := s.BackupKubeConfig() + if (err != nil) != tt.toggles.wantErr { + t.Errorf("EnvSettings.BackupKubeConfig() error = %v, wantErr %v", + err, tt.toggles.wantErr) + } + + if !tt.toggles.wantErr && s.KubeConfig != kubeConfigFilename { + t.Errorf("kube config path not updated after backup, want = %s, got = %s", + kubeConfigFilename, s.KubeConfig) + } + + if tt.toggles.cleanUpTestKubeConfig { + err = os.Remove(kubeConfigFilename) + if err != nil { + t.Errorf("failed to delete %q: %v", kubeConfigFilename, err) + } + } + }) + } +} diff --git a/pkg/cli/testdata/valid-kubeconfig-no-contexts b/pkg/cli/testdata/valid-kubeconfig-no-contexts new file mode 100644 index 000000000..57ddc5f7d --- /dev/null +++ b/pkg/cli/testdata/valid-kubeconfig-no-contexts @@ -0,0 +1,11 @@ +apiVersion: v1 +clusters: null +contexts: null +current-context: "" +kind: Config +preferences: {} +users: +- name: kubeuser/ + user: + password: password + username: user