diff --git a/controller/appcontroller.go b/controller/appcontroller.go index 6dfa478133019..c10099b47bda9 100644 --- a/controller/appcontroller.go +++ b/controller/appcontroller.go @@ -28,6 +28,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" apiruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" @@ -421,8 +422,12 @@ func (ctrl *ApplicationController) getResourceTree(a *appv1.Application, managed }, }) } else { - err := ctrl.stateCache.IterateHierarchy(a.Spec.Destination.Server, kube.GetResourceKey(live), func(child appv1.ResourceNode, appName string) { + err := ctrl.stateCache.IterateHierarchy(a.Spec.Destination.Server, kube.GetResourceKey(live), func(child appv1.ResourceNode, appName string) bool { + if !proj.IsResourcePermitted(schema.GroupKind{Group: child.ResourceRef.Group, Kind: child.ResourceRef.Kind}, child.Namespace, a.Spec.Destination) { + return false + } nodes = append(nodes, child) + return true }) if err != nil { return nil, err @@ -432,16 +437,18 @@ func (ctrl *ApplicationController) getResourceTree(a *appv1.Application, managed orphanedNodes := make([]appv1.ResourceNode, 0) for k := range orphanedNodesMap { if k.Namespace != "" && proj.IsGroupKindPermitted(k.GroupKind(), true) && !isKnownOrphanedResourceExclusion(k, proj) { - err := ctrl.stateCache.IterateHierarchy(a.Spec.Destination.Server, k, func(child appv1.ResourceNode, appName string) { + err := ctrl.stateCache.IterateHierarchy(a.Spec.Destination.Server, k, func(child appv1.ResourceNode, appName string) bool { belongToAnotherApp := false if appName != "" { if _, exists, err := ctrl.appInformer.GetIndexer().GetByKey(ctrl.namespace + "/" + appName); exists && err == nil { belongToAnotherApp = true } } - if !belongToAnotherApp { - orphanedNodes = append(orphanedNodes, child) + if belongToAnotherApp || !proj.IsResourcePermitted(schema.GroupKind{Group: child.ResourceRef.Group, Kind: child.ResourceRef.Kind}, child.Namespace, a.Spec.Destination) { + return false } + orphanedNodes = append(orphanedNodes, child) + return true }) if err != nil { return nil, err @@ -1291,6 +1298,13 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo app.Status.Sync.Status = appv1.SyncStatusCodeUnknown app.Status.Health.Status = health.HealthStatusUnknown ctrl.persistAppStatus(origApp, &app.Status) + + if err := ctrl.cache.SetAppResourcesTree(app.Name, &appv1.ApplicationTree{}); err != nil { + log.Warnf("failed to set app resource tree: %v", err) + } + if err := ctrl.cache.SetAppManagedResources(app.Name, nil); err != nil { + log.Warnf("failed to set app managed resources tree: %v", err) + } return } diff --git a/controller/appcontroller_test.go b/controller/appcontroller_test.go index e7119c8bc13f7..cdb8cef3d0813 100644 --- a/controller/appcontroller_test.go +++ b/controller/appcontroller_test.go @@ -136,12 +136,12 @@ func newFakeController(data *fakeData) *ApplicationController { mockStateCache.On("GetClusterCache", mock.Anything).Return(&clusterCacheMock, nil) mockStateCache.On("IterateHierarchy", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { key := args[1].(kube.ResourceKey) - action := args[2].(func(child argoappv1.ResourceNode, appName string)) + action := args[2].(func(child argoappv1.ResourceNode, appName string) bool) appName := "" if res, ok := data.namespacedResources[key]; ok { appName = res.AppName } - action(argoappv1.ResourceNode{ResourceRef: argoappv1.ResourceRef{Kind: key.Kind, Group: key.Group, Namespace: key.Namespace, Name: key.Name}}, appName) + _ = action(argoappv1.ResourceNode{ResourceRef: argoappv1.ResourceRef{Kind: key.Kind, Group: key.Group, Namespace: key.Namespace, Name: key.Name}}, appName) }).Return(nil) return ctrl } diff --git a/controller/cache/cache.go b/controller/cache/cache.go index ca6c29712f0d7..4149cc09c1a62 100644 --- a/controller/cache/cache.go +++ b/controller/cache/cache.go @@ -108,7 +108,7 @@ type LiveStateCache interface { // Returns synced cluster cache GetClusterCache(server string) (clustercache.ClusterCache, error) // Executes give callback against resource specified by the key and all its children - IterateHierarchy(server string, key kube.ResourceKey, action func(child appv1.ResourceNode, appName string)) error + IterateHierarchy(server string, key kube.ResourceKey, action func(child appv1.ResourceNode, appName string) bool) error // Returns state of live nodes which correspond for target nodes of specified application. GetManagedLiveObjs(a *appv1.Application, targetObjs []*unstructured.Unstructured) (map[kube.ResourceKey]*unstructured.Unstructured, error) // IterateResources iterates all resource stored in cache @@ -481,13 +481,13 @@ func (c *liveStateCache) IsNamespaced(server string, gk schema.GroupKind) (bool, return clusterInfo.IsNamespaced(gk) } -func (c *liveStateCache) IterateHierarchy(server string, key kube.ResourceKey, action func(child appv1.ResourceNode, appName string)) error { +func (c *liveStateCache) IterateHierarchy(server string, key kube.ResourceKey, action func(child appv1.ResourceNode, appName string) bool) error { clusterInfo, err := c.getSyncedCluster(server) if err != nil { return err } - clusterInfo.IterateHierarchy(key, func(resource *clustercache.Resource, namespaceResources map[kube.ResourceKey]*clustercache.Resource) { - action(asResourceNode(resource), getApp(resource, namespaceResources)) + clusterInfo.IterateHierarchy(key, func(resource *clustercache.Resource, namespaceResources map[kube.ResourceKey]*clustercache.Resource) bool { + return action(asResourceNode(resource), getApp(resource, namespaceResources)) }) return nil } diff --git a/controller/cache/mocks/LiveStateCache.go b/controller/cache/mocks/LiveStateCache.go index 0abbb5998ec7f..7dc4d6b7710e2 100644 --- a/controller/cache/mocks/LiveStateCache.go +++ b/controller/cache/mocks/LiveStateCache.go @@ -176,11 +176,11 @@ func (_m *LiveStateCache) IsNamespaced(server string, gk schema.GroupKind) (bool } // IterateHierarchy provides a mock function with given fields: server, key, action -func (_m *LiveStateCache) IterateHierarchy(server string, key kube.ResourceKey, action func(v1alpha1.ResourceNode, string)) error { +func (_m *LiveStateCache) IterateHierarchy(server string, key kube.ResourceKey, action func(v1alpha1.ResourceNode, string) bool) error { ret := _m.Called(server, key, action) var r0 error - if rf, ok := ret.Get(0).(func(string, kube.ResourceKey, func(v1alpha1.ResourceNode, string)) error); ok { + if rf, ok := ret.Get(0).(func(string, kube.ResourceKey, func(v1alpha1.ResourceNode, string) bool) error); ok { r0 = rf(server, key, action) } else { r0 = ret.Error(0) diff --git a/go.mod b/go.mod index 626d014310105..e44d57c15f08b 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d github.com/alicebob/miniredis v2.5.0+incompatible github.com/alicebob/miniredis/v2 v2.14.2 - github.com/argoproj/gitops-engine v0.6.0 + github.com/argoproj/gitops-engine v0.6.1-0.20220316000647-723667dff7d5 github.com/argoproj/notifications-engine v0.3.1-0.20220127183449-91deed20b998 github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0 github.com/bombsimon/logrusr/v2 v2.0.1 diff --git a/go.sum b/go.sum index 806a03999cd7d..dad51d689a0c0 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,8 @@ github.com/antonmedv/expr v1.8.9/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmH github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/appscode/go v0.0.0-20190808133642-1d4ef1f1c1e0/go.mod h1:iy07dV61Z7QQdCKJCIvUoDL21u6AIceRhZzyleh2ymc= -github.com/argoproj/gitops-engine v0.6.0 h1:Tnh6kUUVuBV0m3gueYIymAeErWl9XNN9O9JcOoNM0vU= -github.com/argoproj/gitops-engine v0.6.0/go.mod h1:pRgVpLW7pZqf7n3COJ7UcDepk4cI61LAcJd64Q3Jq/c= +github.com/argoproj/gitops-engine v0.6.1-0.20220316000647-723667dff7d5 h1:QZvSvXHKx/hKTvbw3EK+u9JDVlo6UvC4+IVqNIGbcUo= +github.com/argoproj/gitops-engine v0.6.1-0.20220316000647-723667dff7d5/go.mod h1:pRgVpLW7pZqf7n3COJ7UcDepk4cI61LAcJd64Q3Jq/c= github.com/argoproj/notifications-engine v0.3.1-0.20220127183449-91deed20b998 h1:V9RDg+IZeebnm3XjkfkbN07VM21Fu1Cy/RJNoHO++VM= github.com/argoproj/notifications-engine v0.3.1-0.20220127183449-91deed20b998/go.mod h1:5mKv7zEgI3NO0L+fsuRSwBSY9EIXSuyIsDND8O8TTIw= github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0 h1:Cfp7rO/HpVxnwlRqJe0jHiBbZ77ZgXhB6HWlYD02Xdc= diff --git a/pkg/apis/application/v1alpha1/app_project_types.go b/pkg/apis/application/v1alpha1/app_project_types.go index 1a3253576d236..562b91a77ae3b 100644 --- a/pkg/apis/application/v1alpha1/app_project_types.go +++ b/pkg/apis/application/v1alpha1/app_project_types.go @@ -313,11 +313,15 @@ func (proj AppProject) IsGroupKindPermitted(gk schema.GroupKind, namespaced bool // IsLiveResourcePermitted returns whether a live resource found in the cluster is permitted by an AppProject func (proj AppProject) IsLiveResourcePermitted(un *unstructured.Unstructured, server string, name string) bool { - if !proj.IsGroupKindPermitted(un.GroupVersionKind().GroupKind(), un.GetNamespace() != "") { + return proj.IsResourcePermitted(un.GroupVersionKind().GroupKind(), un.GetNamespace(), ApplicationDestination{Server: server, Name: name}) +} + +func (proj AppProject) IsResourcePermitted(groupKind schema.GroupKind, namespace string, dest ApplicationDestination) bool { + if !proj.IsGroupKindPermitted(groupKind, namespace != "") { return false } - if un.GetNamespace() != "" { - return proj.IsDestinationPermitted(ApplicationDestination{Server: server, Namespace: un.GetNamespace(), Name: name}) + if namespace != "" { + return proj.IsDestinationPermitted(ApplicationDestination{Server: dest.Server, Name: dest.Name, Namespace: namespace}) } return true } diff --git a/server/application/application.go b/server/application/application.go index b16856c467255..c05c9d469e958 100644 --- a/server/application/application.go +++ b/server/application/application.go @@ -488,6 +488,21 @@ func (s *Server) ListResourceEvents(ctx context.Context, q *application.Applicat "involvedObject.namespace": a.Namespace, }).String() } else { + tree, err := s.getAppResources(ctx, a) + if err != nil { + return nil, err + } + found := false + for _, n := range append(tree.Nodes, tree.OrphanedNodes...) { + if n.ResourceRef.UID == q.ResourceUID && n.ResourceRef.Name == q.ResourceName && n.ResourceRef.Namespace == q.ResourceNamespace { + found = true + break + } + } + if !found { + return nil, status.Errorf(codes.InvalidArgument, "%s not found as part of application %s", q.ResourceName, *q.Name) + } + namespace = q.ResourceNamespace var config *rest.Config config, err = s.getApplicationClusterConfig(ctx, a) @@ -937,7 +952,7 @@ func (s *Server) getAppResources(ctx context.Context, a *appv1.Application) (*ap return &tree, err } -func (s *Server) getAppResource(ctx context.Context, action string, q *application.ApplicationResourceRequest) (*appv1.ResourceNode, *rest.Config, *appv1.Application, error) { +func (s *Server) getAppLiveResource(ctx context.Context, action string, q *application.ApplicationResourceRequest) (*appv1.ResourceNode, *rest.Config, *appv1.Application, error) { a, err := s.appLister.Get(*q.Name) if err != nil { return nil, nil, nil, err @@ -952,7 +967,7 @@ func (s *Server) getAppResource(ctx context.Context, action string, q *applicati } found := tree.FindNode(q.Group, q.Kind, q.Namespace, q.ResourceName) - if found == nil { + if found == nil || found.ResourceRef.UID == "" { return nil, nil, nil, status.Errorf(codes.InvalidArgument, "%s %s %s not found as part of application %s", q.Kind, q.Group, q.ResourceName, *q.Name) } config, err := s.getApplicationClusterConfig(ctx, a) @@ -963,7 +978,7 @@ func (s *Server) getAppResource(ctx context.Context, action string, q *applicati } func (s *Server) GetResource(ctx context.Context, q *application.ApplicationResourceRequest) (*application.ApplicationResourceResponse, error) { - res, config, _, err := s.getAppResource(ctx, rbacpolicy.ActionGet, q) + res, config, _, err := s.getAppLiveResource(ctx, rbacpolicy.ActionGet, q) if err != nil { return nil, err } @@ -1008,7 +1023,7 @@ func (s *Server) PatchResource(ctx context.Context, q *application.ApplicationRe Version: q.Version, Group: q.Group, } - res, config, a, err := s.getAppResource(ctx, rbacpolicy.ActionUpdate, resourceRequest) + res, config, a, err := s.getAppLiveResource(ctx, rbacpolicy.ActionUpdate, resourceRequest) if err != nil { return nil, err } @@ -1048,7 +1063,7 @@ func (s *Server) DeleteResource(ctx context.Context, q *application.ApplicationR Version: q.Version, Group: q.Group, } - res, config, a, err := s.getAppResource(ctx, rbacpolicy.ActionDelete, resourceRequest) + res, config, a, err := s.getAppLiveResource(ctx, rbacpolicy.ActionDelete, resourceRequest) if err != nil { return nil, err } @@ -1335,7 +1350,7 @@ func getSelectedPods(treeNodes []appv1.ResourceNode, q *application.ApplicationP var pods []appv1.ResourceNode isTheOneMap := make(map[string]bool) for _, treeNode := range treeNodes { - if treeNode.Kind == kube.PodKind && treeNode.Group == "" { + if treeNode.Kind == kube.PodKind && treeNode.Group == "" && treeNode.UID != "" { if isTheSelectedOne(&treeNode, q, treeNodes, isTheOneMap) { pods = append(pods, treeNode) } @@ -1625,7 +1640,7 @@ 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.getAppResource(ctx, rbacpolicy.ActionGet, q) + res, config, _, err := s.getAppLiveResource(ctx, rbacpolicy.ActionGet, q) if err != nil { return nil, err } @@ -1676,7 +1691,7 @@ func (s *Server) RunResourceAction(ctx context.Context, q *application.ResourceA Group: q.Group, } actionRequest := fmt.Sprintf("%s/%s/%s/%s", rbacpolicy.ActionAction, q.Group, q.Kind, q.Action) - res, config, a, err := s.getAppResource(ctx, actionRequest, resourceRequest) + res, config, a, err := s.getAppLiveResource(ctx, actionRequest, resourceRequest) if err != nil { return nil, err } diff --git a/test/e2e/app_management_test.go b/test/e2e/app_management_test.go index 0ab4f3b18babf..8462948a5875e 100644 --- a/test/e2e/app_management_test.go +++ b/test/e2e/app_management_test.go @@ -1046,64 +1046,125 @@ func TestSyncAsync(t *testing.T) { Expect(SyncStatusIs(SyncStatusCodeSynced)) } -func TestPermissions(t *testing.T) { - EnsureCleanState(t) - appName := Name() - _, err := RunCli("proj", "create", "test") - assert.NoError(t, err) +// assertResourceActions verifies if view/modify resource actions are successful/failing for given application +func assertResourceActions(t *testing.T, appName string, successful bool) { + assertError := func(err error, message string) { + if successful { + assert.NoError(t, err) + } else { + if assert.Error(t, err) { + assert.Contains(t, err.Error(), message) + } + } + } - // make sure app cannot be created without permissions in project - _, err = RunCli("app", "create", appName, "--repo", RepoURL(RepoURLTypeFile), - "--path", guestbookPath, "--project", "test", "--dest-server", KubernetesInternalAPIServerAddr, "--dest-namespace", DeploymentNamespace()) - assert.Error(t, err) - sourceError := fmt.Sprintf("application repo %s is not permitted in project 'test'", RepoURL(RepoURLTypeFile)) - destinationError := fmt.Sprintf("application destination {%s %s} is not permitted in project 'test'", KubernetesInternalAPIServerAddr, DeploymentNamespace()) + closer, cdClient := ArgoCDClientset.NewApplicationClientOrDie() + defer io.Close(closer) - assert.Contains(t, err.Error(), sourceError) - assert.Contains(t, err.Error(), destinationError) + deploymentResource, err := KubeClientset.AppsV1().Deployments(DeploymentNamespace()).Get(context.Background(), "guestbook-ui", metav1.GetOptions{}) + require.NoError(t, err) - proj, err := AppClientset.ArgoprojV1alpha1().AppProjects(ArgoCDNamespace).Get(context.Background(), "test", metav1.GetOptions{}) - assert.NoError(t, err) + logs, err := cdClient.PodLogs(context.Background(), &applicationpkg.ApplicationPodLogsQuery{ + Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Name: &appName, Namespace: DeploymentNamespace(), + }) + require.NoError(t, err) + _, err = logs.Recv() + assertError(err, "EOF") - proj.Spec.Destinations = []ApplicationDestination{{Server: "*", Namespace: "*"}} - proj.Spec.SourceRepos = []string{"*"} - proj, err = AppClientset.ArgoprojV1alpha1().AppProjects(ArgoCDNamespace).Update(context.Background(), proj, metav1.UpdateOptions{}) - assert.NoError(t, err) + expectedError := fmt.Sprintf("Deployment apps guestbook-ui not found as part of application %s", appName) - // make sure controller report permissions issues in conditions - _, err = RunCli("app", "create", appName, "--repo", RepoURL(RepoURLTypeFile), - "--path", guestbookPath, "--project", "test", "--dest-server", KubernetesInternalAPIServerAddr, "--dest-namespace", DeploymentNamespace()) - assert.NoError(t, err) - defer func() { - err = AppClientset.ArgoprojV1alpha1().Applications(ArgoCDNamespace).Delete(context.Background(), appName, metav1.DeleteOptions{}) - assert.NoError(t, err) - }() + _, err = cdClient.ListResourceEvents(context.Background(), &applicationpkg.ApplicationResourceEventsQuery{ + Name: &appName, ResourceName: "guestbook-ui", ResourceNamespace: DeploymentNamespace(), ResourceUID: string(deploymentResource.UID)}) + assertError(err, fmt.Sprintf("%s not found as part of application %s", "guestbook-ui", appName)) - proj.Spec.Destinations = []ApplicationDestination{} - proj.Spec.SourceRepos = []string{} - _, err = AppClientset.ArgoprojV1alpha1().AppProjects(ArgoCDNamespace).Update(context.Background(), proj, metav1.UpdateOptions{}) - assert.NoError(t, err) - time.Sleep(1 * time.Second) - closer, client, err := ArgoCDClientset.NewApplicationClient() - assert.NoError(t, err) - defer io.Close(closer) + _, err = cdClient.GetResource(context.Background(), &applicationpkg.ApplicationResourceRequest{ + Name: &appName, ResourceName: "guestbook-ui", Namespace: DeploymentNamespace(), Version: "v1", Group: "apps", Kind: "Deployment"}) + assertError(err, expectedError) - refresh := string(RefreshTypeNormal) - app, err := client.Get(context.Background(), &applicationpkg.ApplicationQuery{Name: &appName, Refresh: &refresh}) - assert.NoError(t, err) + _, err = cdClient.DeleteResource(context.Background(), &applicationpkg.ApplicationResourceDeleteRequest{ + Name: &appName, ResourceName: "guestbook-ui", Namespace: DeploymentNamespace(), Version: "v1", Group: "apps", Kind: "Deployment", + }) + assertError(err, expectedError) - destinationErrorExist := false - sourceErrorExist := false - for i := range app.Status.Conditions { - if strings.Contains(app.Status.Conditions[i].Message, destinationError) { - destinationErrorExist = true - } - if strings.Contains(app.Status.Conditions[i].Message, sourceError) { - sourceErrorExist = true - } - } - assert.True(t, destinationErrorExist) - assert.True(t, sourceErrorExist) + _, err = cdClient.RunResourceAction(context.Background(), &applicationpkg.ResourceActionRunRequest{ + Name: &appName, ResourceName: "guestbook-ui", Namespace: DeploymentNamespace(), Version: "v1", Group: "apps", Kind: "Deployment", Action: "restart", + }) + assertError(err, expectedError) +} + +func TestPermissions(t *testing.T) { + appCtx := Given(t) + projName := "argo-project" + projActions := projectFixture. + Given(t). + Name(projName). + When(). + Create() + + sourceError := fmt.Sprintf("application repo %s is not permitted in project 'argo-project'", RepoURL(RepoURLTypeFile)) + destinationError := fmt.Sprintf("application destination {%s %s} is not permitted in project 'argo-project'", KubernetesInternalAPIServerAddr, DeploymentNamespace()) + + appCtx. + Path("guestbook-logs"). + Project(projName). + When(). + IgnoreErrors(). + // ensure app is not created if project permissions are missing + CreateApp(). + Then(). + Expect(Error("", sourceError)). + Expect(Error("", destinationError)). + When(). + DoNotIgnoreErrors(). + // add missing permissions, create and sync app + And(func() { + projActions.AddDestination("*", "*") + projActions.AddSource("*") + }). + CreateApp(). + Sync(). + Then(). + // make sure application resource actiions are successful + And(func(app *Application) { + assertResourceActions(t, app.Name, true) + }). + When(). + // remove projet permissions and "refresh" app + And(func() { + projActions.UpdateProject(func(proj *AppProject) { + proj.Spec.Destinations = nil + proj.Spec.SourceRepos = nil + }) + }). + Refresh(RefreshTypeNormal). + Then(). + // ensure app resource tree is empty when source/destination permissions are missing + Expect(Condition(ApplicationConditionInvalidSpecError, destinationError)). + Expect(Condition(ApplicationConditionInvalidSpecError, sourceError)). + And(func(app *Application) { + closer, cdClient := ArgoCDClientset.NewApplicationClientOrDie() + defer io.Close(closer) + tree, err := cdClient.ResourceTree(context.Background(), &applicationpkg.ResourcesQuery{ApplicationName: &app.Name}) + require.NoError(t, err) + assert.Len(t, tree.Nodes, 0) + assert.Len(t, tree.OrphanedNodes, 0) + }). + When(). + // add missing permissions but deny management of Deployment kind + And(func() { + projActions. + AddDestination("*", "*"). + AddSource("*"). + UpdateProject(func(proj *AppProject) { + proj.Spec.NamespaceResourceBlacklist = []metav1.GroupKind{{Group: "*", Kind: "Deployment"}} + }) + }). + Refresh(RefreshTypeNormal). + Then(). + // make sure application resource actiions are failing + And(func(app *Application) { + assertResourceActions(t, "test-permissions", false) + }) } func TestPermissionWithScopedRepo(t *testing.T) { diff --git a/test/e2e/fixture/project/actions.go b/test/e2e/fixture/project/actions.go index 38ede63736f7c..4c3b929277f91 100644 --- a/test/e2e/fixture/project/actions.go +++ b/test/e2e/fixture/project/actions.go @@ -1,7 +1,12 @@ package project import ( + "context" + + "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/v2/test/e2e/fixture" + "github.com/stretchr/testify/require" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // this implements the "when" part of given/when/then @@ -34,6 +39,25 @@ func (a *Actions) Create(args ...string) *Actions { return a } +func (a *Actions) AddDestination(cluster string, namespace string) *Actions { + a.runCli("proj", "add-destination", a.context.name, cluster, namespace) + return a +} + +func (a *Actions) AddSource(repo string) *Actions { + a.runCli("proj", "add-source", a.context.name, repo) + return a +} + +func (a *Actions) UpdateProject(updater func(project *v1alpha1.AppProject)) *Actions { + proj, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.ArgoCDNamespace).Get(context.TODO(), a.context.name, v1.GetOptions{}) + require.NoError(a.context.t, err) + updater(proj) + _, err = fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.ArgoCDNamespace).Update(context.TODO(), proj, v1.UpdateOptions{}) + require.NoError(a.context.t, err) + return a +} + func (a *Actions) Name(name string) *Actions { a.context.name = name return a @@ -72,4 +96,7 @@ func (a *Actions) Then() *Consequences { func (a *Actions) runCli(args ...string) { a.context.t.Helper() a.lastOutput, a.lastError = fixture.RunCli(args...) + if !a.ignoreErrors { + require.Empty(a.context.t, a.lastError) + } }