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 }