github.com/argoproj/argo-cd@v1.8.7/cmd/argocd-util/commands/argocd_util.go (about) 1 package commands 2 3 import ( 4 "bufio" 5 "context" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "os/exec" 11 "reflect" 12 "syscall" 13 14 "github.com/argoproj/gitops-engine/pkg/utils/kube" 15 "github.com/ghodss/yaml" 16 log "github.com/sirupsen/logrus" 17 "github.com/spf13/cobra" 18 apiv1 "k8s.io/api/core/v1" 19 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 21 "k8s.io/apimachinery/pkg/runtime" 22 "k8s.io/apimachinery/pkg/runtime/schema" 23 "k8s.io/client-go/dynamic" 24 "k8s.io/client-go/kubernetes" 25 "k8s.io/client-go/rest" 26 "k8s.io/client-go/tools/clientcmd" 27 28 "github.com/argoproj/argo-cd/common" 29 "github.com/argoproj/argo-cd/util/cli" 30 "github.com/argoproj/argo-cd/util/db" 31 "github.com/argoproj/argo-cd/util/dex" 32 "github.com/argoproj/argo-cd/util/errors" 33 "github.com/argoproj/argo-cd/util/settings" 34 ) 35 36 const ( 37 // CLIName is the name of the CLI 38 cliName = "argocd-util" 39 // YamlSeparator separates sections of a YAML file 40 yamlSeparator = "---\n" 41 ) 42 43 var ( 44 configMapResource = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"} 45 secretResource = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "secrets"} 46 applicationsResource = schema.GroupVersionResource{Group: "argoproj.io", Version: "v1alpha1", Resource: "applications"} 47 appprojectsResource = schema.GroupVersionResource{Group: "argoproj.io", Version: "v1alpha1", Resource: "appprojects"} 48 ) 49 50 // NewCommand returns a new instance of an argocd command 51 func NewCommand() *cobra.Command { 52 var ( 53 logFormat string 54 logLevel string 55 ) 56 57 var command = &cobra.Command{ 58 Use: cliName, 59 Short: "argocd-util tools used by Argo CD", 60 Long: "argocd-util has internal utility tools used by Argo CD", 61 DisableAutoGenTag: true, 62 Run: func(c *cobra.Command, args []string) { 63 c.HelpFunc()(c, args) 64 }, 65 } 66 67 command.AddCommand(cli.NewVersionCmd(cliName)) 68 command.AddCommand(NewRunDexCommand()) 69 command.AddCommand(NewGenDexConfigCommand()) 70 command.AddCommand(NewImportCommand()) 71 command.AddCommand(NewExportCommand()) 72 command.AddCommand(NewClusterConfig()) 73 command.AddCommand(NewProjectsCommand()) 74 command.AddCommand(NewSettingsCommand()) 75 command.AddCommand(NewAppsCommand()) 76 77 command.Flags().StringVar(&logFormat, "logformat", "text", "Set the logging format. One of: text|json") 78 command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error") 79 return command 80 } 81 82 func NewRunDexCommand() *cobra.Command { 83 var ( 84 clientConfig clientcmd.ClientConfig 85 ) 86 var command = cobra.Command{ 87 Use: "rundex", 88 Short: "Runs dex generating a config using settings from the Argo CD configmap and secret", 89 RunE: func(c *cobra.Command, args []string) error { 90 _, err := exec.LookPath("dex") 91 errors.CheckError(err) 92 config, err := clientConfig.ClientConfig() 93 errors.CheckError(err) 94 namespace, _, err := clientConfig.Namespace() 95 errors.CheckError(err) 96 kubeClientset := kubernetes.NewForConfigOrDie(config) 97 settingsMgr := settings.NewSettingsManager(context.Background(), kubeClientset, namespace) 98 prevSettings, err := settingsMgr.GetSettings() 99 errors.CheckError(err) 100 updateCh := make(chan *settings.ArgoCDSettings, 1) 101 settingsMgr.Subscribe(updateCh) 102 103 for { 104 var cmd *exec.Cmd 105 dexCfgBytes, err := dex.GenerateDexConfigYAML(prevSettings) 106 errors.CheckError(err) 107 if len(dexCfgBytes) == 0 { 108 log.Infof("dex is not configured") 109 } else { 110 err = ioutil.WriteFile("/tmp/dex.yaml", dexCfgBytes, 0644) 111 errors.CheckError(err) 112 log.Debug(redactor(string(dexCfgBytes))) 113 cmd = exec.Command("dex", "serve", "/tmp/dex.yaml") 114 cmd.Stdout = os.Stdout 115 cmd.Stderr = os.Stderr 116 err = cmd.Start() 117 errors.CheckError(err) 118 } 119 120 // loop until the dex config changes 121 for { 122 newSettings := <-updateCh 123 newDexCfgBytes, err := dex.GenerateDexConfigYAML(newSettings) 124 errors.CheckError(err) 125 if string(newDexCfgBytes) != string(dexCfgBytes) { 126 prevSettings = newSettings 127 log.Infof("dex config modified. restarting dex") 128 if cmd != nil && cmd.Process != nil { 129 err = cmd.Process.Signal(syscall.SIGTERM) 130 errors.CheckError(err) 131 _, err = cmd.Process.Wait() 132 errors.CheckError(err) 133 } 134 break 135 } else { 136 log.Infof("dex config unmodified") 137 } 138 } 139 } 140 }, 141 } 142 143 clientConfig = cli.AddKubectlFlagsToCmd(&command) 144 return &command 145 } 146 147 func NewGenDexConfigCommand() *cobra.Command { 148 var ( 149 clientConfig clientcmd.ClientConfig 150 out string 151 ) 152 var command = cobra.Command{ 153 Use: "gendexcfg", 154 Short: "Generates a dex config from Argo CD settings", 155 RunE: func(c *cobra.Command, args []string) error { 156 config, err := clientConfig.ClientConfig() 157 errors.CheckError(err) 158 namespace, _, err := clientConfig.Namespace() 159 errors.CheckError(err) 160 kubeClientset := kubernetes.NewForConfigOrDie(config) 161 settingsMgr := settings.NewSettingsManager(context.Background(), kubeClientset, namespace) 162 settings, err := settingsMgr.GetSettings() 163 errors.CheckError(err) 164 dexCfgBytes, err := dex.GenerateDexConfigYAML(settings) 165 errors.CheckError(err) 166 if len(dexCfgBytes) == 0 { 167 log.Infof("dex is not configured") 168 return nil 169 } 170 if out == "" { 171 dexCfg := make(map[string]interface{}) 172 err := yaml.Unmarshal(dexCfgBytes, &dexCfg) 173 errors.CheckError(err) 174 if staticClientsInterface, ok := dexCfg["staticClients"]; ok { 175 if staticClients, ok := staticClientsInterface.([]interface{}); ok { 176 for i := range staticClients { 177 staticClient := staticClients[i] 178 if mappings, ok := staticClient.(map[string]interface{}); ok { 179 for key := range mappings { 180 if key == "secret" { 181 mappings[key] = "******" 182 } 183 } 184 staticClients[i] = mappings 185 } 186 } 187 dexCfg["staticClients"] = staticClients 188 } 189 } 190 errors.CheckError(err) 191 maskedDexCfgBytes, err := yaml.Marshal(dexCfg) 192 errors.CheckError(err) 193 fmt.Print(string(maskedDexCfgBytes)) 194 } else { 195 err = ioutil.WriteFile(out, dexCfgBytes, 0644) 196 errors.CheckError(err) 197 } 198 return nil 199 }, 200 } 201 202 clientConfig = cli.AddKubectlFlagsToCmd(&command) 203 command.Flags().StringVarP(&out, "out", "o", "", "Output to the specified file instead of stdout") 204 return &command 205 } 206 207 // NewImportCommand defines a new command for exporting Kubernetes and Argo CD resources. 208 func NewImportCommand() *cobra.Command { 209 var ( 210 clientConfig clientcmd.ClientConfig 211 prune bool 212 dryRun bool 213 ) 214 var command = cobra.Command{ 215 Use: "import SOURCE", 216 Short: "Import Argo CD data from stdin (specify `-') or a file", 217 Run: func(c *cobra.Command, args []string) { 218 if len(args) != 1 { 219 c.HelpFunc()(c, args) 220 os.Exit(1) 221 } 222 config, err := clientConfig.ClientConfig() 223 errors.CheckError(err) 224 config.QPS = 100 225 config.Burst = 50 226 errors.CheckError(err) 227 namespace, _, err := clientConfig.Namespace() 228 errors.CheckError(err) 229 acdClients := newArgoCDClientsets(config, namespace) 230 231 var input []byte 232 if in := args[0]; in == "-" { 233 input, err = ioutil.ReadAll(os.Stdin) 234 } else { 235 input, err = ioutil.ReadFile(in) 236 } 237 errors.CheckError(err) 238 var dryRunMsg string 239 if dryRun { 240 dryRunMsg = " (dry run)" 241 } 242 243 // pruneObjects tracks live objects and it's current resource version. any remaining 244 // items in this map indicates the resource should be pruned since it no longer appears 245 // in the backup 246 pruneObjects := make(map[kube.ResourceKey]unstructured.Unstructured) 247 configMaps, err := acdClients.configMaps.List(context.Background(), metav1.ListOptions{}) 248 errors.CheckError(err) 249 // referencedSecrets holds any secrets referenced in the argocd-cm configmap. These 250 // secrets need to be imported too 251 var referencedSecrets map[string]bool 252 for _, cm := range configMaps.Items { 253 if isArgoCDConfigMap(cm.GetName()) { 254 pruneObjects[kube.ResourceKey{Group: "", Kind: "ConfigMap", Name: cm.GetName()}] = cm 255 } 256 if cm.GetName() == common.ArgoCDConfigMapName { 257 referencedSecrets = getReferencedSecrets(cm) 258 } 259 } 260 261 secrets, err := acdClients.secrets.List(context.Background(), metav1.ListOptions{}) 262 errors.CheckError(err) 263 for _, secret := range secrets.Items { 264 if isArgoCDSecret(referencedSecrets, secret) { 265 pruneObjects[kube.ResourceKey{Group: "", Kind: "Secret", Name: secret.GetName()}] = secret 266 } 267 } 268 applications, err := acdClients.applications.List(context.Background(), metav1.ListOptions{}) 269 errors.CheckError(err) 270 for _, app := range applications.Items { 271 pruneObjects[kube.ResourceKey{Group: "argoproj.io", Kind: "Application", Name: app.GetName()}] = app 272 } 273 projects, err := acdClients.projects.List(context.Background(), metav1.ListOptions{}) 274 errors.CheckError(err) 275 for _, proj := range projects.Items { 276 pruneObjects[kube.ResourceKey{Group: "argoproj.io", Kind: "AppProject", Name: proj.GetName()}] = proj 277 } 278 279 // Create or replace existing object 280 backupObjects, err := kube.SplitYAML(input) 281 errors.CheckError(err) 282 for _, bakObj := range backupObjects { 283 gvk := bakObj.GroupVersionKind() 284 key := kube.ResourceKey{Group: gvk.Group, Kind: gvk.Kind, Name: bakObj.GetName()} 285 liveObj, exists := pruneObjects[key] 286 delete(pruneObjects, key) 287 var dynClient dynamic.ResourceInterface 288 switch bakObj.GetKind() { 289 case "Secret": 290 dynClient = acdClients.secrets 291 case "ConfigMap": 292 dynClient = acdClients.configMaps 293 case "AppProject": 294 dynClient = acdClients.projects 295 case "Application": 296 dynClient = acdClients.applications 297 } 298 if !exists { 299 if !dryRun { 300 _, err = dynClient.Create(context.Background(), bakObj, metav1.CreateOptions{}) 301 errors.CheckError(err) 302 } 303 fmt.Printf("%s/%s %s created%s\n", gvk.Group, gvk.Kind, bakObj.GetName(), dryRunMsg) 304 } else if specsEqual(*bakObj, liveObj) { 305 fmt.Printf("%s/%s %s unchanged%s\n", gvk.Group, gvk.Kind, bakObj.GetName(), dryRunMsg) 306 } else { 307 if !dryRun { 308 newLive := updateLive(bakObj, &liveObj) 309 _, err = dynClient.Update(context.Background(), newLive, metav1.UpdateOptions{}) 310 errors.CheckError(err) 311 } 312 fmt.Printf("%s/%s %s updated%s\n", gvk.Group, gvk.Kind, bakObj.GetName(), dryRunMsg) 313 } 314 } 315 316 // Delete objects not in backup 317 for key := range pruneObjects { 318 if prune { 319 var dynClient dynamic.ResourceInterface 320 switch key.Kind { 321 case "Secret": 322 dynClient = acdClients.secrets 323 case "AppProject": 324 dynClient = acdClients.projects 325 case "Application": 326 dynClient = acdClients.applications 327 default: 328 log.Fatalf("Unexpected kind '%s' in prune list", key.Kind) 329 } 330 if !dryRun { 331 err = dynClient.Delete(context.Background(), key.Name, metav1.DeleteOptions{}) 332 errors.CheckError(err) 333 } 334 fmt.Printf("%s/%s %s pruned%s\n", key.Group, key.Kind, key.Name, dryRunMsg) 335 } else { 336 fmt.Printf("%s/%s %s needs pruning\n", key.Group, key.Kind, key.Name) 337 } 338 } 339 }, 340 } 341 342 clientConfig = cli.AddKubectlFlagsToCmd(&command) 343 command.Flags().BoolVar(&dryRun, "dry-run", false, "Print what will be performed") 344 command.Flags().BoolVar(&prune, "prune", false, "Prune secrets, applications and projects which do not appear in the backup") 345 346 return &command 347 } 348 349 type argoCDClientsets struct { 350 configMaps dynamic.ResourceInterface 351 secrets dynamic.ResourceInterface 352 applications dynamic.ResourceInterface 353 projects dynamic.ResourceInterface 354 } 355 356 func newArgoCDClientsets(config *rest.Config, namespace string) *argoCDClientsets { 357 dynamicIf, err := dynamic.NewForConfig(config) 358 errors.CheckError(err) 359 return &argoCDClientsets{ 360 configMaps: dynamicIf.Resource(configMapResource).Namespace(namespace), 361 secrets: dynamicIf.Resource(secretResource).Namespace(namespace), 362 applications: dynamicIf.Resource(applicationsResource).Namespace(namespace), 363 projects: dynamicIf.Resource(appprojectsResource).Namespace(namespace), 364 } 365 } 366 367 // NewExportCommand defines a new command for exporting Kubernetes and Argo CD resources. 368 func NewExportCommand() *cobra.Command { 369 var ( 370 clientConfig clientcmd.ClientConfig 371 out string 372 ) 373 var command = cobra.Command{ 374 Use: "export", 375 Short: "Export all Argo CD data to stdout (default) or a file", 376 Run: func(c *cobra.Command, args []string) { 377 config, err := clientConfig.ClientConfig() 378 errors.CheckError(err) 379 namespace, _, err := clientConfig.Namespace() 380 errors.CheckError(err) 381 382 var writer io.Writer 383 if out == "-" { 384 writer = os.Stdout 385 } else { 386 f, err := os.Create(out) 387 errors.CheckError(err) 388 bw := bufio.NewWriter(f) 389 writer = bw 390 defer func() { 391 err = bw.Flush() 392 errors.CheckError(err) 393 err = f.Close() 394 errors.CheckError(err) 395 }() 396 } 397 398 acdClients := newArgoCDClientsets(config, namespace) 399 acdConfigMap, err := acdClients.configMaps.Get(context.Background(), common.ArgoCDConfigMapName, metav1.GetOptions{}) 400 errors.CheckError(err) 401 export(writer, *acdConfigMap) 402 acdRBACConfigMap, err := acdClients.configMaps.Get(context.Background(), common.ArgoCDRBACConfigMapName, metav1.GetOptions{}) 403 errors.CheckError(err) 404 export(writer, *acdRBACConfigMap) 405 acdKnownHostsConfigMap, err := acdClients.configMaps.Get(context.Background(), common.ArgoCDKnownHostsConfigMapName, metav1.GetOptions{}) 406 errors.CheckError(err) 407 export(writer, *acdKnownHostsConfigMap) 408 acdTLSCertsConfigMap, err := acdClients.configMaps.Get(context.Background(), common.ArgoCDTLSCertsConfigMapName, metav1.GetOptions{}) 409 errors.CheckError(err) 410 export(writer, *acdTLSCertsConfigMap) 411 412 referencedSecrets := getReferencedSecrets(*acdConfigMap) 413 secrets, err := acdClients.secrets.List(context.Background(), metav1.ListOptions{}) 414 errors.CheckError(err) 415 for _, secret := range secrets.Items { 416 if isArgoCDSecret(referencedSecrets, secret) { 417 export(writer, secret) 418 } 419 } 420 projects, err := acdClients.projects.List(context.Background(), metav1.ListOptions{}) 421 errors.CheckError(err) 422 for _, proj := range projects.Items { 423 export(writer, proj) 424 } 425 applications, err := acdClients.applications.List(context.Background(), metav1.ListOptions{}) 426 errors.CheckError(err) 427 for _, app := range applications.Items { 428 export(writer, app) 429 } 430 }, 431 } 432 433 clientConfig = cli.AddKubectlFlagsToCmd(&command) 434 command.Flags().StringVarP(&out, "out", "o", "-", "Output to the specified file instead of stdout") 435 436 return &command 437 } 438 439 // getReferencedSecrets examines the argocd-cm config for any referenced repo secrets and returns a 440 // map of all referenced secrets. 441 func getReferencedSecrets(un unstructured.Unstructured) map[string]bool { 442 var cm apiv1.ConfigMap 443 err := runtime.DefaultUnstructuredConverter.FromUnstructured(un.Object, &cm) 444 errors.CheckError(err) 445 referencedSecrets := make(map[string]bool) 446 447 // Referenced repository secrets 448 if reposRAW, ok := cm.Data["repositories"]; ok { 449 repos := make([]settings.Repository, 0) 450 err := yaml.Unmarshal([]byte(reposRAW), &repos) 451 errors.CheckError(err) 452 for _, cred := range repos { 453 if cred.PasswordSecret != nil { 454 referencedSecrets[cred.PasswordSecret.Name] = true 455 } 456 if cred.SSHPrivateKeySecret != nil { 457 referencedSecrets[cred.SSHPrivateKeySecret.Name] = true 458 } 459 if cred.UsernameSecret != nil { 460 referencedSecrets[cred.UsernameSecret.Name] = true 461 } 462 if cred.TLSClientCertDataSecret != nil { 463 referencedSecrets[cred.TLSClientCertDataSecret.Name] = true 464 } 465 if cred.TLSClientCertKeySecret != nil { 466 referencedSecrets[cred.TLSClientCertKeySecret.Name] = true 467 } 468 } 469 } 470 471 // Referenced repository credentials secrets 472 if reposRAW, ok := cm.Data["repository.credentials"]; ok { 473 creds := make([]settings.RepositoryCredentials, 0) 474 err := yaml.Unmarshal([]byte(reposRAW), &creds) 475 errors.CheckError(err) 476 for _, cred := range creds { 477 if cred.PasswordSecret != nil { 478 referencedSecrets[cred.PasswordSecret.Name] = true 479 } 480 if cred.SSHPrivateKeySecret != nil { 481 referencedSecrets[cred.SSHPrivateKeySecret.Name] = true 482 } 483 if cred.UsernameSecret != nil { 484 referencedSecrets[cred.UsernameSecret.Name] = true 485 } 486 if cred.TLSClientCertDataSecret != nil { 487 referencedSecrets[cred.TLSClientCertDataSecret.Name] = true 488 } 489 if cred.TLSClientCertKeySecret != nil { 490 referencedSecrets[cred.TLSClientCertKeySecret.Name] = true 491 } 492 } 493 } 494 return referencedSecrets 495 } 496 497 // isArgoCDSecret returns whether or not the given secret is a part of Argo CD configuration 498 // (e.g. argocd-secret, repo credentials, or cluster credentials) 499 func isArgoCDSecret(repoSecretRefs map[string]bool, un unstructured.Unstructured) bool { 500 secretName := un.GetName() 501 if secretName == common.ArgoCDSecretName { 502 return true 503 } 504 if repoSecretRefs != nil { 505 if _, ok := repoSecretRefs[secretName]; ok { 506 return true 507 } 508 } 509 if labels := un.GetLabels(); labels != nil { 510 if _, ok := labels[common.LabelKeySecretType]; ok { 511 return true 512 } 513 } 514 if annotations := un.GetAnnotations(); annotations != nil { 515 if annotations[common.AnnotationKeyManagedBy] == common.AnnotationValueManagedByArgoCD { 516 return true 517 } 518 } 519 return false 520 } 521 522 // isArgoCDConfigMap returns true if the configmap name is one of argo cd's well known configmaps 523 func isArgoCDConfigMap(name string) bool { 524 switch name { 525 case common.ArgoCDConfigMapName, common.ArgoCDRBACConfigMapName, common.ArgoCDKnownHostsConfigMapName, common.ArgoCDTLSCertsConfigMapName: 526 return true 527 } 528 return false 529 530 } 531 532 // specsEqual returns if the spec, data, labels, annotations, and finalizers of the two 533 // supplied objects are equal, indicating that no update is necessary during importing 534 func specsEqual(left, right unstructured.Unstructured) bool { 535 if !reflect.DeepEqual(left.GetAnnotations(), right.GetAnnotations()) { 536 return false 537 } 538 if !reflect.DeepEqual(left.GetLabels(), right.GetLabels()) { 539 return false 540 } 541 if !reflect.DeepEqual(left.GetFinalizers(), right.GetFinalizers()) { 542 return false 543 } 544 switch left.GetKind() { 545 case "Secret", "ConfigMap": 546 leftData, _, _ := unstructured.NestedMap(left.Object, "data") 547 rightData, _, _ := unstructured.NestedMap(right.Object, "data") 548 return reflect.DeepEqual(leftData, rightData) 549 case "AppProject": 550 leftSpec, _, _ := unstructured.NestedMap(left.Object, "spec") 551 rightSpec, _, _ := unstructured.NestedMap(right.Object, "spec") 552 return reflect.DeepEqual(leftSpec, rightSpec) 553 case "Application": 554 leftSpec, _, _ := unstructured.NestedMap(left.Object, "spec") 555 rightSpec, _, _ := unstructured.NestedMap(right.Object, "spec") 556 leftStatus, _, _ := unstructured.NestedMap(left.Object, "status") 557 rightStatus, _, _ := unstructured.NestedMap(right.Object, "status") 558 // reconciledAt and observedAt are constantly changing and we ignore any diff there 559 delete(leftStatus, "reconciledAt") 560 delete(rightStatus, "reconciledAt") 561 delete(leftStatus, "observedAt") 562 delete(rightStatus, "observedAt") 563 return reflect.DeepEqual(leftSpec, rightSpec) && reflect.DeepEqual(leftStatus, rightStatus) 564 } 565 return false 566 } 567 568 // updateLive replaces the live object's finalizers, spec, annotations, labels, and data from the 569 // backup object but leaves all other fields intact (status, other metadata, etc...) 570 func updateLive(bak, live *unstructured.Unstructured) *unstructured.Unstructured { 571 newLive := live.DeepCopy() 572 newLive.SetAnnotations(bak.GetAnnotations()) 573 newLive.SetLabels(bak.GetLabels()) 574 newLive.SetFinalizers(bak.GetFinalizers()) 575 switch live.GetKind() { 576 case "Secret", "ConfigMap": 577 newLive.Object["data"] = bak.Object["data"] 578 case "AppProject": 579 newLive.Object["spec"] = bak.Object["spec"] 580 case "Application": 581 newLive.Object["spec"] = bak.Object["spec"] 582 if _, ok := bak.Object["status"]; ok { 583 newLive.Object["status"] = bak.Object["status"] 584 } 585 } 586 return newLive 587 } 588 589 // export writes the unstructured object and removes extraneous cruft from output before writing 590 func export(w io.Writer, un unstructured.Unstructured) { 591 name := un.GetName() 592 finalizers := un.GetFinalizers() 593 apiVersion := un.GetAPIVersion() 594 kind := un.GetKind() 595 labels := un.GetLabels() 596 annotations := un.GetAnnotations() 597 unstructured.RemoveNestedField(un.Object, "metadata") 598 un.SetName(name) 599 un.SetFinalizers(finalizers) 600 un.SetAPIVersion(apiVersion) 601 un.SetKind(kind) 602 un.SetLabels(labels) 603 un.SetAnnotations(annotations) 604 data, err := yaml.Marshal(un.Object) 605 errors.CheckError(err) 606 _, err = w.Write(data) 607 errors.CheckError(err) 608 _, err = w.Write([]byte(yamlSeparator)) 609 errors.CheckError(err) 610 } 611 612 // NewClusterConfig returns a new instance of `argocd-util kubeconfig` command 613 func NewClusterConfig() *cobra.Command { 614 var ( 615 clientConfig clientcmd.ClientConfig 616 ) 617 var command = &cobra.Command{ 618 Use: "kubeconfig CLUSTER_URL OUTPUT_PATH", 619 Short: "Generates kubeconfig for the specified cluster", 620 DisableAutoGenTag: true, 621 Run: func(c *cobra.Command, args []string) { 622 if len(args) != 2 { 623 c.HelpFunc()(c, args) 624 os.Exit(1) 625 } 626 serverUrl := args[0] 627 output := args[1] 628 conf, err := clientConfig.ClientConfig() 629 errors.CheckError(err) 630 namespace, _, err := clientConfig.Namespace() 631 errors.CheckError(err) 632 kubeclientset, err := kubernetes.NewForConfig(conf) 633 errors.CheckError(err) 634 635 cluster, err := db.NewDB(namespace, settings.NewSettingsManager(context.Background(), kubeclientset, namespace), kubeclientset).GetCluster(context.Background(), serverUrl) 636 errors.CheckError(err) 637 err = kube.WriteKubeConfig(cluster.RawRestConfig(), namespace, output) 638 errors.CheckError(err) 639 }, 640 } 641 clientConfig = cli.AddKubectlFlagsToCmd(command) 642 return command 643 } 644 645 func iterateStringFields(obj interface{}, callback func(name string, val string) string) { 646 if mapField, ok := obj.(map[string]interface{}); ok { 647 for field, val := range mapField { 648 if strVal, ok := val.(string); ok { 649 mapField[field] = callback(field, strVal) 650 } else { 651 iterateStringFields(val, callback) 652 } 653 } 654 } else if arrayField, ok := obj.([]interface{}); ok { 655 for i := range arrayField { 656 iterateStringFields(arrayField[i], callback) 657 } 658 } 659 } 660 661 func redactor(dirtyString string) string { 662 config := make(map[string]interface{}) 663 err := yaml.Unmarshal([]byte(dirtyString), &config) 664 errors.CheckError(err) 665 iterateStringFields(config, func(name string, val string) string { 666 if name == "clientSecret" || name == "secret" || name == "bindPW" { 667 return "********" 668 } else { 669 return val 670 } 671 }) 672 data, err := yaml.Marshal(config) 673 errors.CheckError(err) 674 return string(data) 675 }