github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/playground/kubeconfig.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package playground 21 22 import ( 23 "fmt" 24 "os" 25 "path/filepath" 26 "time" 27 28 "k8s.io/client-go/tools/clientcmd" 29 clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 30 "k8s.io/klog/v2" 31 ) 32 33 // writeKubeConfigOptions provides a set of options for writing a KubeConfig file 34 type writeKubeConfigOptions struct { 35 UpdateExisting bool 36 UpdateCurrentContext bool 37 OverwriteExisting bool 38 } 39 40 // kubeConfigWrite writes the kubeconfig to the specified output 41 func kubeConfigWrite(kubeConfigStr string, output string, options writeKubeConfigOptions) error { 42 var err error 43 44 // convert the kubeconfig string to a kubeconfig object 45 kubeConfig, err := clientcmd.Load([]byte(kubeConfigStr)) 46 if err != nil { 47 return fmt.Errorf("failed to load kubeconfig: %w", err) 48 } 49 50 // if output is not specified, use the default kubeconfig path 51 if output == "" { 52 output, err = kubeConfigGetDefaultPath() 53 if err != nil { 54 return fmt.Errorf("failed to get default kubeconfig path: %w", err) 55 } 56 } 57 58 // simply write the kubeconfig to the output path, ignoring existing contents 59 if options.OverwriteExisting || output == "-" { 60 return kubeConfigWriteToPath(kubeConfig, output) 61 } 62 63 var existingKubeConfig *clientcmdapi.Config 64 firstRun := true 65 for { 66 existingKubeConfig, err = clientcmd.LoadFromFile(output) 67 if err == nil { 68 break 69 } 70 71 // the output file does not exist, try to create it and try again 72 if os.IsNotExist(err) && firstRun { 73 klog.V(1).Infof("Output path '%s' does not exist, try to create it", output) 74 75 // create directory path 76 if err := os.MkdirAll(filepath.Dir(output), 0755); err != nil { 77 return fmt.Errorf("failed to create output directory '%s': %w", filepath.Dir(output), err) 78 } 79 80 // create output file 81 f, err := os.Create(output) 82 if err != nil { 83 return fmt.Errorf("failed to create output file '%s': %w", output, err) 84 } 85 f.Close() 86 87 // try again 88 firstRun = false 89 continue 90 } 91 return fmt.Errorf("failed to load kubeconfig from output path '%s': %w", output, err) 92 } 93 return kubeConfigMerge(kubeConfig, existingKubeConfig, output, options) 94 } 95 96 // kubeConfigGetDefaultPath returns the path of the default kubeconfig, print errors 97 // if the KUBECONFIG env var specifies more than one file 98 func kubeConfigGetDefaultPath() (string, error) { 99 defaultKubeConfigLoadingRules := clientcmd.NewDefaultClientConfigLoadingRules() 100 if len(defaultKubeConfigLoadingRules.GetLoadingPrecedence()) > 1 { 101 return "", fmt.Errorf("multiple kubeconfigs specified via KUBECONFIG env var: Please reduce to one entry, unset KUBECONFIG or explicitly choose one") 102 } 103 return defaultKubeConfigLoadingRules.GetDefaultFilename(), nil 104 } 105 106 // kubeConfigMerge merges the kubeconfig into the existing kubeconfig and writes it to the output path 107 func kubeConfigMerge(kubeConfig, existingKubeConfig *clientcmdapi.Config, output string, options writeKubeConfigOptions) error { 108 klog.V(1).Infof("Merging new kubeconfig:\n%+v\n>>> into existing Kubeconfig:\n%+v", kubeConfig, existingKubeConfig) 109 110 // overwrite values in existing kubeconfig with values from new kubeconfig 111 for k, v := range kubeConfig.Clusters { 112 if _, ok := existingKubeConfig.Clusters[k]; ok && !options.UpdateExisting { 113 return fmt.Errorf("cluster \"%s\" already exists in target kubeconfig", k) 114 } 115 existingKubeConfig.Clusters[k] = v 116 } 117 118 for k, v := range kubeConfig.AuthInfos { 119 if _, ok := existingKubeConfig.AuthInfos[k]; ok && !options.UpdateExisting { 120 return fmt.Errorf("user '%s' already exists in target KubeConfig", k) 121 } 122 existingKubeConfig.AuthInfos[k] = v 123 } 124 125 for k, v := range kubeConfig.Contexts { 126 if _, ok := existingKubeConfig.Contexts[k]; ok && !options.UpdateExisting { 127 return fmt.Errorf("context '%s' already exists in target KubeConfig", k) 128 } 129 existingKubeConfig.Contexts[k] = v 130 } 131 132 // set current context if it is not set, or we want to update it 133 if existingKubeConfig.CurrentContext == "" || options.UpdateCurrentContext { 134 klog.V(1).Infof("Setting new current-context '%s'", kubeConfig.CurrentContext) 135 existingKubeConfig.CurrentContext = kubeConfig.CurrentContext 136 } 137 138 return kubeConfigAtomicWrite(existingKubeConfig, output) 139 } 140 141 // kubeConfigWriteToPath takes a kubeconfig and writes it to some path, which can be '-' for os.Stdout 142 func kubeConfigWriteToPath(kubeconfig *clientcmdapi.Config, path string) error { 143 var output *os.File 144 defer output.Close() 145 var err error 146 147 if path == "-" { 148 output = os.Stdout 149 } else { 150 output, err = os.Create(path) 151 if err != nil { 152 return fmt.Errorf("failed to create file '%s': %w", path, err) 153 } 154 defer output.Close() 155 } 156 157 kubeconfigBytes, err := clientcmd.Write(*kubeconfig) 158 if err != nil { 159 return fmt.Errorf("failed to write kubeconfig: %w", err) 160 } 161 162 _, err = output.Write(kubeconfigBytes) 163 if err != nil { 164 return fmt.Errorf("failed to write file '%s': %w", output.Name(), err) 165 } 166 167 klog.V(1).Infof("Wrote kubeconfig to '%s'", output.Name()) 168 169 return nil 170 } 171 172 // kubeConfigWrite writes a kubeconfig to a path atomically 173 func kubeConfigAtomicWrite(kubeconfig *clientcmdapi.Config, path string) error { 174 tempPath := fmt.Sprintf("%s.kb_playground_%s", path, time.Now().Format("20060102_150405.000000")) 175 if err := clientcmd.WriteToFile(*kubeconfig, tempPath); err != nil { 176 return fmt.Errorf("failed to write merged kubeconfig to temporary file '%s': %w", tempPath, err) 177 } 178 179 // Move temporary file over existing KubeConfig 180 if err := os.Rename(tempPath, path); err != nil { 181 return fmt.Errorf("failed to overwrite existing KubeConfig '%s' with new kubeconfig '%s': %w", path, tempPath, err) 182 } 183 184 klog.V(1).Infof("Wrote kubeconfig to '%s'", path) 185 186 return nil 187 } 188 189 // kubeConfigRemove removes the specified kubeconfig from the specified cfgPath path 190 func kubeConfigRemove(kubeConfigStr string, cfgPath string) error { 191 // convert the kubeconfig string to a kubeconfig object 192 kubeConfig, err := clientcmd.Load([]byte(kubeConfigStr)) 193 if err != nil { 194 return fmt.Errorf("failed to load kubeconfig: %w", err) 195 } 196 197 // get the existing kubeconfig 198 existingKubeConfig, err := clientcmd.LoadFromFile(cfgPath) 199 if err != nil { 200 return err 201 } 202 203 for k := range kubeConfig.Clusters { 204 delete(existingKubeConfig.Clusters, k) 205 } 206 207 for k := range kubeConfig.AuthInfos { 208 delete(existingKubeConfig.AuthInfos, k) 209 } 210 211 for k := range kubeConfig.Contexts { 212 delete(existingKubeConfig.Contexts, k) 213 } 214 215 return kubeConfigAtomicWrite(existingKubeConfig, cfgPath) 216 } 217 218 func kubeConfigCurrentContext(kubeConfigStr string) (string, error) { 219 kubeConfig, err := clientcmd.Load([]byte(kubeConfigStr)) 220 if err != nil { 221 return "", fmt.Errorf("failed to load kubeconfig: %w", err) 222 } 223 return kubeConfig.CurrentContext, nil 224 } 225 226 func kubeConfigCurrentContextFromFile(kubeConfigPath string) (string, error) { 227 kubeConfig, err := clientcmd.LoadFromFile(kubeConfigPath) 228 if err != nil { 229 return "", fmt.Errorf("failed to load kubeconfig: %w", err) 230 } 231 return kubeConfig.CurrentContext, nil 232 }