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