Skip to content

Commit

Permalink
feat: Support Custom Application Actions in CLI #7577 (#10015)
Browse files Browse the repository at this point in the history
* Expand `ListResourceActions` to support the root App

Prior to this change, `ListResourceActions` could only be performed on
resources managed by the Application; this implicitly did not include
the Application itself, unless it was a self-referential Application.
This prevents the listing of actions on the Application itself.

This change detects when the requested resource is an Application with
the same name as the managing Application, thus bypassing the fetching
of the requested resource listing and  simply retrieving the
Application.  It then retrieves the resource details from the
Application itself, instead of any sub-resource.

Signed-off-by: David Monks <david.monks@octoenergy.com>

* Expand `RunResourceAction` to support the root App

Prior to this change, similar to the `ListResourceActions` function
updated in the previous commit, the `RunResourceAction` function only
supports running actions on resources managed by the Application, rather
than the Application itself.

This change re-uses in the `RunResourceAction` function the new
behaviour introduced in the previous commit to make the Application a
valid target for resource actions.

Signed-off-by: David Monks <david.monks@octoenergy.com>

* Extract App actions resource gathering to a private function

This abstracts the fetching of resources that can provide custom resource actions for a given Application via the CLI to a new private function next to the two Commands that do it.

This is a precursor to expanding this set of resources to include the Application itself.

Signed-off-by: David Monks <david.monks@octoenergy.com>

* Include the Application in the `actions list` CLI command

Prior to this change, only resources managed by the Application (and not
the Application itself) were able to have actions associated with them
for running via the CLI.

This change includes the Application itself in the list of resources to
check for custom resource actions when listing or running them via the
CLI.

Signed-off-by: David Monks <david.monks@octoenergy.com>

Signed-off-by: David Monks <david.monks@octoenergy.com>
  • Loading branch information
scalen authored Aug 19, 2022
1 parent f93d678 commit d545198
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 25 deletions.
49 changes: 38 additions & 11 deletions cmd/argocd/commands/app_actions.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package commands

import (
"context"
"encoding/json"
"fmt"
"os"
Expand All @@ -17,6 +18,7 @@ import (
"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
applicationpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
v1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/argo"
"github.com/argoproj/argo-cd/v2/util/errors"
"github.com/argoproj/argo-cd/v2/util/io"
Expand Down Expand Up @@ -66,12 +68,9 @@ func NewApplicationResourceActionsListCommand(clientOpts *argocdclient.ClientOpt
appName, appNs := argo.ParseAppQualifiedName(args[0], "")
conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
defer io.Close(conn)
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{
ApplicationName: &appName,
AppNamespace: &appNs,
})
resources, err := getActionableResourcesForApplication(appIf, ctx, &appNs, &appName)
errors.CheckError(err)
filteredObjects, err := util.FilterResources(command.Flags().Changed("group"), resources.Items, group, kind, namespace, resourceName, true)
filteredObjects, err := util.FilterResources(command.Flags().Changed("group"), resources, group, kind, namespace, resourceName, true)
errors.CheckError(err)
var availableActions []DisplayedAction
for i := range filteredObjects {
Expand All @@ -84,7 +83,7 @@ func NewApplicationResourceActionsListCommand(clientOpts *argocdclient.ClientOpt
ResourceName: pointer.String(obj.GetName()),
Group: pointer.String(gvk.Group),
Kind: pointer.String(gvk.Kind),
Version: pointer.String(gvk.GroupVersion().Version),
Version: pointer.String(gvk.Version),
})
errors.CheckError(err)
for _, action := range availActionsForResource.Actions {
Expand Down Expand Up @@ -157,12 +156,9 @@ func NewApplicationResourceActionsRunCommand(clientOpts *argocdclient.ClientOpti

conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
defer io.Close(conn)
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{
ApplicationName: &appName,
AppNamespace: &appNs,
})
resources, err := getActionableResourcesForApplication(appIf, ctx, &appNs, &appName)
errors.CheckError(err)
filteredObjects, err := util.FilterResources(command.Flags().Changed("group"), resources.Items, group, kind, namespace, resourceName, all)
filteredObjects, err := util.FilterResources(command.Flags().Changed("group"), resources, group, kind, namespace, resourceName, all)
errors.CheckError(err)
var resGroup = filteredObjects[0].GroupVersionKind().Group
for i := range filteredObjects[1:] {
Expand Down Expand Up @@ -190,3 +186,34 @@ func NewApplicationResourceActionsRunCommand(clientOpts *argocdclient.ClientOpti
}
return command
}

func getActionableResourcesForApplication(appIf applicationpkg.ApplicationServiceClient, ctx context.Context, appNs *string, appName *string) ([]*v1alpha1.ResourceDiff, error) {
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{
ApplicationName: appName,
AppNamespace: appNs,
})
if err != nil {
return nil, err
}
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{
Name: appName,
AppNamespace: appNs,
})
if err != nil {
return nil, err
}
app.Kind = "Application"
app.APIVersion = "argoproj.io/v1alpha1"
appManifest, err := json.Marshal(app)
if err != nil {
return nil, err
}
appGVK := app.GroupVersionKind()
return append(resources.Items, &v1alpha1.ResourceDiff{
Group: appGVK.Group,
Kind: appGVK.Kind,
Namespace: app.Namespace,
Name: *appName,
LiveState: string(appManifest),
}), nil
}
52 changes: 38 additions & 14 deletions server/application/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -1988,13 +1988,9 @@ func (s *Server) logResourceEvent(res *appv1.ResourceNode, ctx context.Context,
}

func (s *Server) ListResourceActions(ctx context.Context, q *application.ApplicationResourceRequest) (*application.ResourceActionsListResponse, error) {
res, config, _, err := s.getAppLiveResource(ctx, rbacpolicy.ActionGet, q)
obj, _, _, _, err := s.getUnstructuredLiveResourceOrApp(ctx, rbacpolicy.ActionGet, q)
if err != nil {
return nil, fmt.Errorf("error getting app live resource: %w", err)
}
obj, err := s.kubectl.GetResource(ctx, config, res.GroupKindVersion(), res.Name, res.Namespace)
if err != nil {
return nil, fmt.Errorf("error getting resource: %w", err)
return nil, err
}
resourceOverrides, err := s.settingsMgr.GetResourceOverrides()
if err != nil {
Expand All @@ -2013,6 +2009,34 @@ func (s *Server) ListResourceActions(ctx context.Context, q *application.Applica
return &application.ResourceActionsListResponse{Actions: actionsPtr}, nil
}

func (s *Server) getUnstructuredLiveResourceOrApp(ctx context.Context, rbacRequest string, q *application.ApplicationResourceRequest) (obj *unstructured.Unstructured, res *appv1.ResourceNode, app *appv1.Application, config *rest.Config, err error) {
if q.GetKind() == "Application" && q.GetGroup() == "argoproj.io" && q.GetName() == q.GetResourceName() {
namespace := s.appNamespaceOrDefault(q.GetAppNamespace())
app, err = s.appLister.Applications(namespace).Get(q.GetName())
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("error getting app by name: %w", err)
}
if err = s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacRequest, app.RBACName(s.ns)); err != nil {
return nil, nil, nil, nil, err
}
config, err = s.getApplicationClusterConfig(ctx, app)
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("error getting application cluster config: %w", err)
}
obj, err = kube.ToUnstructured(app)
} else {
res, config, app, err = s.getAppLiveResource(ctx, rbacRequest, q)
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("error getting app live resource: %w", err)
}
obj, err = s.kubectl.GetResource(ctx, config, res.GroupKindVersion(), res.Name, res.Namespace)
}
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("error getting resource: %w", err)
}
return
}

func (s *Server) getAvailableActions(resourceOverrides map[string]appv1.ResourceOverride, obj *unstructured.Unstructured) ([]appv1.ResourceAction, error) {
luaVM := lua.VM{
ResourceOverrides: resourceOverrides,
Expand Down Expand Up @@ -2044,13 +2068,9 @@ func (s *Server) RunResourceAction(ctx context.Context, q *application.ResourceA
Group: q.Group,
}
actionRequest := fmt.Sprintf("%s/%s/%s/%s", rbacpolicy.ActionAction, q.GetGroup(), q.GetKind(), q.GetAction())
res, config, a, err := s.getAppLiveResource(ctx, actionRequest, resourceRequest)
liveObj, res, a, config, err := s.getUnstructuredLiveResourceOrApp(ctx, actionRequest, resourceRequest)
if err != nil {
return nil, fmt.Errorf("error getting app live resource: %w", err)
}
liveObj, err := s.kubectl.GetResource(ctx, config, res.GroupKindVersion(), res.Name, res.Namespace)
if err != nil {
return nil, fmt.Errorf("error getting resource: %w", err)
return nil, err
}

resourceOverrides, err := s.settingsMgr.GetResourceOverrides()
Expand Down Expand Up @@ -2121,8 +2141,12 @@ func (s *Server) RunResourceAction(ctx context.Context, q *application.ResourceA
}
}

s.logAppEvent(a, ctx, argo.EventReasonResourceActionRan, fmt.Sprintf("ran action %s on resource %s/%s/%s", q.GetAction(), res.Group, res.Kind, res.Name))
s.logResourceEvent(res, ctx, argo.EventReasonResourceActionRan, fmt.Sprintf("ran action %s", q.GetAction()))
if res == nil {
s.logAppEvent(a, ctx, argo.EventReasonResourceActionRan, fmt.Sprintf("ran action %s", q.GetAction()))
} else {
s.logAppEvent(a, ctx, argo.EventReasonResourceActionRan, fmt.Sprintf("ran action %s on resource %s/%s/%s", q.GetAction(), res.Group, res.Kind, res.Name))
s.logResourceEvent(res, ctx, argo.EventReasonResourceActionRan, fmt.Sprintf("ran action %s", q.GetAction()))
}
return &application.ApplicationResponse{}, nil
}

Expand Down

0 comments on commit d545198

Please sign in to comment.