github.com/argoproj/argo-cd/v3@v3.2.1/cmd/argocd/commands/app_actions.go (about)

     1  package commands
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"os"
     8  	"strconv"
     9  	"text/tabwriter"
    10  
    11  	log "github.com/sirupsen/logrus"
    12  	"github.com/spf13/cobra"
    13  	"google.golang.org/grpc/codes"
    14  	"k8s.io/utils/ptr"
    15  	"sigs.k8s.io/yaml"
    16  
    17  	"github.com/argoproj/argo-cd/v3/cmd/argocd/commands/headless"
    18  	"github.com/argoproj/argo-cd/v3/cmd/util"
    19  	argocdclient "github.com/argoproj/argo-cd/v3/pkg/apiclient"
    20  	applicationpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/application"
    21  	"github.com/argoproj/argo-cd/v3/pkg/apis/application"
    22  	"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    23  	"github.com/argoproj/argo-cd/v3/util/argo"
    24  	"github.com/argoproj/argo-cd/v3/util/errors"
    25  	"github.com/argoproj/argo-cd/v3/util/grpc"
    26  	utilio "github.com/argoproj/argo-cd/v3/util/io"
    27  	"github.com/argoproj/argo-cd/v3/util/templates"
    28  )
    29  
    30  type DisplayedAction struct {
    31  	Group    string
    32  	Kind     string
    33  	Name     string
    34  	Action   string
    35  	Disabled bool
    36  }
    37  
    38  var appActionExample = templates.Examples(`
    39  	# List all the available actions for an application
    40  	argocd app actions list APPNAME
    41  
    42  	# Run an available action for an application
    43  	argocd app actions run APPNAME ACTION --kind KIND [--resource-name RESOURCE] [--namespace NAMESPACE] [--group GROUP]
    44  	`)
    45  
    46  // NewApplicationResourceActionsCommand returns a new instance of an `argocd app actions` command
    47  func NewApplicationResourceActionsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
    48  	command := &cobra.Command{
    49  		Use:     "actions",
    50  		Short:   "Manage Resource actions",
    51  		Example: appActionExample,
    52  		Run: func(c *cobra.Command, args []string) {
    53  			c.HelpFunc()(c, args)
    54  			os.Exit(1)
    55  		},
    56  	}
    57  	command.AddCommand(NewApplicationResourceActionsListCommand(clientOpts))
    58  	command.AddCommand(NewApplicationResourceActionsRunCommand(clientOpts))
    59  	return command
    60  }
    61  
    62  // NewApplicationResourceActionsListCommand returns a new instance of an `argocd app actions list` command
    63  func NewApplicationResourceActionsListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
    64  	var namespace string
    65  	var kind string
    66  	var group string
    67  	var resourceName string
    68  	var output string
    69  	command := &cobra.Command{
    70  		Use:   "list APPNAME",
    71  		Short: "Lists available actions on a resource",
    72  		Example: templates.Examples(`
    73  	# List all the available actions for an application
    74  	argocd app actions list APPNAME
    75  	`),
    76  	}
    77  	command.Run = func(c *cobra.Command, args []string) {
    78  		ctx := c.Context()
    79  
    80  		if len(args) != 1 {
    81  			c.HelpFunc()(c, args)
    82  			os.Exit(1)
    83  		}
    84  		appName, appNs := argo.ParseFromQualifiedName(args[0], "")
    85  		conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
    86  		defer utilio.Close(conn)
    87  		resources, err := getActionableResourcesForApplication(ctx, appIf, &appNs, &appName)
    88  		errors.CheckError(err)
    89  		filteredObjects, err := util.FilterResources(command.Flags().Changed("group"), resources, group, kind, namespace, resourceName, true)
    90  		errors.CheckError(err)
    91  		var availableActions []DisplayedAction
    92  		for i := range filteredObjects {
    93  			obj := filteredObjects[i]
    94  			gvk := obj.GroupVersionKind()
    95  			availActionsForResource, err := appIf.ListResourceActions(ctx, &applicationpkg.ApplicationResourceRequest{
    96  				Name:         &appName,
    97  				AppNamespace: &appNs,
    98  				Namespace:    ptr.To(obj.GetNamespace()),
    99  				ResourceName: ptr.To(obj.GetName()),
   100  				Group:        ptr.To(gvk.Group),
   101  				Kind:         ptr.To(gvk.Kind),
   102  				Version:      ptr.To(gvk.Version),
   103  			})
   104  			errors.CheckError(err)
   105  			for _, action := range availActionsForResource.Actions {
   106  				displayAction := DisplayedAction{
   107  					Group:    gvk.Group,
   108  					Kind:     gvk.Kind,
   109  					Name:     obj.GetName(),
   110  					Action:   action.Name,
   111  					Disabled: action.Disabled,
   112  				}
   113  				availableActions = append(availableActions, displayAction)
   114  			}
   115  		}
   116  
   117  		switch output {
   118  		case "yaml":
   119  			yamlBytes, err := yaml.Marshal(availableActions)
   120  			errors.CheckError(err)
   121  			fmt.Println(string(yamlBytes))
   122  		case "json":
   123  			jsonBytes, err := json.MarshalIndent(availableActions, "", "  ")
   124  			errors.CheckError(err)
   125  			fmt.Println(string(jsonBytes))
   126  		case "":
   127  			w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
   128  			fmt.Fprintf(w, "GROUP\tKIND\tNAME\tACTION\tDISABLED\n")
   129  			for _, action := range availableActions {
   130  				fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", action.Group, action.Kind, action.Name, action.Action, strconv.FormatBool(action.Disabled))
   131  			}
   132  			_ = w.Flush()
   133  		}
   134  	}
   135  	command.Flags().StringVar(&resourceName, "resource-name", "", "Name of resource")
   136  	command.Flags().StringVar(&kind, "kind", "", "Kind")
   137  	command.Flags().StringVar(&group, "group", "", "Group")
   138  	command.Flags().StringVar(&namespace, "namespace", "", "Namespace")
   139  	command.Flags().StringVarP(&output, "out", "o", "", "Output format. One of: yaml, json")
   140  
   141  	return command
   142  }
   143  
   144  // NewApplicationResourceActionsRunCommand returns a new instance of an `argocd app actions run` command
   145  func NewApplicationResourceActionsRunCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   146  	var namespace string
   147  	var resourceName string
   148  	var kind string
   149  	var group string
   150  	var all bool
   151  	command := &cobra.Command{
   152  		Use:   "run APPNAME ACTION",
   153  		Short: "Runs an available action on resource(s) matching the specified filters.",
   154  		Long:  "All filters except --kind are optional. Use --all to run the action on all matching resources if more than one resource matches the filters. Actions may only be run on resources that are represented in git and cannot be run on child resources.",
   155  		Example: templates.Examples(`
   156  	# Run an available action for an application
   157  	argocd app actions run APPNAME ACTION --kind KIND [--resource-name RESOURCE] [--namespace NAMESPACE] [--group GROUP]
   158  	`),
   159  	}
   160  
   161  	command.Flags().StringVar(&resourceName, "resource-name", "", "Name of resource on which the action should be run")
   162  	command.Flags().StringVar(&namespace, "namespace", "", "Namespace of the resource on which the action should be run")
   163  	command.Flags().StringVar(&kind, "kind", "", "Kind of the resource on which the action should be run")
   164  	command.Flags().StringVar(&group, "group", "", "Group of the resource on which the action should be run")
   165  	errors.CheckError(command.MarkFlagRequired("kind"))
   166  	command.Flags().BoolVar(&all, "all", false, "Indicates whether to run the action on multiple matching resources")
   167  
   168  	command.Run = func(c *cobra.Command, args []string) {
   169  		ctx := c.Context()
   170  
   171  		if len(args) != 2 {
   172  			c.HelpFunc()(c, args)
   173  			os.Exit(1)
   174  		}
   175  		appName, appNs := argo.ParseFromQualifiedName(args[0], "")
   176  		actionName := args[1]
   177  
   178  		conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
   179  		defer utilio.Close(conn)
   180  		resources, err := getActionableResourcesForApplication(ctx, appIf, &appNs, &appName)
   181  		errors.CheckError(err)
   182  		filteredObjects, err := util.FilterResources(command.Flags().Changed("group"), resources, group, kind, namespace, resourceName, all)
   183  		errors.CheckError(err)
   184  		resGroup := filteredObjects[0].GroupVersionKind().Group
   185  		for i := range filteredObjects[1:] {
   186  			if filteredObjects[i].GroupVersionKind().Group != resGroup {
   187  				log.Fatal("Ambiguous resource group. Use flag --group to specify resource group explicitly.")
   188  			}
   189  		}
   190  
   191  		for i := range filteredObjects {
   192  			obj := filteredObjects[i]
   193  			gvk := obj.GroupVersionKind()
   194  			objResourceName := obj.GetName()
   195  			_, err := appIf.RunResourceActionV2(ctx, &applicationpkg.ResourceActionRunRequestV2{
   196  				Name:         &appName,
   197  				AppNamespace: &appNs,
   198  				Namespace:    ptr.To(obj.GetNamespace()),
   199  				ResourceName: ptr.To(objResourceName),
   200  				Group:        ptr.To(gvk.Group),
   201  				Kind:         ptr.To(gvk.Kind),
   202  				Version:      ptr.To(gvk.GroupVersion().Version),
   203  				Action:       ptr.To(actionName),
   204  				// TODO: add support for parameters
   205  			})
   206  			if err == nil {
   207  				continue
   208  			}
   209  			if grpc.UnwrapGRPCStatus(err).Code() != codes.Unimplemented {
   210  				errors.CheckError(err)
   211  			}
   212  			fmt.Println("RunResourceActionV2 is not supported by the server, falling back to RunResourceAction.")
   213  			//nolint:staticcheck // RunResourceAction is deprecated, but we still need to support it for backward compatibility.
   214  			_, err = appIf.RunResourceAction(ctx, &applicationpkg.ResourceActionRunRequest{
   215  				Name:         &appName,
   216  				AppNamespace: &appNs,
   217  				Namespace:    ptr.To(obj.GetNamespace()),
   218  				ResourceName: ptr.To(objResourceName),
   219  				Group:        ptr.To(gvk.Group),
   220  				Kind:         ptr.To(gvk.Kind),
   221  				Version:      ptr.To(gvk.GroupVersion().Version),
   222  				Action:       ptr.To(actionName),
   223  			})
   224  			errors.CheckError(err)
   225  		}
   226  	}
   227  	return command
   228  }
   229  
   230  func getActionableResourcesForApplication(ctx context.Context, appIf applicationpkg.ApplicationServiceClient, appNs *string, appName *string) ([]*v1alpha1.ResourceDiff, error) {
   231  	resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{
   232  		ApplicationName: appName,
   233  		AppNamespace:    appNs,
   234  	})
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  	app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{
   239  		Name:         appName,
   240  		AppNamespace: appNs,
   241  	})
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  	app.Kind = application.ApplicationKind
   246  	app.APIVersion = "argoproj.io/v1alpha1"
   247  	appManifest, err := json.Marshal(app)
   248  	if err != nil {
   249  		return nil, err
   250  	}
   251  	appGVK := app.GroupVersionKind()
   252  	return append(resources.Items, &v1alpha1.ResourceDiff{
   253  		Group:     appGVK.Group,
   254  		Kind:      appGVK.Kind,
   255  		Namespace: app.Namespace,
   256  		Name:      *appName,
   257  		LiveState: string(appManifest),
   258  	}), nil
   259  }