github.com/argoproj/argo-cd@v1.8.7/cmd/argocd-util/commands/settings.go (about) 1 package commands 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "reflect" 10 "sort" 11 "strconv" 12 "strings" 13 "text/tabwriter" 14 15 healthutil "github.com/argoproj/gitops-engine/pkg/health" 16 "github.com/ghodss/yaml" 17 log "github.com/sirupsen/logrus" 18 "github.com/spf13/cobra" 19 corev1 "k8s.io/api/core/v1" 20 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 22 "k8s.io/client-go/kubernetes" 23 "k8s.io/client-go/kubernetes/fake" 24 "k8s.io/client-go/tools/clientcmd" 25 26 "github.com/argoproj/argo-cd/common" 27 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" 28 "github.com/argoproj/argo-cd/util/argo/normalizers" 29 "github.com/argoproj/argo-cd/util/cli" 30 "github.com/argoproj/argo-cd/util/errors" 31 "github.com/argoproj/argo-cd/util/lua" 32 "github.com/argoproj/argo-cd/util/settings" 33 ) 34 35 type settingsOpts struct { 36 argocdCMPath string 37 argocdSecretPath string 38 loadClusterSettings bool 39 clientConfig clientcmd.ClientConfig 40 } 41 42 type commandContext interface { 43 createSettingsManager() (*settings.SettingsManager, error) 44 } 45 46 func collectLogs(callback func()) string { 47 log.SetLevel(log.DebugLevel) 48 out := bytes.Buffer{} 49 log.SetOutput(&out) 50 defer log.SetLevel(log.FatalLevel) 51 callback() 52 return out.String() 53 } 54 55 func setSettingsMeta(obj v1.Object) { 56 obj.SetNamespace("default") 57 labels := obj.GetLabels() 58 if labels == nil { 59 labels = make(map[string]string) 60 } 61 labels["app.kubernetes.io/part-of"] = "argocd" 62 obj.SetLabels(labels) 63 } 64 65 func (opts *settingsOpts) createSettingsManager() (*settings.SettingsManager, error) { 66 var argocdCM *corev1.ConfigMap 67 if opts.argocdCMPath == "" && !opts.loadClusterSettings { 68 return nil, fmt.Errorf("either --argocd-cm-path must be provided or --load-cluster-settings must be set to true") 69 } else if opts.argocdCMPath == "" { 70 realClientset, ns, err := opts.getK8sClient() 71 if err != nil { 72 return nil, err 73 } 74 75 argocdCM, err = realClientset.CoreV1().ConfigMaps(ns).Get(context.Background(), common.ArgoCDConfigMapName, v1.GetOptions{}) 76 if err != nil { 77 return nil, err 78 } 79 } else { 80 data, err := ioutil.ReadFile(opts.argocdCMPath) 81 if err != nil { 82 return nil, err 83 } 84 err = yaml.Unmarshal(data, &argocdCM) 85 if err != nil { 86 return nil, err 87 } 88 } 89 setSettingsMeta(argocdCM) 90 91 var argocdSecret *corev1.Secret 92 if opts.argocdSecretPath != "" { 93 data, err := ioutil.ReadFile(opts.argocdSecretPath) 94 if err != nil { 95 return nil, err 96 } 97 err = yaml.Unmarshal(data, &argocdSecret) 98 if err != nil { 99 return nil, err 100 } 101 setSettingsMeta(argocdSecret) 102 } else if opts.loadClusterSettings { 103 realClientset, ns, err := opts.getK8sClient() 104 if err != nil { 105 return nil, err 106 } 107 argocdSecret, err = realClientset.CoreV1().Secrets(ns).Get(context.Background(), common.ArgoCDSecretName, v1.GetOptions{}) 108 if err != nil { 109 return nil, err 110 } 111 } else { 112 argocdSecret = &corev1.Secret{ 113 ObjectMeta: v1.ObjectMeta{ 114 Name: common.ArgoCDSecretName, 115 }, 116 Data: map[string][]byte{ 117 "admin.password": []byte("test"), 118 "server.secretkey": []byte("test"), 119 }, 120 } 121 } 122 setSettingsMeta(argocdSecret) 123 clientset := fake.NewSimpleClientset(argocdSecret, argocdCM) 124 125 manager := settings.NewSettingsManager(context.Background(), clientset, "default") 126 errors.CheckError(manager.ResyncInformers()) 127 128 return manager, nil 129 } 130 131 func (opts *settingsOpts) getK8sClient() (*kubernetes.Clientset, string, error) { 132 namespace, _, err := opts.clientConfig.Namespace() 133 if err != nil { 134 return nil, "", err 135 } 136 137 restConfig, err := opts.clientConfig.ClientConfig() 138 if err != nil { 139 return nil, "", err 140 } 141 142 realClientset, err := kubernetes.NewForConfig(restConfig) 143 if err != nil { 144 return nil, "", err 145 } 146 return realClientset, namespace, nil 147 } 148 149 func NewSettingsCommand() *cobra.Command { 150 var ( 151 opts settingsOpts 152 ) 153 154 var command = &cobra.Command{ 155 Use: "settings", 156 Short: "Provides set of commands for settings validation and troubleshooting", 157 Run: func(c *cobra.Command, args []string) { 158 c.HelpFunc()(c, args) 159 }, 160 } 161 log.SetLevel(log.FatalLevel) 162 163 command.AddCommand(NewValidateSettingsCommand(&opts)) 164 command.AddCommand(NewResourceOverridesCommand(&opts)) 165 166 opts.clientConfig = cli.AddKubectlFlagsToCmd(command) 167 command.PersistentFlags().StringVar(&opts.argocdCMPath, "argocd-cm-path", "", "Path to local argocd-cm.yaml file") 168 command.PersistentFlags().StringVar(&opts.argocdSecretPath, "argocd-secret-path", "", "Path to local argocd-secret.yaml file") 169 command.PersistentFlags().BoolVar(&opts.loadClusterSettings, "load-cluster-settings", false, 170 "Indicates that config map and secret should be loaded from cluster unless local file path is provided") 171 return command 172 } 173 174 type settingValidator func(manager *settings.SettingsManager) (string, error) 175 176 func joinValidators(validators ...settingValidator) settingValidator { 177 return func(manager *settings.SettingsManager) (string, error) { 178 var errorStrs []string 179 var summaries []string 180 for i := range validators { 181 summary, err := validators[i](manager) 182 if err != nil { 183 errorStrs = append(errorStrs, err.Error()) 184 } 185 if summary != "" { 186 summaries = append(summaries, summary) 187 } 188 } 189 if len(errorStrs) > 0 { 190 return "", fmt.Errorf("%s", strings.Join(errorStrs, "\n")) 191 } 192 return strings.Join(summaries, "\n"), nil 193 } 194 } 195 196 var validatorsByGroup = map[string]settingValidator{ 197 "general": joinValidators(func(manager *settings.SettingsManager) (string, error) { 198 general, err := manager.GetSettings() 199 if err != nil { 200 return "", err 201 } 202 ssoProvider := "" 203 if general.DexConfig != "" { 204 if _, err := settings.UnmarshalDexConfig(general.DexConfig); err != nil { 205 return "", fmt.Errorf("invalid dex.config: %v", err) 206 } 207 ssoProvider = "Dex" 208 } else if general.OIDCConfigRAW != "" { 209 if _, err := settings.UnmarshalOIDCConfig(general.OIDCConfigRAW); err != nil { 210 return "", fmt.Errorf("invalid oidc.config: %v", err) 211 } 212 ssoProvider = "OIDC" 213 } 214 var summary string 215 if ssoProvider != "" { 216 summary = fmt.Sprintf("%s is configured", ssoProvider) 217 if general.URL == "" { 218 summary = summary + " ('url' field is missing)" 219 } 220 } else if ssoProvider != "" && general.URL != "" { 221 222 } else { 223 summary = "SSO is not configured" 224 } 225 return summary, nil 226 }, func(manager *settings.SettingsManager) (string, error) { 227 _, err := manager.GetAppInstanceLabelKey() 228 return "", err 229 }, func(manager *settings.SettingsManager) (string, error) { 230 _, err := manager.GetHelp() 231 return "", err 232 }, func(manager *settings.SettingsManager) (string, error) { 233 _, err := manager.GetGoogleAnalytics() 234 return "", err 235 }), 236 "plugins": func(manager *settings.SettingsManager) (string, error) { 237 plugins, err := manager.GetConfigManagementPlugins() 238 if err != nil { 239 return "", err 240 } 241 return fmt.Sprintf("%d plugins", len(plugins)), nil 242 }, 243 "kustomize": func(manager *settings.SettingsManager) (string, error) { 244 opts, err := manager.GetKustomizeSettings() 245 if err != nil { 246 return "", err 247 } 248 summary := "default options" 249 if opts.BuildOptions != "" { 250 summary = opts.BuildOptions 251 } 252 if len(opts.Versions) > 0 { 253 summary = fmt.Sprintf("%s (%d versions)", summary, len(opts.Versions)) 254 } 255 return summary, err 256 }, 257 "repositories": joinValidators(func(manager *settings.SettingsManager) (string, error) { 258 repos, err := manager.GetRepositories() 259 if err != nil { 260 return "", err 261 } 262 return fmt.Sprintf("%d repositories", len(repos)), nil 263 }, func(manager *settings.SettingsManager) (string, error) { 264 creds, err := manager.GetRepositoryCredentials() 265 if err != nil { 266 return "", err 267 } 268 return fmt.Sprintf("%d repository credentials", len(creds)), nil 269 }), 270 "accounts": func(manager *settings.SettingsManager) (string, error) { 271 accounts, err := manager.GetAccounts() 272 if err != nil { 273 return "", err 274 } 275 return fmt.Sprintf("%d accounts", len(accounts)), nil 276 }, 277 "resource-overrides": func(manager *settings.SettingsManager) (string, error) { 278 overrides, err := manager.GetResourceOverrides() 279 if err != nil { 280 return "", err 281 } 282 return fmt.Sprintf("%d resource overrides", len(overrides)), nil 283 }, 284 } 285 286 func NewValidateSettingsCommand(cmdCtx commandContext) *cobra.Command { 287 var ( 288 groups []string 289 ) 290 291 var allGroups []string 292 for k := range validatorsByGroup { 293 allGroups = append(allGroups, k) 294 } 295 sort.Slice(allGroups, func(i, j int) bool { 296 return allGroups[i] < allGroups[j] 297 }) 298 299 var command = &cobra.Command{ 300 Use: "validate", 301 Short: "Validate settings", 302 Long: "Validates settings specified in 'argocd-cm' ConfigMap and 'argocd-secret' Secret", 303 Example: ` 304 #Validates all settings in the specified YAML file 305 argocd-util settings validate --argocd-cm-path ./argocd-cm.yaml 306 307 #Validates accounts and plugins settings in Kubernetes cluster of current kubeconfig context 308 argocd-util settings validate --group accounts --group plugins --load-cluster-settings`, 309 Run: func(c *cobra.Command, args []string) { 310 settingsManager, err := cmdCtx.createSettingsManager() 311 errors.CheckError(err) 312 313 if len(groups) == 0 { 314 groups = allGroups 315 } 316 for i, group := range groups { 317 validator := validatorsByGroup[group] 318 319 logs := collectLogs(func() { 320 summary, err := validator(settingsManager) 321 322 if err != nil { 323 _, _ = fmt.Fprintf(os.Stdout, "❌ %s\n", group) 324 _, _ = fmt.Fprintf(os.Stdout, "%s\n", err.Error()) 325 } else { 326 _, _ = fmt.Fprintf(os.Stdout, "✅ %s\n", group) 327 if summary != "" { 328 _, _ = fmt.Fprintf(os.Stdout, "%s\n", summary) 329 } 330 } 331 }) 332 if logs != "" { 333 _, _ = fmt.Fprintf(os.Stdout, "%s\n", logs) 334 } 335 if i != len(groups)-1 { 336 _, _ = fmt.Fprintf(os.Stdout, "\n") 337 } 338 } 339 }, 340 } 341 342 command.Flags().StringArrayVar(&groups, "group", nil, fmt.Sprintf( 343 "Optional list of setting groups that have to be validated ( one of: %s)", strings.Join(allGroups, ", "))) 344 345 return command 346 } 347 348 func NewResourceOverridesCommand(cmdCtx commandContext) *cobra.Command { 349 var command = &cobra.Command{ 350 Use: "resource-overrides", 351 Short: "Troubleshoot resource overrides", 352 Run: func(c *cobra.Command, args []string) { 353 c.HelpFunc()(c, args) 354 }, 355 } 356 command.AddCommand(NewResourceIgnoreDifferencesCommand(cmdCtx)) 357 command.AddCommand(NewResourceActionListCommand(cmdCtx)) 358 command.AddCommand(NewResourceActionRunCommand(cmdCtx)) 359 command.AddCommand(NewResourceHealthCommand(cmdCtx)) 360 return command 361 } 362 363 func executeResourceOverrideCommand(cmdCtx commandContext, args []string, callback func(res unstructured.Unstructured, override v1alpha1.ResourceOverride, overrides map[string]v1alpha1.ResourceOverride)) { 364 data, err := ioutil.ReadFile(args[0]) 365 errors.CheckError(err) 366 367 res := unstructured.Unstructured{} 368 errors.CheckError(yaml.Unmarshal(data, &res)) 369 370 settingsManager, err := cmdCtx.createSettingsManager() 371 errors.CheckError(err) 372 373 overrides, err := settingsManager.GetResourceOverrides() 374 errors.CheckError(err) 375 gvk := res.GroupVersionKind() 376 key := gvk.Kind 377 if gvk.Group != "" { 378 key = fmt.Sprintf("%s/%s", gvk.Group, gvk.Kind) 379 } 380 override, hasOverride := overrides[key] 381 if !hasOverride { 382 _, _ = fmt.Printf("No overrides configured for '%s/%s'\n", gvk.Group, gvk.Kind) 383 return 384 } 385 callback(res, override, overrides) 386 } 387 388 func NewResourceIgnoreDifferencesCommand(cmdCtx commandContext) *cobra.Command { 389 var command = &cobra.Command{ 390 Use: "ignore-differences RESOURCE_YAML_PATH", 391 Short: "Renders fields excluded from diffing", 392 Long: "Renders ignored fields using the 'ignoreDifferences' setting specified in the 'resource.customizations' field of 'argocd-cm' ConfigMap", 393 Example: ` 394 argocd-util settings resource-overrides ignore-differences ./deploy.yaml --argocd-cm-path ./argocd-cm.yaml`, 395 Run: func(c *cobra.Command, args []string) { 396 if len(args) < 1 { 397 c.HelpFunc()(c, args) 398 os.Exit(1) 399 } 400 401 executeResourceOverrideCommand(cmdCtx, args, func(res unstructured.Unstructured, override v1alpha1.ResourceOverride, overrides map[string]v1alpha1.ResourceOverride) { 402 gvk := res.GroupVersionKind() 403 if len(override.IgnoreDifferences.JSONPointers) == 0 { 404 _, _ = fmt.Printf("Ignore differences are not configured for '%s/%s'\n", gvk.Group, gvk.Kind) 405 return 406 } 407 408 normalizer, err := normalizers.NewIgnoreNormalizer(nil, overrides) 409 errors.CheckError(err) 410 411 normalizedRes := res.DeepCopy() 412 logs := collectLogs(func() { 413 errors.CheckError(normalizer.Normalize(normalizedRes)) 414 }) 415 if logs != "" { 416 _, _ = fmt.Println(logs) 417 } 418 419 if reflect.DeepEqual(&res, normalizedRes) { 420 _, _ = fmt.Printf("No fields are ignored by ignoreDifferences settings: \n%s\n", override.IgnoreDifferences) 421 return 422 } 423 424 _, _ = fmt.Printf("Following fields are ignored:\n\n") 425 _ = cli.PrintDiff(res.GetName(), &res, normalizedRes) 426 }) 427 }, 428 } 429 return command 430 } 431 432 func NewResourceHealthCommand(cmdCtx commandContext) *cobra.Command { 433 var command = &cobra.Command{ 434 Use: "health RESOURCE_YAML_PATH", 435 Short: "Assess resource health", 436 Long: "Assess resource health using the lua script configured in the 'resource.customizations' field of 'argocd-cm' ConfigMap", 437 Example: ` 438 argocd-util settings resource-overrides health ./deploy.yaml --argocd-cm-path ./argocd-cm.yaml`, 439 Run: func(c *cobra.Command, args []string) { 440 if len(args) < 1 { 441 c.HelpFunc()(c, args) 442 os.Exit(1) 443 } 444 445 executeResourceOverrideCommand(cmdCtx, args, func(res unstructured.Unstructured, override v1alpha1.ResourceOverride, overrides map[string]v1alpha1.ResourceOverride) { 446 gvk := res.GroupVersionKind() 447 if override.HealthLua == "" { 448 _, _ = fmt.Printf("Health script is not configured for '%s/%s'\n", gvk.Group, gvk.Kind) 449 return 450 } 451 452 resHealth, err := healthutil.GetResourceHealth(&res, lua.ResourceHealthOverrides(overrides)) 453 errors.CheckError(err) 454 455 _, _ = fmt.Printf("STATUS: %s\n", resHealth.Status) 456 _, _ = fmt.Printf("MESSAGE: %s\n", resHealth.Message) 457 }) 458 }, 459 } 460 return command 461 } 462 463 func NewResourceActionListCommand(cmdCtx commandContext) *cobra.Command { 464 var command = &cobra.Command{ 465 Use: "list-actions RESOURCE_YAML_PATH", 466 Short: "List available resource actions", 467 Long: "List actions available for given resource action using the lua scripts configured in the 'resource.customizations' field of 'argocd-cm' ConfigMap and outputs updated fields", 468 Example: ` 469 argocd-util settings resource-overrides action list /tmp/deploy.yaml --argocd-cm-path ./argocd-cm.yaml`, 470 Run: func(c *cobra.Command, args []string) { 471 if len(args) < 1 { 472 c.HelpFunc()(c, args) 473 os.Exit(1) 474 } 475 476 executeResourceOverrideCommand(cmdCtx, args, func(res unstructured.Unstructured, override v1alpha1.ResourceOverride, overrides map[string]v1alpha1.ResourceOverride) { 477 gvk := res.GroupVersionKind() 478 if override.Actions == "" { 479 _, _ = fmt.Printf("Actions are not configured for '%s/%s'\n", gvk.Group, gvk.Kind) 480 return 481 } 482 483 luaVM := lua.VM{ResourceOverrides: overrides} 484 discoveryScript, err := luaVM.GetResourceActionDiscovery(&res) 485 errors.CheckError(err) 486 487 availableActions, err := luaVM.ExecuteResourceActionDiscovery(&res, discoveryScript) 488 errors.CheckError(err) 489 sort.Slice(availableActions, func(i, j int) bool { 490 return availableActions[i].Name < availableActions[j].Name 491 }) 492 493 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 494 _, _ = fmt.Fprintf(w, "NAME\tENABLED\n") 495 for _, action := range availableActions { 496 _, _ = fmt.Fprintf(w, "%s\t%s\n", action.Name, strconv.FormatBool(action.Disabled)) 497 } 498 _ = w.Flush() 499 }) 500 }, 501 } 502 return command 503 } 504 505 func NewResourceActionRunCommand(cmdCtx commandContext) *cobra.Command { 506 var command = &cobra.Command{ 507 Use: "run-action RESOURCE_YAML_PATH ACTION", 508 Aliases: []string{"action"}, 509 Short: "Executes resource action", 510 Long: "Executes resource action using the lua script configured in the 'resource.customizations' field of 'argocd-cm' ConfigMap and outputs updated fields", 511 Example: ` 512 argocd-util settings resource-overrides action run /tmp/deploy.yaml restart --argocd-cm-path ./argocd-cm.yaml`, 513 Run: func(c *cobra.Command, args []string) { 514 if len(args) < 2 { 515 c.HelpFunc()(c, args) 516 os.Exit(1) 517 } 518 action := args[1] 519 520 executeResourceOverrideCommand(cmdCtx, args, func(res unstructured.Unstructured, override v1alpha1.ResourceOverride, overrides map[string]v1alpha1.ResourceOverride) { 521 gvk := res.GroupVersionKind() 522 if override.Actions == "" { 523 _, _ = fmt.Printf("Actions are not configured for '%s/%s'\n", gvk.Group, gvk.Kind) 524 return 525 } 526 527 luaVM := lua.VM{ResourceOverrides: overrides} 528 action, err := luaVM.GetResourceAction(&res, action) 529 errors.CheckError(err) 530 531 modifiedRes, err := luaVM.ExecuteResourceAction(&res, action.ActionLua) 532 errors.CheckError(err) 533 534 if reflect.DeepEqual(&res, modifiedRes) { 535 _, _ = fmt.Printf("No fields had been changed by action: \n%s\n", action.Name) 536 return 537 } 538 539 _, _ = fmt.Printf("Following fields have been changed:\n\n") 540 _ = cli.PrintDiff(res.GetName(), &res, modifiedRes) 541 }) 542 }, 543 } 544 return command 545 }