diff --git a/Makefile b/Makefile index 37e6315ed9c57..003e2c34cc170 100644 --- a/Makefile +++ b/Makefile @@ -456,6 +456,7 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local ARGOCD_IN_CI=$(ARGOCD_IN_CI) \ BIN_MODE=$(ARGOCD_BIN_MODE) \ ARGOCD_APPLICATION_NAMESPACES=argocd-e2e-external \ + ARGOCD_APPLICATIONSET_NAMESPACES=argocd-e2e-external \ ARGOCD_E2E_TEST=true \ goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START} diff --git a/applicationset/controllers/applicationset_controller.go b/applicationset/controllers/applicationset_controller.go index b008c12d3b877..6fd6fc75db0ce 100644 --- a/applicationset/controllers/applicationset_controller.go +++ b/applicationset/controllers/applicationset_controller.go @@ -31,13 +31,16 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/source" "github.com/argoproj/argo-cd/v2/applicationset/generators" "github.com/argoproj/argo-cd/v2/applicationset/utils" "github.com/argoproj/argo-cd/v2/common" "github.com/argoproj/argo-cd/v2/util/db" + "github.com/argoproj/argo-cd/v2/util/glob" argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned" @@ -70,8 +73,9 @@ type ApplicationSetReconciler struct { KubeClientset kubernetes.Interface utils.Policy utils.Renderer - - EnableProgressiveSyncs bool + ArgoCDNamespace string + ApplicationSetNamespaces []string + EnableProgressiveSyncs bool } // +kubebuilder:rbac:groups=argoproj.io,resources=applicationsets,verbs=get;list;watch;create;update;patch;delete @@ -114,7 +118,7 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque parametersGenerated = true - validateErrors, err := r.validateGeneratedApplications(ctx, desiredApplications, applicationSetInfo, req.Namespace) + validateErrors, err := r.validateGeneratedApplications(ctx, desiredApplications, applicationSetInfo) if err != nil { // While some generators may return an error that requires user intervention, // other generators reference external resources that may change to cause @@ -396,7 +400,7 @@ func (r *ApplicationSetReconciler) setApplicationSetStatusCondition(ctx context. // validateGeneratedApplications uses the Argo CD validation functions to verify the correctness of the // generated applications. -func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Context, desiredApplications []argov1alpha1.Application, applicationSetInfo argov1alpha1.ApplicationSet, namespace string) (map[int]error, error) { +func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Context, desiredApplications []argov1alpha1.Application, applicationSetInfo argov1alpha1.ApplicationSet) (map[int]error, error) { errorsByIndex := map[int]error{} namesSet := map[string]bool{} for i, app := range desiredApplications { @@ -408,7 +412,7 @@ func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Con continue } - proj, err := r.ArgoAppClientset.ArgoprojV1alpha1().AppProjects(namespace).Get(ctx, app.Spec.GetProject(), metav1.GetOptions{}) + proj, err := r.ArgoAppClientset.ArgoprojV1alpha1().AppProjects(r.ArgoCDNamespace).Get(ctx, app.Spec.GetProject(), metav1.GetOptions{}) if err != nil { if apierr.IsNotFound(err) { errorsByIndex[i] = fmt.Errorf("application references project %s which does not exist", app.Spec.Project) @@ -417,7 +421,7 @@ func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Con return nil, err } - if err := utils.ValidateDestination(ctx, &app.Spec.Destination, r.KubeClientset, namespace); err != nil { + if err := utils.ValidateDestination(ctx, &app.Spec.Destination, r.KubeClientset, r.ArgoCDNamespace); err != nil { errorsByIndex[i] = fmt.Errorf("application destination spec is invalid: %s", err.Error()) continue } @@ -512,6 +516,14 @@ func (r *ApplicationSetReconciler) generateApplications(applicationSetInfo argov return res, applicationSetReason, firstError } +func ignoreNotAllowedNamespaces(namespaces []string) predicate.Predicate { + return predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + return glob.MatchStringInList(namespaces, e.Object.GetNamespace(), false) + }, + } +} + func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager) error { if err := mgr.GetFieldIndexer().IndexField(context.TODO(), &argov1alpha1.Application{}, ".metadata.controller", func(rawObj client.Object) []string { // grab the job object, extract the owner... @@ -534,6 +546,7 @@ func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&argov1alpha1.ApplicationSet{}). Owns(&argov1alpha1.Application{}). + WithEventFilter(ignoreNotAllowedNamespaces(r.ApplicationSetNamespaces)). Watches( &source.Kind{Type: &corev1.Secret{}}, &clusterSecretEventHandler{ @@ -655,7 +668,7 @@ func (r *ApplicationSetReconciler) deleteInCluster(ctx context.Context, applicat // settingsMgr := settings.NewSettingsManager(context.TODO(), r.KubeClientset, applicationSet.Namespace) // argoDB := db.NewDB(applicationSet.Namespace, settingsMgr, r.KubeClientset) // clusterList, err := argoDB.ListClusters(ctx) - clusterList, err := utils.ListClusters(ctx, r.KubeClientset, applicationSet.Namespace) + clusterList, err := utils.ListClusters(ctx, r.KubeClientset, r.ArgoCDNamespace) if err != nil { return fmt.Errorf("error listing clusters: %w", err) } @@ -716,7 +729,7 @@ func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx conte var validDestination bool // Detect if the destination is invalid (name doesn't correspond to a matching cluster) - if err := utils.ValidateDestination(ctx, &app.Spec.Destination, r.KubeClientset, applicationSet.Namespace); err != nil { + if err := utils.ValidateDestination(ctx, &app.Spec.Destination, r.KubeClientset, r.ArgoCDNamespace); err != nil { appLog.Warnf("The destination cluster for %s couldn't be found: %v", app.Name, err) validDestination = false } else { diff --git a/applicationset/controllers/applicationset_controller_test.go b/applicationset/controllers/applicationset_controller_test.go index 76e46659d9639..deaf6ff98874e 100644 --- a/applicationset/controllers/applicationset_controller_test.go +++ b/applicationset/controllers/applicationset_controller_test.go @@ -1739,13 +1739,14 @@ func TestValidateGeneratedApplications(t *testing.T) { Recorder: record.NewFakeRecorder(1), Generators: map[string]generators.Generator{}, ArgoDB: &argoDBMock, + ArgoCDNamespace: "namespace", ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...), KubeClientset: kubeclientset, } appSetInfo := argov1alpha1.ApplicationSet{} - validationErrors, _ := r.validateGeneratedApplications(context.TODO(), cc.apps, appSetInfo, "namespace") + validationErrors, _ := r.validateGeneratedApplications(context.TODO(), cc.apps, appSetInfo) var errorMessages []string for _, v := range validationErrors { errorMessages = append(errorMessages, v.Error()) @@ -1847,6 +1848,7 @@ func TestReconcilerValidationErrorBehaviour(t *testing.T) { ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...), KubeClientset: kubeclientset, Policy: &utils.SyncPolicy{}, + ArgoCDNamespace: "argocd", } req := ctrl.Request{ @@ -2135,6 +2137,7 @@ func TestPolicies(t *testing.T) { "List": generators.NewListGenerator(), }, ArgoDB: &argoDBMock, + ArgoCDNamespace: "argocd", ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...), KubeClientset: kubeclientset, Policy: policy, diff --git a/applicationset/utils/clusterUtils.go b/applicationset/utils/clusterUtils.go index e06d7b39fac50..ee9832f533e5e 100644 --- a/applicationset/utils/clusterUtils.go +++ b/applicationset/utils/clusterUtils.go @@ -50,10 +50,10 @@ const ( // ValidateDestination checks: // if we used destination name we infer the server url // if we used both name and server then we return an invalid spec error -func ValidateDestination(ctx context.Context, dest *appv1.ApplicationDestination, clientset kubernetes.Interface, namespace string) error { +func ValidateDestination(ctx context.Context, dest *appv1.ApplicationDestination, clientset kubernetes.Interface, argoCDNamespace string) error { if dest.Name != "" { if dest.Server == "" { - server, err := getDestinationServer(ctx, dest.Name, clientset, namespace) + server, err := getDestinationServer(ctx, dest.Name, clientset, argoCDNamespace) if err != nil { return fmt.Errorf("unable to find destination server: %v", err) } @@ -70,11 +70,11 @@ func ValidateDestination(ctx context.Context, dest *appv1.ApplicationDestination return nil } -func getDestinationServer(ctx context.Context, clusterName string, clientset kubernetes.Interface, namespace string) (string, error) { +func getDestinationServer(ctx context.Context, clusterName string, clientset kubernetes.Interface, argoCDNamespace string) (string, error) { // settingsMgr := settings.NewSettingsManager(context.TODO(), clientset, namespace) // argoDB := db.NewDB(namespace, settingsMgr, clientset) // clusterList, err := argoDB.ListClusters(ctx) - clusterList, err := ListClusters(ctx, clientset, namespace) + clusterList, err := ListClusters(ctx, clientset, argoCDNamespace) if err != nil { return "", err } diff --git a/assets/swagger.json b/assets/swagger.json index 4bbb97c796914..e7c7b66448f2e 100644 --- a/assets/swagger.json +++ b/assets/swagger.json @@ -1713,6 +1713,12 @@ "description": "the selector to restrict returned list to applications only with matched labels.", "name": "selector", "in": "query" + }, + { + "type": "string", + "description": "The application set namespace. Default empty is argocd control plane namespace.", + "name": "appsetNamespace", + "in": "query" } ], "responses": { @@ -1781,6 +1787,12 @@ "name": "name", "in": "path", "required": true + }, + { + "type": "string", + "description": "The application set namespace. Default empty is argocd control plane namespace.", + "name": "appsetNamespace", + "in": "query" } ], "responses": { @@ -1810,6 +1822,12 @@ "name": "name", "in": "path", "required": true + }, + { + "type": "string", + "description": "The application set namespace. Default empty is argocd control plane namespace.", + "name": "appsetNamespace", + "in": "query" } ], "responses": { diff --git a/cmd/argocd-applicationset-controller/commands/applicationset_controller.go b/cmd/argocd-applicationset-controller/commands/applicationset_controller.go index 618acbd3af103..0ff387b11e44a 100644 --- a/cmd/argocd-applicationset-controller/commands/applicationset_controller.go +++ b/cmd/argocd-applicationset-controller/commands/applicationset_controller.go @@ -9,7 +9,6 @@ import ( "github.com/argoproj/pkg/stats" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/cache" "github.com/argoproj/argo-cd/v2/applicationset/controllers" "github.com/argoproj/argo-cd/v2/applicationset/generators" @@ -46,17 +45,17 @@ func getSubmoduleEnabled() bool { func NewCommand() *cobra.Command { var ( - clientConfig clientcmd.ClientConfig - metricsAddr string - probeBindAddr string - webhookAddr string - enableLeaderElection bool - namespace string - argocdRepoServer string - policy string - debugLog bool - dryRun bool - enableProgressiveSyncs bool + clientConfig clientcmd.ClientConfig + metricsAddr string + probeBindAddr string + webhookAddr string + enableLeaderElection bool + applicationSetNamespaces *[]string + argocdRepoServer string + policy string + debugLog bool + dryRun bool + enableProgressiveSyncs bool ) scheme := runtime.NewScheme() _ = clientgoscheme.AddToScheme(scheme) @@ -70,6 +69,8 @@ func NewCommand() *cobra.Command { vers := common.GetVersion() namespace, _, err := clientConfig.Namespace() + *applicationSetNamespaces = append(*applicationSetNamespaces, namespace) + errors.CheckError(err) vers.LogStartupInfo( "ArgoCD ApplicationSet Controller", @@ -94,19 +95,25 @@ func NewCommand() *cobra.Command { os.Exit(1) } + // By default watch all namespace + var watchedNamespace string = "" + + // If the applicationset-namespaces contains only one namespace it corresponds to the current namespace + if len(*applicationSetNamespaces) == 1 { + watchedNamespace = (*applicationSetNamespaces)[0] + } + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ - Scheme: scheme, - MetricsBindAddress: metricsAddr, - // Our cache and thus watches and client queries are restricted to the namespace we're running in. This assumes - // the applicationset controller is in the same namespace as argocd, which should be the same namespace of - // all cluster Secrets and Applications we interact with. - NewCache: cache.MultiNamespacedCacheBuilder([]string{namespace}), + Scheme: scheme, + MetricsBindAddress: metricsAddr, + Namespace: watchedNamespace, HealthProbeBindAddress: probeBindAddr, Port: 9443, LeaderElection: enableLeaderElection, LeaderElectionID: "58ac56fa.applicationsets.argoproj.io", DryRunClient: dryRun, }) + if err != nil { log.Error(err, "unable to start manager") os.Exit(1) @@ -169,16 +176,18 @@ func NewCommand() *cobra.Command { go func() { errors.CheckError(askPassServer.Run(askpass.SocketPath)) }() if err = (&controllers.ApplicationSetReconciler{ - Generators: topLevelGenerators, - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - Recorder: mgr.GetEventRecorderFor("applicationset-controller"), - Renderer: &utils.Render{}, - Policy: policyObj, - ArgoAppClientset: appSetConfig, - KubeClientset: k8sClient, - ArgoDB: argoCDDB, - EnableProgressiveSyncs: enableProgressiveSyncs, + Generators: topLevelGenerators, + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("applicationset-controller"), + Renderer: &utils.Render{}, + Policy: policyObj, + ArgoAppClientset: appSetConfig, + KubeClientset: k8sClient, + ArgoDB: argoCDDB, + ArgoCDNamespace: namespace, + ApplicationSetNamespaces: *applicationSetNamespaces, + EnableProgressiveSyncs: enableProgressiveSyncs, }).SetupWithManager(mgr); err != nil { log.Error(err, "unable to create controller", "controller", "ApplicationSet") os.Exit(1) @@ -200,7 +209,7 @@ func NewCommand() *cobra.Command { command.Flags().BoolVar(&enableLeaderElection, "enable-leader-election", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_LEADER_ELECTION", false), "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") - command.Flags().StringVar(&namespace, "namespace", env.StringFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACE", ""), "Argo CD repo namespace (default: argocd)") + applicationSetNamespaces = command.Flags().StringArray("applicationset-namespaces", env.StringsFromEnv("ARGOCD_APPLICATIONSET_NAMESPACES", []string{}, ","), "Argo CD applicationset namespaces") command.Flags().StringVar(&argocdRepoServer, "argocd-repo-server", env.StringFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_REPO_SERVER", common.DefaultRepoServerAddr), "Argo CD repo server address") command.Flags().StringVar(&policy, "policy", env.StringFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_POLICY", "sync"), "Modify how application is synced between the generator and the cluster. Default is 'sync' (create & update & delete), options: 'create-only', 'create-update' (no deletion), 'create-delete' (no update)") command.Flags().BoolVar(&debugLog, "debug", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_DEBUG", false), "Print debug logs. Takes precedence over loglevel") diff --git a/cmd/argocd/commands/app.go b/cmd/argocd/commands/app.go index aa54345ba2b00..058dd8eae3c57 100644 --- a/cmd/argocd/commands/app.go +++ b/cmd/argocd/commands/app.go @@ -282,7 +282,7 @@ func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com conn, appIf := acdClient.NewApplicationClientOrDie() defer argoio.Close(conn) - appName, appNs := argo.ParseAppQualifiedName(args[0], "") + appName, appNs := argo.ParseFromQualifiedName(args[0], "") app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{ Name: &appName, Refresh: getRefreshType(refresh, hardRefresh), @@ -366,7 +366,7 @@ func NewApplicationLogsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co acdClient := headless.NewClientOrDie(clientOpts, c) conn, appIf := acdClient.NewApplicationClientOrDie() defer argoio.Close(conn) - appName, appNs := argo.ParseAppQualifiedName(args[0], "") + appName, appNs := argo.ParseFromQualifiedName(args[0], "") retry := true for retry { @@ -610,7 +610,7 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com c.HelpFunc()(c, args) os.Exit(1) } - appName, appNs := argo.ParseAppQualifiedName(args[0], "") + appName, appNs := argo.ParseFromQualifiedName(args[0], "") argocdClient := headless.NewClientOrDie(clientOpts, c) conn, appIf := argocdClient.NewApplicationClientOrDie() defer argoio.Close(conn) @@ -675,7 +675,7 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C c.HelpFunc()(c, args) os.Exit(1) } - appName, appNs := argo.ParseAppQualifiedName(args[0], "") + appName, appNs := argo.ParseFromQualifiedName(args[0], "") conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie() defer argoio.Close(conn) app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{Name: &appName, AppNamespace: &appNs}) @@ -909,7 +909,7 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co clientset := headless.NewClientOrDie(clientOpts, c) conn, appIf := clientset.NewApplicationClientOrDie() defer argoio.Close(conn) - appName, appNs := argo.ParseAppQualifiedName(args[0], "") + appName, appNs := argo.ParseFromQualifiedName(args[0], "") app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{ Name: &appName, Refresh: getRefreshType(refresh, hardRefresh), @@ -1152,7 +1152,7 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra. } for _, appFullName := range appNames { - appName, appNs := argo.ParseAppQualifiedName(appFullName, "") + appName, appNs := argo.ParseFromQualifiedName(appFullName, "") appDeleteReq := applicationpkg.ApplicationDeleteRequest{ Name: &appName, AppNamespace: &appNs, @@ -1580,7 +1580,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co } for _, appQualifiedName := range appNames { - appName, appNs := argo.ParseAppQualifiedName(appQualifiedName, "") + appName, appNs := argo.ParseFromQualifiedName(appQualifiedName, "") if len(selectedLabels) > 0 { q := applicationpkg.ApplicationManifestQuery{ @@ -1970,7 +1970,7 @@ func waitOnApplicationStatus(ctx context.Context, acdClient argocdclient.Client, // time when the sync status lags behind when an operation completes refresh := false - appRealName, appNs := argo.ParseAppQualifiedName(appName, "") + appRealName, appNs := argo.ParseFromQualifiedName(appName, "") printFinalStatus := func(app *argoappv1.Application) *argoappv1.Application { var err error @@ -2162,7 +2162,7 @@ func NewApplicationHistoryCommand(clientOpts *argocdclient.ClientOptions) *cobra } conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie() defer argoio.Close(conn) - appName, appNs := argo.ParseAppQualifiedName(args[0], "") + appName, appNs := argo.ParseFromQualifiedName(args[0], "") app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{ Name: &appName, AppNamespace: &appNs, @@ -2213,7 +2213,7 @@ func NewApplicationRollbackCommand(clientOpts *argocdclient.ClientOptions) *cobr c.HelpFunc()(c, args) os.Exit(1) } - appName, appNs := argo.ParseAppQualifiedName(args[0], "") + appName, appNs := argo.ParseFromQualifiedName(args[0], "") var err error depID := -1 if len(args) > 1 { @@ -2295,7 +2295,7 @@ func NewApplicationManifestsCommand(clientOpts *argocdclient.ClientOptions) *cob c.HelpFunc()(c, args) os.Exit(1) } - appName, appNs := argo.ParseAppQualifiedName(args[0], "") + appName, appNs := argo.ParseFromQualifiedName(args[0], "") clientset := headless.NewClientOrDie(clientOpts, c) conn, appIf := clientset.NewApplicationClientOrDie() defer argoio.Close(conn) @@ -2377,7 +2377,7 @@ func NewApplicationTerminateOpCommand(clientOpts *argocdclient.ClientOptions) *c c.HelpFunc()(c, args) os.Exit(1) } - appName, appNs := argo.ParseAppQualifiedName(args[0], "") + appName, appNs := argo.ParseFromQualifiedName(args[0], "") conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie() defer argoio.Close(conn) _, err := appIf.TerminateOperation(ctx, &applicationpkg.OperationTerminateRequest{ @@ -2402,7 +2402,7 @@ func NewApplicationEditCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co c.HelpFunc()(c, args) os.Exit(1) } - appName, appNs := argo.ParseAppQualifiedName(args[0], "") + appName, appNs := argo.ParseFromQualifiedName(args[0], "") conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie() defer argoio.Close(conn) app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{ @@ -2465,7 +2465,7 @@ func NewApplicationPatchCommand(clientOpts *argocdclient.ClientOptions) *cobra.C c.HelpFunc()(c, args) os.Exit(1) } - appName, appNs := argo.ParseAppQualifiedName(args[0], "") + appName, appNs := argo.ParseFromQualifiedName(args[0], "") conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie() defer argoio.Close(conn) diff --git a/cmd/argocd/commands/app_actions.go b/cmd/argocd/commands/app_actions.go index 7145e2a52c658..bc10408baaf0e 100644 --- a/cmd/argocd/commands/app_actions.go +++ b/cmd/argocd/commands/app_actions.go @@ -65,7 +65,7 @@ func NewApplicationResourceActionsListCommand(clientOpts *argocdclient.ClientOpt c.HelpFunc()(c, args) os.Exit(1) } - appName, appNs := argo.ParseAppQualifiedName(args[0], "") + appName, appNs := argo.ParseFromQualifiedName(args[0], "") conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie() defer io.Close(conn) resources, err := getActionableResourcesForApplication(appIf, ctx, &appNs, &appName) @@ -151,7 +151,7 @@ func NewApplicationResourceActionsRunCommand(clientOpts *argocdclient.ClientOpti c.HelpFunc()(c, args) os.Exit(1) } - appName, appNs := argo.ParseAppQualifiedName(args[0], "") + appName, appNs := argo.ParseFromQualifiedName(args[0], "") actionName := args[1] conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie() diff --git a/cmd/argocd/commands/app_resources.go b/cmd/argocd/commands/app_resources.go index 02c1054b6372d..60ba6efff406e 100644 --- a/cmd/argocd/commands/app_resources.go +++ b/cmd/argocd/commands/app_resources.go @@ -54,7 +54,7 @@ func NewApplicationPatchResourceCommand(clientOpts *argocdclient.ClientOptions) c.HelpFunc()(c, args) os.Exit(1) } - appName, appNs := argo.ParseAppQualifiedName(args[0], "") + appName, appNs := argo.ParseFromQualifiedName(args[0], "") conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie() defer argoio.Close(conn) @@ -116,7 +116,7 @@ func NewApplicationDeleteResourceCommand(clientOpts *argocdclient.ClientOptions) c.HelpFunc()(c, args) os.Exit(1) } - appName, appNs := argo.ParseAppQualifiedName(args[0], "") + appName, appNs := argo.ParseFromQualifiedName(args[0], "") conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie() defer argoio.Close(conn) @@ -182,7 +182,7 @@ func NewApplicationListResourcesCommand(clientOpts *argocdclient.ClientOptions) os.Exit(1) } listAll := !c.Flag("orphaned").Changed - appName, appNs := argo.ParseAppQualifiedName(args[0], "") + appName, appNs := argo.ParseFromQualifiedName(args[0], "") conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie() defer argoio.Close(conn) appResourceTree, err := appIf.ResourceTree(ctx, &applicationpkg.ResourcesQuery{ diff --git a/cmd/argocd/commands/applicationset.go b/cmd/argocd/commands/applicationset.go index 678d5699b9d19..81382c568b32a 100644 --- a/cmd/argocd/commands/applicationset.go +++ b/cmd/argocd/commands/applicationset.go @@ -16,6 +16,7 @@ import ( argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient" "github.com/argoproj/argo-cd/v2/pkg/apiclient/applicationset" arogappsetv1 "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/cli" "github.com/argoproj/argo-cd/v2/util/errors" "github.com/argoproj/argo-cd/v2/util/grpc" @@ -76,8 +77,10 @@ func NewApplicationSetGetCommand(clientOpts *argocdclient.ClientOptions) *cobra. acdClient := headless.NewClientOrDie(clientOpts, c) conn, appIf := acdClient.NewApplicationSetClientOrDie() defer argoio.Close(conn) - appSetName := args[0] - appSet, err := appIf.Get(ctx, &applicationset.ApplicationSetGetQuery{Name: appSetName}) + + appSetName, appSetNs := argo.ParseFromQualifiedName(args[0], "") + + appSet, err := appIf.Get(ctx, &applicationset.ApplicationSetGetQuery{Name: appSetName, AppsetNamespace: appSetNs}) errors.CheckError(err) switch output { @@ -176,9 +179,10 @@ func NewApplicationSetCreateCommand(clientOpts *argocdclient.ClientOptions) *cob // NewApplicationSetListCommand returns a new instance of an `argocd appset list` command func NewApplicationSetListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { var ( - output string - selector string - projects []string + output string + selector string + projects []string + appSetNamespace string ) var command = &cobra.Command{ Use: "list", @@ -192,7 +196,7 @@ func NewApplicationSetListCommand(clientOpts *argocdclient.ClientOptions) *cobra conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationSetClientOrDie() defer argoio.Close(conn) - appsets, err := appIf.List(ctx, &applicationset.ApplicationSetListQuery{Selector: selector, Projects: projects}) + appsets, err := appIf.List(ctx, &applicationset.ApplicationSetListQuery{Selector: selector, Projects: projects, AppsetNamespace: appSetNamespace}) errors.CheckError(err) appsetList := appsets.Items @@ -213,6 +217,7 @@ func NewApplicationSetListCommand(clientOpts *argocdclient.ClientOptions) *cobra command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: wide|name|json|yaml") command.Flags().StringVarP(&selector, "selector", "l", "", "List applicationsets by label") command.Flags().StringArrayVarP(&projects, "project", "p", []string{}, "Filter by project name") + command.Flags().StringVarP(&appSetNamespace, "appset-namespace", "N", "", "Only list applicationsets in namespace") return command } @@ -245,18 +250,22 @@ func NewApplicationSetDeleteCommand(clientOpts *argocdclient.ClientOptions) *cob if promptFlag.Changed && promptFlag.Value.String() == "true" { noPrompt = true } - for _, appsetName := range args { + for _, appSetQualifiedName := range args { + + appSetName, appSetNs := argo.ParseFromQualifiedName(appSetQualifiedName, "") + appsetDeleteReq := applicationset.ApplicationSetDeleteRequest{ - Name: appsetName, + Name: appSetName, + AppsetNamespace: appSetNs, } if isTerminal && !noPrompt { var lowercaseAnswer string if numOfApps == 1 { - lowercaseAnswer = cli.AskToProceedS("Are you sure you want to delete '" + appsetName + "' and all its Applications? [y/n] ") + lowercaseAnswer = cli.AskToProceedS("Are you sure you want to delete '" + appSetQualifiedName + "' and all its Applications? [y/n] ") } else { if !isConfirmAll { - lowercaseAnswer = cli.AskToProceedS("Are you sure you want to delete '" + appsetName + "' and all its Applications? [y/n/A] where 'A' is to delete all specified ApplicationSets and their Applications without prompting") + lowercaseAnswer = cli.AskToProceedS("Are you sure you want to delete '" + appSetQualifiedName + "' and all its Applications? [y/n/A] where 'A' is to delete all specified ApplicationSets and their Applications without prompting") if lowercaseAnswer == "a" || lowercaseAnswer == "all" { lowercaseAnswer = "y" isConfirmAll = true @@ -268,9 +277,9 @@ func NewApplicationSetDeleteCommand(clientOpts *argocdclient.ClientOptions) *cob if lowercaseAnswer == "y" || lowercaseAnswer == "yes" { _, err := appIf.Delete(ctx, &appsetDeleteReq) errors.CheckError(err) - fmt.Printf("applicationset '%s' deleted\n", appsetName) + fmt.Printf("applicationset '%s' deleted\n", appSetQualifiedName) } else { - fmt.Println("The command to delete '" + appsetName + "' was cancelled.") + fmt.Println("The command to delete '" + appSetQualifiedName + "' was cancelled.") } } else { _, err := appIf.Delete(ctx, &appsetDeleteReq) @@ -286,7 +295,7 @@ func NewApplicationSetDeleteCommand(clientOpts *argocdclient.ClientOptions) *cob // Print simple list of application names func printApplicationSetNames(apps []arogappsetv1.ApplicationSet) { for _, app := range apps { - fmt.Println(app.Name) + fmt.Println(app.QualifiedName()) } } @@ -294,12 +303,12 @@ func printApplicationSetNames(apps []arogappsetv1.ApplicationSet) { func printApplicationSetTable(apps []arogappsetv1.ApplicationSet, output *string) { w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) var fmtStr string - headers := []interface{}{"NAME", "NAMESPACE", "PROJECT", "SYNCPOLICY", "CONDITIONS"} + headers := []interface{}{"NAME", "PROJECT", "SYNCPOLICY", "CONDITIONS"} if *output == "wide" { - fmtStr = "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" + fmtStr = "%s\t%s\t%s\t%s\t%s\t%s\t%s\n" headers = append(headers, "REPO", "PATH", "TARGET") } else { - fmtStr = "%s\t%s\t%s\t%s\t%s\n" + fmtStr = "%s\t%s\t%s\t%s\n" } _, _ = fmt.Fprintf(w, fmtStr, headers...) for _, app := range apps { @@ -310,8 +319,7 @@ func printApplicationSetTable(apps []arogappsetv1.ApplicationSet, output *string } } vals := []interface{}{ - app.ObjectMeta.Name, - app.ObjectMeta.Namespace, + app.QualifiedName(), app.Spec.Template.Spec.Project, app.Spec.SyncPolicy, conditions, @@ -334,7 +342,7 @@ func getServerForAppSet(appSet *arogappsetv1.ApplicationSet) string { func printAppSetSummaryTable(appSet *arogappsetv1.ApplicationSet) { source := appSet.Spec.Template.Spec.GetSource() - fmt.Printf(printOpFmtStr, "Name:", appSet.Name) + fmt.Printf(printOpFmtStr, "Name:", appSet.QualifiedName()) fmt.Printf(printOpFmtStr, "Project:", appSet.Spec.Template.Spec.GetProject()) fmt.Printf(printOpFmtStr, "Server:", getServerForAppSet(appSet)) fmt.Printf(printOpFmtStr, "Namespace:", appSet.Spec.Template.Spec.Destination.Namespace) diff --git a/cmd/argocd/commands/applicationset_test.go b/cmd/argocd/commands/applicationset_test.go index 9937b183e5c29..ef26174715fb0 100644 --- a/cmd/argocd/commands/applicationset_test.go +++ b/cmd/argocd/commands/applicationset_test.go @@ -16,10 +16,16 @@ func TestPrintApplicationSetNames(t *testing.T) { Name: "test", }, } - printApplicationSetNames([]arogappsetv1.ApplicationSet{*appSet, *appSet}) + appSet2 := &arogappsetv1.ApplicationSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "team-one", + Name: "test", + }, + } + printApplicationSetNames([]arogappsetv1.ApplicationSet{*appSet, *appSet2}) return nil }) - expectation := "test\ntest\n" + expectation := "test\nteam-one/test\n" if output != expectation { t.Fatalf("Incorrect print params output %q, should be %q", output, expectation) } @@ -60,11 +66,46 @@ func TestPrintApplicationSetTable(t *testing.T) { }, }, } + + app2 := &arogappsetv1.ApplicationSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "app-name", + Namespace: "team-two", + }, + Spec: arogappsetv1.ApplicationSetSpec{ + Generators: []arogappsetv1.ApplicationSetGenerator{ + arogappsetv1.ApplicationSetGenerator{ + Git: &arogappsetv1.GitGenerator{ + RepoURL: "https://github.com/argoproj/argo-cd.git", + Revision: "head", + Directories: []arogappsetv1.GitDirectoryGeneratorItem{ + arogappsetv1.GitDirectoryGeneratorItem{ + Path: "applicationset/examples/git-generator-directory/cluster-addons/*", + }, + }, + }, + }, + }, + Template: arogappsetv1.ApplicationSetTemplate{ + Spec: v1alpha1.ApplicationSpec{ + Project: "default", + }, + }, + }, + Status: arogappsetv1.ApplicationSetStatus{ + Conditions: []arogappsetv1.ApplicationSetCondition{ + arogappsetv1.ApplicationSetCondition{ + Status: v1alpha1.ApplicationSetConditionStatusTrue, + Type: arogappsetv1.ApplicationSetConditionResourcesUpToDate, + }, + }, + }, + } output := "table" - printApplicationSetTable([]arogappsetv1.ApplicationSet{*app, *app}, &output) + printApplicationSetTable([]arogappsetv1.ApplicationSet{*app, *app2}, &output) return nil }) assert.NoError(t, err) - expectation := "NAME NAMESPACE PROJECT SYNCPOLICY CONDITIONS\napp-name default nil [{ResourcesUpToDate True }]\napp-name default nil [{ResourcesUpToDate True }]\n" + expectation := "NAME PROJECT SYNCPOLICY CONDITIONS\napp-name default nil [{ResourcesUpToDate True }]\nteam-two/app-name default nil [{ResourcesUpToDate True }]\n" assert.Equal(t, expectation, output) } diff --git a/cmd/util/app.go b/cmd/util/app.go index e80950533b054..eab050985b817 100644 --- a/cmd/util/app.go +++ b/cmd/util/app.go @@ -576,7 +576,7 @@ func constructAppsBaseOnName(appName string, labels, annotations, args []string, } appName = args[0] } - appName, appNs := argo.ParseAppQualifiedName(appName, "") + appName, appNs := argo.ParseFromQualifiedName(appName, "") app = &argoappv1.Application{ TypeMeta: v1.TypeMeta{ Kind: application.ApplicationKind, diff --git a/docs/operator-manual/applicationset/Appset-Any-Namespace.md b/docs/operator-manual/applicationset/Appset-Any-Namespace.md new file mode 100644 index 0000000000000..f61158d8e6da0 --- /dev/null +++ b/docs/operator-manual/applicationset/Appset-Any-Namespace.md @@ -0,0 +1,177 @@ +# ApplicationSet in any namespace + +**Current feature state**: Beta + +!!! warning + Please read this documentation carefully before you enable this feature. Misconfiguration could lead to potential security issues. + +## Introduction + +As of version 2.7, Argo CD supports managing `ApplicationSet` resources in namespaces other than the control plane's namespace (which is usually `argocd`), but this feature has to be explicitly enabled and configured appropriately. + +Argo CD administrators can define a certain set of namespaces where `ApplicationSet` resources may be created, updated and reconciled in. + +As Applications generated by an ApplicationSet are generated in the same namespace as the ApplicationSet itself, this works in combination with [App in any namespace](../app-any-namespace.md). + +## Prerequisites + +### App in any namespace configured + +This feature needs [App in any namespace](../app-any-namespace.md) feature activated. The list of namespace must be the same. + +### Cluster-scoped Argo CD installation + +This feature can only be enabled and used when your Argo CD ApplicationSet controller is installed as a cluster-wide instance, so it has permissions to list and manipulate resources on a cluster scope. It will *not* work with an Argo CD installed in namespace-scoped mode. + +## Implementation details + +### Overview + +In order for an ApplicationSet to be managed and reconciled outside the Argo CD's control plane namespace, two prerequisites must match: + +1. The `ApplicationSet`'s namespace must be explicitly enabled using the `--applicationset-namespaces` parameter for the `argocd-applicationset-controller`. This parameter controls the list of namespaces that Argo CD will be allowed to source `ApplicationSet` resources from globally. +2. The number of namespaces covered by the `--applicationset-namespaces` must be entirely covered by the [App in any namespace](../app-any-namespace.md), otherwise the generated Applications generated outside the allowed Application namespaces won't be reconciled. + +`ApplicationSets` in different namespaces can be created and managed just like any other `ApplicationSet` in the `argocd` namespace previously, either declaratively or through the Argo CD API (e.g. using the CLI, the web UI, the REST API, etc). + +### Reconfigure Argo CD to allow certain namespaces + +#### Change workload startup parameters + +In order to enable this feature, the Argo CD administrator must reconfigure the and `argocd-applicationset-controller` workloads to add the `--applicationset-namespaces` parameter to the container's startup command. + +### Safely template project + +As [App in any namespace](../app-any-namespace.md) is a prerequisite, it is possible to safely template project. + +Let's take an example with two teams and an infra project: + +```yaml +kind: AppProject +apiVersion: argoproj.io/v1alpha1 +metadata: + name: infra-project + namespace: argocd +spec: + destinations: + - namespace: '*' +``` + +```yaml +kind: AppProject +apiVersion: argoproj.io/v1alpha1 +metadata: + name: team-one-project + namespace: argocd +spec: + sourceNamespaces: + - team-one-cd +``` + +```yaml +kind: AppProject +apiVersion: argoproj.io/v1alpha1 +metadata: + name: team-two-project + namespace: argocd +spec: + sourceNamespaces: + - team-two-cd +``` + +Creating following `ApplicationSet` generates two Applications `infra-escalation` and `team-two-escalation`. Both will be rejected as they are outside `argocd` namespace, therefore `sourceNamespaces` will be checked + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: team-one-product-one + namespace: team-one-cd +spec: + list: + - id: infra + project: infra-project + - id: team-two + project: team-two-project + template: + metadata: + name: '{{name}}-escalation' + spec: + project: "{{project}}" +``` + +### ApplicationSet names + +For the CLI, applicationSets are now referred to and displayed as in the format `/`. + +For backwards compatibility, if the namespace of the ApplicationSet is the control plane's namespace (i.e. `argocd`), the `` can be omitted from the applicationset name when referring to it. For example, the application names `argocd/someappset` and `someappset` are semantically the same and refer to the same application in the CLI and the UI. + +### Applicationsets RBAC + +The RBAC syntax for Application objects has been changed from `/` to `//` to accomodate the need to restrict access based on the source namespace of the Application to be managed. + +For backwards compatibility, Applications in the argocd namespace can still be refered to as `/` in the RBAC policy rules. + +Wildcards do not make any distinction between project and applicationset namespaces yet. For example, the following RBAC rule would match any application belonging to project foo, regardless of the namespace it is created in: + + +``` +p, somerole, applicationsets, get, foo/*, allow +``` + +If you want to restrict access to be granted only to `ApplicationSets` with project `foo` within namespace `bar`, the rule would need to be adapted as follows: + +``` +p, somerole, applicationsets, get, foo/bar/*, allow +``` + +## Managing applicationSets in other namespaces + +### Using the CLI + +You can use all existing Argo CD CLI commands for managing applications in other namespaces, exactly as you would use the CLI to manage applications in the control plane's namespace. + +For example, to retrieve the `ApplicationSet` named `foo` in the namespace `bar`, you can use the following CLI command: + +```shell +argocd appset get foo/bar +``` + +Likewise, to manage this applicationSet, keep referring to it as `foo/bar`: + +```bash +# Delete the application +argocd appset delete foo/bar +``` + +There is no change on the create command as it is using a file. You just need to add the namespace in the `metadata.namespace` field. + +As stated previously, for applicationSets in the Argo CD's control plane namespace, you can omit the namespace from the application name. + +### Using the REST API + +If you are using the REST API, the namespace for `ApplicationSet` cannot be specified as the application name, and resources need to be specified using the optional `appNamespace` query parameter. For example, to work with the `ApplicationSet` resource named `foo` in the namespace `bar`, the request would look like follows: + +```bash +GET /api/v1/applicationsets/foo?appsetNamespace=bar +``` + +For other operations such as `POST` and `PUT`, the `appNamespace` parameter must be part of the request's payload. + +For `ApplicationSet` resources in the control plane namespace, this parameter can be omitted. + +## Secrets consideration + +By allowing ApplicationSet in any namespace you must be aware that clusters, API token secrets (etc...) can be discovered and used. + +Example: + +Following will discover all clusters + +```yaml +spec: + generators: + - clusters: {} # Automatically use all clusters defined within Argo CD +``` + +If you don't want to allow users to discover secrets with ApplicationSets from other namespaces you may consider deploying ArgoCD in namespace scope or use OPA rules. \ No newline at end of file diff --git a/docs/user-guide/commands/argocd_appset_list.md b/docs/user-guide/commands/argocd_appset_list.md index 1320ccc1b7883..2fb0e3f593633 100644 --- a/docs/user-guide/commands/argocd_appset_list.md +++ b/docs/user-guide/commands/argocd_appset_list.md @@ -16,10 +16,11 @@ argocd appset list [flags] ### Options ``` - -h, --help help for list - -o, --output string Output format. One of: wide|name|json|yaml (default "wide") - -p, --project stringArray Filter by project name - -l, --selector string List applicationsets by label + -N, --appset-namespace string Only list applicationsets in namespace + -h, --help help for list + -o, --output string Output format. One of: wide|name|json|yaml (default "wide") + -p, --project stringArray Filter by project name + -l, --selector string List applicationsets by label ``` ### Options inherited from parent commands diff --git a/mkdocs.yml b/mkdocs.yml index 05a04fa35ed41..8794dae84d630 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -105,6 +105,7 @@ nav: - Controlling Resource Modification: operator-manual/applicationset/Controlling-Resource-Modification.md - Application Pruning & Resource Deletion: operator-manual/applicationset/Application-Deletion.md - Progressive Syncs: operator-manual/applicationset/Progressive-Syncs.md + - ApplicationSet in any namespace: operator-manual/applicationset/Appset-Any-Namespace.md - Server Configuration Parameters: - operator-manual/server-commands/argocd-server.md - operator-manual/server-commands/argocd-application-controller.md diff --git a/pkg/apiclient/apiclient.go b/pkg/apiclient/apiclient.go index 5f122433ee285..65685eef26b91 100644 --- a/pkg/apiclient/apiclient.go +++ b/pkg/apiclient/apiclient.go @@ -809,7 +809,7 @@ func (c *client) NewAccountClientOrDie() (io.Closer, accountpkg.AccountServiceCl func (c *client) WatchApplicationWithRetry(ctx context.Context, appName string, revision string) chan *argoappv1.ApplicationWatchEvent { appEventsCh := make(chan *argoappv1.ApplicationWatchEvent) cancelled := false - appName, appNs := argo.ParseAppQualifiedName(appName, "") + appName, appNs := argo.ParseFromQualifiedName(appName, "") go func() { defer close(appEventsCh) for !cancelled { diff --git a/pkg/apiclient/applicationset/applicationset.pb.go b/pkg/apiclient/applicationset/applicationset.pb.go index f24e802789c2d..8f717d1f6920f 100644 --- a/pkg/apiclient/applicationset/applicationset.pb.go +++ b/pkg/apiclient/applicationset/applicationset.pb.go @@ -35,7 +35,9 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // ApplicationSetGetQuery is a query for applicationset resources type ApplicationSetGetQuery struct { // the applicationsets's name - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // The application set namespace. Default empty is argocd control plane namespace + AppsetNamespace string `protobuf:"bytes,2,opt,name=appsetNamespace,proto3" json:"appsetNamespace,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -81,11 +83,20 @@ func (m *ApplicationSetGetQuery) GetName() string { return "" } +func (m *ApplicationSetGetQuery) GetAppsetNamespace() string { + if m != nil { + return m.AppsetNamespace + } + return "" +} + type ApplicationSetListQuery struct { // the project names to restrict returned list applicationsets Projects []string `protobuf:"bytes,1,rep,name=projects,proto3" json:"projects,omitempty"` // the selector to restrict returned list to applications only with matched labels - Selector string `protobuf:"bytes,2,opt,name=selector,proto3" json:"selector,omitempty"` + Selector string `protobuf:"bytes,2,opt,name=selector,proto3" json:"selector,omitempty"` + // The application set namespace. Default empty is argocd control plane namespace + AppsetNamespace string `protobuf:"bytes,3,opt,name=appsetNamespace,proto3" json:"appsetNamespace,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -138,6 +149,13 @@ func (m *ApplicationSetListQuery) GetSelector() string { return "" } +func (m *ApplicationSetListQuery) GetAppsetNamespace() string { + if m != nil { + return m.AppsetNamespace + } + return "" +} + type ApplicationSetResponse struct { Project string `protobuf:"bytes,1,opt,name=project,proto3" json:"project,omitempty"` Applicationset *v1alpha1.ApplicationSet `protobuf:"bytes,2,opt,name=applicationset,proto3" json:"applicationset,omitempty"` @@ -249,7 +267,9 @@ func (m *ApplicationSetCreateRequest) GetUpsert() bool { } type ApplicationSetDeleteRequest struct { - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // The application set namespace. Default empty is argocd control plane namespace + AppsetNamespace string `protobuf:"bytes,2,opt,name=appsetNamespace,proto3" json:"appsetNamespace,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -295,6 +315,13 @@ func (m *ApplicationSetDeleteRequest) GetName() string { return "" } +func (m *ApplicationSetDeleteRequest) GetAppsetNamespace() string { + if m != nil { + return m.AppsetNamespace + } + return "" +} + func init() { proto.RegisterType((*ApplicationSetGetQuery)(nil), "applicationset.ApplicationSetGetQuery") proto.RegisterType((*ApplicationSetListQuery)(nil), "applicationset.ApplicationSetListQuery") @@ -308,39 +335,40 @@ func init() { } var fileDescriptor_eacb9df0ce5738fa = []byte{ - // 501 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x94, 0xcf, 0x6e, 0x13, 0x31, - 0x10, 0xc6, 0xe5, 0xb6, 0x84, 0xd6, 0x48, 0x1c, 0x2c, 0xd1, 0x86, 0x05, 0x85, 0x68, 0x0f, 0xa5, - 0x14, 0xb0, 0x95, 0x70, 0x83, 0x13, 0x7f, 0xa4, 0x0a, 0x29, 0x07, 0xba, 0xbd, 0x71, 0x41, 0xae, - 0x33, 0xda, 0x2e, 0xdd, 0xae, 0x8d, 0xed, 0xac, 0x84, 0x10, 0x17, 0x24, 0x9e, 0x80, 0x27, 0x00, - 0x2e, 0x48, 0x5c, 0x79, 0x08, 0x8e, 0x48, 0xbc, 0x00, 0x8a, 0x78, 0x10, 0x64, 0x6f, 0x36, 0xe9, - 0x5a, 0x69, 0xc3, 0x21, 0xbd, 0x79, 0xd6, 0xe3, 0xf1, 0x6f, 0x3f, 0x7f, 0x33, 0x78, 0xd7, 0x80, - 0x2e, 0x41, 0x33, 0xae, 0x54, 0x9e, 0x09, 0x6e, 0x33, 0x59, 0x18, 0xb0, 0x41, 0x48, 0x95, 0x96, - 0x56, 0x92, 0xab, 0xcd, 0xaf, 0xd1, 0xcd, 0x54, 0xca, 0x34, 0x07, 0xc6, 0x55, 0xc6, 0x78, 0x51, - 0x48, 0x5b, 0xed, 0x54, 0xd9, 0xd1, 0x20, 0xcd, 0xec, 0xd1, 0xe8, 0x90, 0x0a, 0x79, 0xc2, 0xb8, - 0x4e, 0xa5, 0xd2, 0xf2, 0xb5, 0x5f, 0xdc, 0x17, 0x43, 0x56, 0xf6, 0x99, 0x3a, 0x4e, 0xdd, 0x49, - 0x73, 0xfa, 0x2e, 0x56, 0xf6, 0x78, 0xae, 0x8e, 0x78, 0x8f, 0xa5, 0x50, 0x80, 0xe6, 0x16, 0x86, - 0x55, 0xb5, 0xf8, 0x1e, 0xde, 0x7c, 0x3c, 0xcb, 0x3b, 0x00, 0xbb, 0x07, 0x76, 0x7f, 0x04, 0xfa, - 0x2d, 0x21, 0x78, 0xad, 0xe0, 0x27, 0xd0, 0x46, 0x5d, 0xb4, 0xb3, 0x91, 0xf8, 0x75, 0xbc, 0x8f, - 0xb7, 0x9a, 0xd9, 0x83, 0xcc, 0x4c, 0xd2, 0x23, 0xbc, 0xee, 0x48, 0x40, 0x58, 0xd3, 0x46, 0xdd, - 0xd5, 0x9d, 0x8d, 0x64, 0x1a, 0xbb, 0x3d, 0x03, 0x39, 0x08, 0x2b, 0x75, 0x7b, 0xc5, 0x97, 0x9b, - 0xc6, 0xf1, 0x37, 0x14, 0x12, 0x24, 0x60, 0x94, 0x13, 0x82, 0xb4, 0xf1, 0xe5, 0x49, 0x89, 0x09, - 0x44, 0x1d, 0x12, 0x8b, 0x03, 0xcd, 0x7c, 0xd9, 0x2b, 0xfd, 0x01, 0x9d, 0x89, 0x43, 0x6b, 0x71, - 0xfc, 0xe2, 0x95, 0x18, 0xd2, 0xb2, 0x4f, 0xd5, 0x71, 0x4a, 0x9d, 0x38, 0xf4, 0xd4, 0x71, 0x5a, - 0x8b, 0x43, 0x03, 0x8e, 0xe0, 0x8e, 0xf8, 0x3b, 0xc2, 0x37, 0x9a, 0x29, 0x4f, 0x35, 0x70, 0x0b, - 0x09, 0xbc, 0x19, 0x81, 0x99, 0x47, 0x85, 0x2e, 0x9e, 0x8a, 0x6c, 0xe2, 0xd6, 0x48, 0x19, 0xd0, - 0x95, 0x06, 0xeb, 0xc9, 0x24, 0x8a, 0x7b, 0x21, 0xec, 0x33, 0xc8, 0x61, 0x06, 0x3b, 0xe7, 0x79, - 0xfb, 0x9f, 0x2f, 0xe1, 0x6b, 0xcd, 0x33, 0x07, 0xa0, 0xcb, 0x4c, 0x00, 0xf9, 0x8a, 0xf0, 0xea, - 0x1e, 0x58, 0xb2, 0x4d, 0x03, 0x07, 0xcf, 0x37, 0x4f, 0xb4, 0xd4, 0x5f, 0x8e, 0xb7, 0x3f, 0xfc, - 0xfe, 0xfb, 0x69, 0xa5, 0x4b, 0x3a, 0xbe, 0x25, 0xca, 0x5e, 0xd0, 0x46, 0x86, 0xbd, 0x73, 0xf8, - 0xef, 0xc9, 0x17, 0x84, 0xd7, 0x9c, 0x23, 0xc9, 0xed, 0xf3, 0x31, 0xa7, 0xae, 0x8d, 0x5e, 0x2c, - 0x93, 0xd3, 0x95, 0x8d, 0x6f, 0x79, 0xd6, 0xeb, 0x64, 0xeb, 0x0c, 0x56, 0xf2, 0x03, 0xe1, 0x56, - 0xe5, 0x1b, 0x72, 0xf7, 0x7c, 0xcc, 0x86, 0xbb, 0x96, 0x2c, 0x29, 0xf3, 0x98, 0x77, 0xe2, 0xb3, - 0x30, 0x1f, 0x86, 0x36, 0xfb, 0x88, 0x70, 0xab, 0x72, 0xd0, 0x22, 0xec, 0x86, 0xcf, 0xa2, 0x05, - 0x8e, 0xa9, 0x9b, 0xbd, 0x7e, 0xe3, 0xdd, 0x05, 0x6f, 0xfc, 0xe4, 0xf9, 0xcf, 0x71, 0x07, 0xfd, - 0x1a, 0x77, 0xd0, 0x9f, 0x71, 0x07, 0xbd, 0x7c, 0xf4, 0x7f, 0xc3, 0x50, 0xe4, 0x19, 0x14, 0xe1, - 0xf4, 0x3d, 0x6c, 0xf9, 0x11, 0xf8, 0xe0, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb1, 0x22, 0xb1, - 0x96, 0xac, 0x05, 0x00, 0x00, + // 526 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x94, 0xdf, 0x8a, 0x13, 0x31, + 0x14, 0xc6, 0xc9, 0x76, 0xad, 0xbb, 0x11, 0x14, 0x02, 0xee, 0xd6, 0x51, 0x6a, 0x99, 0x8b, 0xb5, + 0xae, 0x98, 0xd0, 0x7a, 0xa7, 0x57, 0xfe, 0x81, 0x45, 0x28, 0xa2, 0xb3, 0xe0, 0x85, 0x5e, 0x48, + 0x76, 0x7a, 0x98, 0x1d, 0x77, 0x3a, 0x89, 0x49, 0x3a, 0x20, 0x8b, 0x37, 0x82, 0x4f, 0xe0, 0x13, + 0xa8, 0x37, 0x82, 0xb7, 0x3e, 0x84, 0x97, 0x82, 0x2f, 0x20, 0xc5, 0x07, 0x91, 0xc9, 0xcc, 0xb4, + 0x3b, 0xa1, 0xdb, 0x0a, 0x76, 0xef, 0x72, 0x26, 0x99, 0x73, 0x7e, 0xf9, 0xf2, 0x9d, 0x83, 0x77, + 0x35, 0xa8, 0x0c, 0x14, 0xe3, 0x52, 0x26, 0x71, 0xc8, 0x4d, 0x2c, 0x52, 0x0d, 0xc6, 0x09, 0xa9, + 0x54, 0xc2, 0x08, 0x72, 0xb1, 0xfe, 0xd5, 0xbb, 0x16, 0x09, 0x11, 0x25, 0xc0, 0xb8, 0x8c, 0x19, + 0x4f, 0x53, 0x61, 0x8a, 0x9d, 0xe2, 0xb4, 0x37, 0x88, 0x62, 0x73, 0x38, 0x3e, 0xa0, 0xa1, 0x18, + 0x31, 0xae, 0x22, 0x21, 0x95, 0x78, 0x6d, 0x17, 0xb7, 0xc3, 0x21, 0xcb, 0xfa, 0x4c, 0x1e, 0x45, + 0xf9, 0x9f, 0xfa, 0x64, 0x2d, 0x96, 0xf5, 0x78, 0x22, 0x0f, 0x79, 0x8f, 0x45, 0x90, 0x82, 0xe2, + 0x06, 0x86, 0x45, 0x36, 0xff, 0x39, 0xde, 0xba, 0x3f, 0x3b, 0xb7, 0x0f, 0x66, 0x0f, 0xcc, 0xb3, + 0x31, 0xa8, 0xb7, 0x84, 0xe0, 0xf5, 0x94, 0x8f, 0xa0, 0x85, 0x3a, 0xa8, 0xbb, 0x19, 0xd8, 0x35, + 0xe9, 0xe2, 0x4b, 0x5c, 0x4a, 0x0d, 0xe6, 0x09, 0x1f, 0x81, 0x96, 0x3c, 0x84, 0xd6, 0x9a, 0xdd, + 0x76, 0x3f, 0xfb, 0xc7, 0x78, 0xbb, 0x9e, 0x77, 0x10, 0xeb, 0x32, 0xb1, 0x87, 0x37, 0x72, 0x66, + 0x08, 0x8d, 0x6e, 0xa1, 0x4e, 0xa3, 0xbb, 0x19, 0x4c, 0xe3, 0x7c, 0x4f, 0x43, 0x02, 0xa1, 0x11, + 0xaa, 0xcc, 0x3c, 0x8d, 0xe7, 0x15, 0x6f, 0xcc, 0x2f, 0xfe, 0x15, 0xb9, 0xb7, 0x0a, 0x40, 0xcb, + 0x5c, 0x5c, 0xd2, 0xc2, 0xe7, 0xcb, 0x62, 0xe5, 0xc5, 0xaa, 0x90, 0x18, 0xec, 0xbc, 0x83, 0x05, + 0xb8, 0xd0, 0x1f, 0xd0, 0x99, 0xe0, 0xb4, 0x12, 0xdc, 0x2e, 0x5e, 0x85, 0x43, 0x9a, 0xf5, 0xa9, + 0x3c, 0x8a, 0x68, 0x2e, 0x38, 0x3d, 0xf1, 0x3b, 0xad, 0x04, 0xa7, 0x0e, 0x87, 0x53, 0xc3, 0xff, + 0x86, 0xf0, 0xd5, 0xfa, 0x91, 0x87, 0x0a, 0xb8, 0x81, 0x00, 0xde, 0x8c, 0x41, 0xcf, 0xa3, 0x42, + 0x67, 0x4f, 0x45, 0xb6, 0x70, 0x73, 0x2c, 0x35, 0xa8, 0x42, 0x83, 0x8d, 0xa0, 0x8c, 0xfc, 0x97, + 0x2e, 0xec, 0x23, 0x48, 0x60, 0x06, 0xfb, 0x5f, 0x96, 0xe9, 0x7f, 0x3a, 0x87, 0x2f, 0xd7, 0xb3, + 0xef, 0x83, 0xca, 0xe2, 0x10, 0xc8, 0x17, 0x84, 0x1b, 0x7b, 0x60, 0xc8, 0x0e, 0x75, 0xfa, 0x67, + 0xbe, 0x75, 0xbd, 0x95, 0x8a, 0xe3, 0xef, 0xbc, 0xff, 0xf5, 0xe7, 0xe3, 0x5a, 0x87, 0xb4, 0x6d, + 0x43, 0x66, 0x3d, 0xa7, 0x89, 0x35, 0x3b, 0xce, 0x2f, 0xfa, 0x8e, 0x7c, 0x46, 0x78, 0x3d, 0x77, + 0x39, 0xb9, 0xb1, 0x18, 0x73, 0xda, 0x09, 0xde, 0xd3, 0x55, 0x72, 0xe6, 0x69, 0xfd, 0xeb, 0x96, + 0xf5, 0x0a, 0xd9, 0x3e, 0x85, 0x95, 0x7c, 0x47, 0xb8, 0x59, 0x38, 0x8c, 0xdc, 0x5a, 0x8c, 0x59, + 0xf3, 0xe1, 0x8a, 0x25, 0x65, 0x16, 0xf3, 0xa6, 0x7f, 0x1a, 0xe6, 0x5d, 0xd7, 0x90, 0x1f, 0x10, + 0x6e, 0x16, 0x5e, 0x5b, 0x86, 0x5d, 0x73, 0xa4, 0xb7, 0xc4, 0x31, 0xd5, 0x58, 0xa8, 0xde, 0x78, + 0x77, 0xc9, 0x1b, 0x3f, 0x78, 0xfc, 0x63, 0xd2, 0x46, 0x3f, 0x27, 0x6d, 0xf4, 0x7b, 0xd2, 0x46, + 0x2f, 0xee, 0xfd, 0xdb, 0x28, 0x0e, 0x93, 0x18, 0x52, 0x77, 0xf6, 0x1f, 0x34, 0xed, 0x00, 0xbe, + 0xf3, 0x37, 0x00, 0x00, 0xff, 0xff, 0x96, 0x3f, 0x16, 0xa7, 0x2a, 0x06, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -563,6 +591,13 @@ func (m *ApplicationSetGetQuery) MarshalToSizedBuffer(dAtA []byte) (int, error) i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if len(m.AppsetNamespace) > 0 { + i -= len(m.AppsetNamespace) + copy(dAtA[i:], m.AppsetNamespace) + i = encodeVarintApplicationset(dAtA, i, uint64(len(m.AppsetNamespace))) + i-- + dAtA[i] = 0x12 + } if len(m.Name) > 0 { i -= len(m.Name) copy(dAtA[i:], m.Name) @@ -597,6 +632,13 @@ func (m *ApplicationSetListQuery) MarshalToSizedBuffer(dAtA []byte) (int, error) i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if len(m.AppsetNamespace) > 0 { + i -= len(m.AppsetNamespace) + copy(dAtA[i:], m.AppsetNamespace) + i = encodeVarintApplicationset(dAtA, i, uint64(len(m.AppsetNamespace))) + i-- + dAtA[i] = 0x1a + } if len(m.Selector) > 0 { i -= len(m.Selector) copy(dAtA[i:], m.Selector) @@ -735,6 +777,13 @@ func (m *ApplicationSetDeleteRequest) MarshalToSizedBuffer(dAtA []byte) (int, er i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if len(m.AppsetNamespace) > 0 { + i -= len(m.AppsetNamespace) + copy(dAtA[i:], m.AppsetNamespace) + i = encodeVarintApplicationset(dAtA, i, uint64(len(m.AppsetNamespace))) + i-- + dAtA[i] = 0x12 + } if len(m.Name) > 0 { i -= len(m.Name) copy(dAtA[i:], m.Name) @@ -766,6 +815,10 @@ func (m *ApplicationSetGetQuery) Size() (n int) { if l > 0 { n += 1 + l + sovApplicationset(uint64(l)) } + l = len(m.AppsetNamespace) + if l > 0 { + n += 1 + l + sovApplicationset(uint64(l)) + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -788,6 +841,10 @@ func (m *ApplicationSetListQuery) Size() (n int) { if l > 0 { n += 1 + l + sovApplicationset(uint64(l)) } + l = len(m.AppsetNamespace) + if l > 0 { + n += 1 + l + sovApplicationset(uint64(l)) + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -843,6 +900,10 @@ func (m *ApplicationSetDeleteRequest) Size() (n int) { if l > 0 { n += 1 + l + sovApplicationset(uint64(l)) } + l = len(m.AppsetNamespace) + if l > 0 { + n += 1 + l + sovApplicationset(uint64(l)) + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -916,6 +977,38 @@ func (m *ApplicationSetGetQuery) Unmarshal(dAtA []byte) error { } m.Name = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppsetNamespace", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApplicationset + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthApplicationset + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthApplicationset + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppsetNamespace = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipApplicationset(dAtA[iNdEx:]) @@ -1031,6 +1124,38 @@ func (m *ApplicationSetListQuery) Unmarshal(dAtA []byte) error { } m.Selector = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppsetNamespace", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApplicationset + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthApplicationset + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthApplicationset + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppsetNamespace = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipApplicationset(dAtA[iNdEx:]) @@ -1340,6 +1465,38 @@ func (m *ApplicationSetDeleteRequest) Unmarshal(dAtA []byte) error { } m.Name = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppsetNamespace", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApplicationset + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthApplicationset + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthApplicationset + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppsetNamespace = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipApplicationset(dAtA[iNdEx:]) diff --git a/pkg/apiclient/applicationset/applicationset.pb.gw.go b/pkg/apiclient/applicationset/applicationset.pb.gw.go index db537f548cb30..5e4c73f7add3b 100644 --- a/pkg/apiclient/applicationset/applicationset.pb.gw.go +++ b/pkg/apiclient/applicationset/applicationset.pb.gw.go @@ -33,6 +33,10 @@ var _ = utilities.NewDoubleArray var _ = descriptor.ForMessage var _ = metadata.Join +var ( + filter_ApplicationSetService_Get_0 = &utilities.DoubleArray{Encoding: map[string]int{"name": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} +) + func request_ApplicationSetService_Get_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationSetServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq ApplicationSetGetQuery var metadata runtime.ServerMetadata @@ -55,6 +59,13 @@ func request_ApplicationSetService_Get_0(ctx context.Context, marshaler runtime. return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ApplicationSetService_Get_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.Get(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err @@ -82,6 +93,13 @@ func local_request_ApplicationSetService_Get_0(ctx context.Context, marshaler ru return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ApplicationSetService_Get_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.Get(ctx, &protoReq) return msg, metadata, err @@ -175,6 +193,10 @@ func local_request_ApplicationSetService_Create_0(ctx context.Context, marshaler } +var ( + filter_ApplicationSetService_Delete_0 = &utilities.DoubleArray{Encoding: map[string]int{"name": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} +) + func request_ApplicationSetService_Delete_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationSetServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq ApplicationSetDeleteRequest var metadata runtime.ServerMetadata @@ -197,6 +219,13 @@ func request_ApplicationSetService_Delete_0(ctx context.Context, marshaler runti return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ApplicationSetService_Delete_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.Delete(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err @@ -224,6 +253,13 @@ func local_request_ApplicationSetService_Delete_0(ctx context.Context, marshaler return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ApplicationSetService_Delete_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.Delete(ctx, &protoReq) return msg, metadata, err diff --git a/pkg/apis/application/v1alpha1/applicationset_types.go b/pkg/apis/application/v1alpha1/applicationset_types.go index 508fc226fe736..2edfa5064ffa1 100644 --- a/pkg/apis/application/v1alpha1/applicationset_types.go +++ b/pkg/apis/application/v1alpha1/applicationset_types.go @@ -22,6 +22,7 @@ import ( "sort" "github.com/argoproj/argo-cd/v2/common" + "github.com/argoproj/argo-cd/v2/util/security" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -48,8 +49,8 @@ type ApplicationSet struct { } // RBACName formats fully qualified application name for RBAC check. -func (a *ApplicationSet) RBACName() string { - return fmt.Sprintf("%s/%s", a.Spec.Template.Spec.GetProject(), a.ObjectMeta.Name) +func (a *ApplicationSet) RBACName(defaultNS string) string { + return security.RBACName(defaultNS, a.Spec.Template.Spec.GetProject(), a.Namespace, a.Name) } // ApplicationSetSpec represents a class of application set state. @@ -675,3 +676,14 @@ func (status *ApplicationSetStatus) SetApplicationStatus(newStatus ApplicationSe } status.ApplicationStatus = append(status.ApplicationStatus, newStatus) } + +// QualifiedName returns the full qualified name of the applicationset, including +// the name of the namespace it is created in delimited by a forward slash, +// i.e. / +func (a *ApplicationSet) QualifiedName() string { + if a.Namespace == "" { + return a.Name + } else { + return a.Namespace + "/" + a.Name + } +} diff --git a/pkg/apis/application/v1alpha1/applicationset_types_test.go b/pkg/apis/application/v1alpha1/applicationset_types_test.go index 67fc502470a06..4dd8dd77c7435 100644 --- a/pkg/apis/application/v1alpha1/applicationset_types_test.go +++ b/pkg/apis/application/v1alpha1/applicationset_types_test.go @@ -38,6 +38,31 @@ func newTestAppSet(name, namespace, repo string) *ApplicationSet { return a } +func TestApplicationSetRBACName(t *testing.T) { + testRepo := "https://github.com/org/repo" + + t.Run("Test RBAC name with namespace", func(t *testing.T) { + namespace := "guestbook" + a := newTestAppSet("test-appset", namespace, testRepo) + a.Spec.Template.Spec.Project = "test" + assert.Equal(t, "test/guestbook/test-appset", a.RBACName("argocd")) + }) + + t.Run("Test RBAC name default ns", func(t *testing.T) { + namespace := "argocd" + a := newTestAppSet("test-appset", namespace, testRepo) + a.Spec.Template.Spec.Project = "test" + assert.Equal(t, "test/test-appset", a.RBACName("argocd")) + }) + + t.Run("Test RBAC no ns", func(t *testing.T) { + a := newTestAppSet("test-appset", "", testRepo) + a.Spec.Template.Spec.Project = "test" + assert.Equal(t, "test/test-appset", a.RBACName("argocd")) + }) + +} + func TestApplicationSetSetConditions(t *testing.T) { fiveMinsAgo := &metav1.Time{Time: time.Now().Add(-5 * time.Minute)} tenMinsAgo := &metav1.Time{Time: time.Now().Add(-10 * time.Minute)} diff --git a/pkg/apis/application/v1alpha1/types.go b/pkg/apis/application/v1alpha1/types.go index 409489b14bb67..6251b264e998e 100644 --- a/pkg/apis/application/v1alpha1/types.go +++ b/pkg/apis/application/v1alpha1/types.go @@ -2716,5 +2716,5 @@ func (a *Application) QualifiedName() string { // RBACName returns the full qualified RBAC resource name for the application // in a backwards-compatible way. func (a *Application) RBACName(defaultNS string) string { - return security.AppRBACName(defaultNS, a.Spec.GetProject(), a.Namespace, a.Name) + return security.RBACName(defaultNS, a.Spec.GetProject(), a.Namespace, a.Name) } diff --git a/reposerver/repository/repository.go b/reposerver/repository/repository.go index ad5a7ab2caf21..7f1929db1d8f5 100644 --- a/reposerver/repository/repository.go +++ b/reposerver/repository/repository.go @@ -1045,7 +1045,7 @@ func helmTemplate(appPath string, repoRoot string, env *v1alpha1.Env, q *apiclie // contain any underscore characters and must not exceed 53 characters. // We are not interested in the fully qualified application name while // templating, thus, we just use the name part of the identifier. - appName, _ := argo.ParseAppInstanceName(q.AppName, "") + appName, _ := argo.ParseInstanceName(q.AppName, "") templateOpts := &helm.TemplateOpts{ Name: appName, diff --git a/server/application/application_test.go b/server/application/application_test.go index 7569734d33b42..685cfe249dcbe 100644 --- a/server/application/application_test.go +++ b/server/application/application_test.go @@ -1022,7 +1022,7 @@ func TestLogsGetSelectedPod(t *testing.T) { // refreshAnnotationRemover runs an infinite loop until it detects and removes refresh annotation or given context is done func refreshAnnotationRemover(t *testing.T, ctx context.Context, patched *int32, appServer *Server, appName string, ch chan string) { for ctx.Err() == nil { - aName, appNs := argo.ParseAppQualifiedName(appName, appServer.ns) + aName, appNs := argo.ParseFromQualifiedName(appName, appServer.ns) a, err := appServer.appLister.Applications(appNs).Get(aName) require.NoError(t, err) a = a.DeepCopy() diff --git a/server/application/terminal.go b/server/application/terminal.go index 5052e38d92c1c..667ff529ae076 100644 --- a/server/application/terminal.go +++ b/server/application/terminal.go @@ -139,7 +139,7 @@ func (s *terminalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - appRBACName := security.AppRBACName(s.namespace, project, appNamespace, app) + appRBACName := security.RBACName(s.namespace, project, appNamespace, app) if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, appRBACName); err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return diff --git a/server/applicationset/applicationset.go b/server/applicationset/applicationset.go index 44ae512d5f1c7..17483888e1593 100644 --- a/server/applicationset/applicationset.go +++ b/server/applicationset/applicationset.go @@ -30,24 +30,27 @@ import ( "github.com/argoproj/argo-cd/v2/util/argo" argoutil "github.com/argoproj/argo-cd/v2/util/argo" "github.com/argoproj/argo-cd/v2/util/db" + "github.com/argoproj/argo-cd/v2/util/glob" "github.com/argoproj/argo-cd/v2/util/rbac" + "github.com/argoproj/argo-cd/v2/util/security" "github.com/argoproj/argo-cd/v2/util/session" "github.com/argoproj/argo-cd/v2/util/settings" ) type Server struct { - ns string - db db.ArgoDB - enf *rbac.Enforcer - cache *servercache.Cache - appclientset appclientset.Interface - appLister applisters.ApplicationLister - appsetInformer cache.SharedIndexInformer - appsetLister applisters.ApplicationSetNamespaceLister - projLister applisters.AppProjectNamespaceLister - auditLogger *argo.AuditLogger - settings *settings.SettingsManager - projectLock sync.KeyLock + ns string + db db.ArgoDB + enf *rbac.Enforcer + cache *servercache.Cache + appclientset appclientset.Interface + appLister applisters.ApplicationLister + appsetInformer cache.SharedIndexInformer + appsetLister applisters.ApplicationSetNamespaceLister + projLister applisters.AppProjectNamespaceLister + auditLogger *argo.AuditLogger + settings *settings.SettingsManager + projectLock sync.KeyLock + enabledNamespaces []string } // NewServer returns a new instance of the ApplicationSet service @@ -64,31 +67,40 @@ func NewServer( settings *settings.SettingsManager, namespace string, projectLock sync.KeyLock, + enabledNamespaces []string, ) applicationset.ApplicationSetServiceServer { s := &Server{ - ns: namespace, - cache: cache, - db: db, - enf: enf, - appclientset: appclientset, - appLister: appLister, - appsetInformer: appsetInformer, - appsetLister: appsetLister, - projLister: projLister, - settings: settings, - projectLock: projectLock, - auditLogger: argo.NewAuditLogger(namespace, kubeclientset, "argocd-server"), + ns: namespace, + cache: cache, + db: db, + enf: enf, + appclientset: appclientset, + appLister: appLister, + appsetInformer: appsetInformer, + appsetLister: appsetLister, + projLister: projLister, + settings: settings, + projectLock: projectLock, + auditLogger: argo.NewAuditLogger(namespace, kubeclientset, "argocd-server"), + enabledNamespaces: enabledNamespaces, } return s } func (s *Server) Get(ctx context.Context, q *applicationset.ApplicationSetGetQuery) (*v1alpha1.ApplicationSet, error) { - a, err := s.appclientset.ArgoprojV1alpha1().ApplicationSets(s.ns).Get(ctx, q.GetName(), metav1.GetOptions{}) + + namespace := s.appsetNamespaceOrDefault(q.AppsetNamespace) + + if !s.isNamespaceEnabled(namespace) { + return nil, security.NamespaceNotPermittedError(namespace) + } + + a, err := s.appclientset.ArgoprojV1alpha1().ApplicationSets(namespace).Get(ctx, q.Name, metav1.GetOptions{}) if err != nil { return nil, fmt.Errorf("error getting ApplicationSet: %w", err) } - if err = s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplicationSets, rbacpolicy.ActionGet, a.RBACName()); err != nil { + if err = s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplicationSets, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil { return nil, err } @@ -97,20 +109,27 @@ func (s *Server) Get(ctx context.Context, q *applicationset.ApplicationSetGetQue // List returns list of ApplicationSets func (s *Server) List(ctx context.Context, q *applicationset.ApplicationSetListQuery) (*v1alpha1.ApplicationSetList, error) { - labelsMap, err := labels.ConvertSelectorToLabelsMap(q.GetSelector()) + selector, err := labels.Parse(q.GetSelector()) if err != nil { - return nil, fmt.Errorf("error converting selector to labels map: %w", err) + return nil, fmt.Errorf("error parsing the selector: %w", err) } - appIf := s.appclientset.ArgoprojV1alpha1().ApplicationSets(s.ns) - appsetList, err := appIf.List(ctx, metav1.ListOptions{LabelSelector: labelsMap.AsSelector().String()}) + appIf := s.appclientset.ArgoprojV1alpha1().ApplicationSets(q.AppsetNamespace) + appsetList, err := appIf.List(ctx, metav1.ListOptions{LabelSelector: selector.String()}) if err != nil { return nil, fmt.Errorf("error listing ApplicationSets with selectors: %w", err) } newItems := make([]v1alpha1.ApplicationSet, 0) for _, a := range appsetList.Items { - if s.enf.Enforce(ctx.Value("claims"), rbacpolicy.ResourceApplicationSets, rbacpolicy.ActionGet, a.RBACName()) { + + // Skip any application that is neither in the conrol plane's namespace + // nor in the list of enabled namespaces. + if a.Namespace != s.ns && !glob.MatchStringInList(s.enabledNamespaces, a.Namespace, false) { + continue + } + + if s.enf.Enforce(ctx.Value("claims"), rbacpolicy.ResourceApplicationSets, rbacpolicy.ActionGet, a.RBACName(s.ns)) { newItems = append(newItems, a) } } @@ -144,6 +163,12 @@ func (s *Server) Create(ctx context.Context, q *applicationset.ApplicationSetCre return nil, fmt.Errorf("error validating ApplicationSets: %w", err) } + namespace := s.appsetNamespaceOrDefault(appset.Namespace) + + if !s.isNamespaceEnabled(namespace) { + return nil, security.NamespaceNotPermittedError(namespace) + } + if err := s.checkCreatePermissions(ctx, appset, projectName); err != nil { return nil, fmt.Errorf("error checking create permissions for ApplicationSets %s : %s", appset.Name, err) } @@ -151,7 +176,7 @@ func (s *Server) Create(ctx context.Context, q *applicationset.ApplicationSetCre s.projectLock.RLock(projectName) defer s.projectLock.RUnlock(projectName) - created, err := s.appclientset.ArgoprojV1alpha1().ApplicationSets(s.ns).Create(ctx, appset, metav1.CreateOptions{}) + created, err := s.appclientset.ArgoprojV1alpha1().ApplicationSets(namespace).Create(ctx, appset, metav1.CreateOptions{}) if err == nil { s.logAppSetEvent(created, ctx, argo.EventReasonResourceCreated, "created ApplicationSet") s.waitSync(created) @@ -181,7 +206,7 @@ func (s *Server) Create(ctx context.Context, q *applicationset.ApplicationSetCre if !q.Upsert { return nil, status.Errorf(codes.InvalidArgument, "existing ApplicationSet spec is different, use upsert flag to force update") } - if err = s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplicationSets, rbacpolicy.ActionUpdate, appset.RBACName()); err != nil { + if err = s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplicationSets, rbacpolicy.ActionUpdate, appset.RBACName(s.ns)); err != nil { return nil, err } updated, err := s.updateAppSet(existing, appset, ctx, true) @@ -209,11 +234,11 @@ func (s *Server) updateAppSet(appset *v1alpha1.ApplicationSet, newAppset *v1alph if appset != nil && appset.Spec.Template.Spec.Project != newAppset.Spec.Template.Spec.Project { // When changing projects, caller must have applicationset create and update privileges in new project // NOTE: the update check was already verified in the caller to this function - if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplicationSets, rbacpolicy.ActionCreate, newAppset.RBACName()); err != nil { + if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplicationSets, rbacpolicy.ActionCreate, newAppset.RBACName(s.ns)); err != nil { return nil, err } // They also need 'update' privileges in the old project - if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplicationSets, rbacpolicy.ActionUpdate, appset.RBACName()); err != nil { + if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplicationSets, rbacpolicy.ActionUpdate, appset.RBACName(s.ns)); err != nil { return nil, err } } @@ -248,19 +273,21 @@ func (s *Server) updateAppSet(appset *v1alpha1.ApplicationSet, newAppset *v1alph func (s *Server) Delete(ctx context.Context, q *applicationset.ApplicationSetDeleteRequest) (*applicationset.ApplicationSetResponse, error) { - appset, err := s.appclientset.ArgoprojV1alpha1().ApplicationSets(s.ns).Get(ctx, q.Name, metav1.GetOptions{}) + namespace := s.appsetNamespaceOrDefault(q.AppsetNamespace) + + appset, err := s.appclientset.ArgoprojV1alpha1().ApplicationSets(namespace).Get(ctx, q.Name, metav1.GetOptions{}) if err != nil { return nil, fmt.Errorf("error getting ApplicationSets: %w", err) } - if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplicationSets, rbacpolicy.ActionDelete, appset.RBACName()); err != nil { + if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplicationSets, rbacpolicy.ActionDelete, appset.RBACName(s.ns)); err != nil { return nil, err } s.projectLock.RLock(appset.Spec.Template.Spec.Project) defer s.projectLock.RUnlock(appset.Spec.Template.Spec.Project) - err = s.appclientset.ArgoprojV1alpha1().ApplicationSets(s.ns).Delete(ctx, q.Name, metav1.DeleteOptions{}) + err = s.appclientset.ArgoprojV1alpha1().ApplicationSets(namespace).Delete(ctx, q.Name, metav1.DeleteOptions{}) if err != nil { return nil, fmt.Errorf("error deleting ApplicationSets: %w", err) } @@ -289,7 +316,7 @@ func (s *Server) validateAppSet(ctx context.Context, appset *v1alpha1.Applicatio func (s *Server) checkCreatePermissions(ctx context.Context, appset *v1alpha1.ApplicationSet, projectName string) error { - if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplicationSets, rbacpolicy.ActionCreate, appset.RBACName()); err != nil { + if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplicationSets, rbacpolicy.ActionCreate, appset.RBACName(s.ns)); err != nil { return err } @@ -346,3 +373,15 @@ func (s *Server) logAppSetEvent(a *v1alpha1.ApplicationSet, ctx context.Context, message := fmt.Sprintf("%s %s", user, action) s.auditLogger.LogAppSetEvent(a, eventInfo, message) } + +func (s *Server) appsetNamespaceOrDefault(appNs string) string { + if appNs == "" { + return s.ns + } else { + return appNs + } +} + +func (s *Server) isNamespaceEnabled(namespace string) bool { + return security.IsNamespaceEnabled(namespace, s.ns, s.enabledNamespaces) +} diff --git a/server/applicationset/applicationset.proto b/server/applicationset/applicationset.proto index 8f6d09cf2b75b..2a857d41a00ce 100644 --- a/server/applicationset/applicationset.proto +++ b/server/applicationset/applicationset.proto @@ -14,6 +14,8 @@ import "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1/generated.p message ApplicationSetGetQuery { // the applicationsets's name string name = 1; + // The application set namespace. Default empty is argocd control plane namespace + string appsetNamespace = 2; } message ApplicationSetListQuery { @@ -21,6 +23,8 @@ message ApplicationSetListQuery { repeated string projects = 1; // the selector to restrict returned list to applications only with matched labels string selector = 2; + // The application set namespace. Default empty is argocd control plane namespace + string appsetNamespace = 3; } @@ -38,6 +42,8 @@ message ApplicationSetCreateRequest { message ApplicationSetDeleteRequest { string name = 1; + // The application set namespace. Default empty is argocd control plane namespace + string appsetNamespace = 2; } diff --git a/server/applicationset/applicationset_test.go b/server/applicationset/applicationset_test.go new file mode 100644 index 0000000000000..aef61f289d494 --- /dev/null +++ b/server/applicationset/applicationset_test.go @@ -0,0 +1,434 @@ +package applicationset + +import ( + "context" + "testing" + + "github.com/argoproj/pkg/sync" + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/fake" + k8scache "k8s.io/client-go/tools/cache" + + "github.com/argoproj/argo-cd/v2/common" + "github.com/argoproj/argo-cd/v2/pkg/apiclient/applicationset" + appsv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + apps "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned/fake" + appinformer "github.com/argoproj/argo-cd/v2/pkg/client/informers/externalversions" + "github.com/argoproj/argo-cd/v2/server/rbacpolicy" + "github.com/argoproj/argo-cd/v2/util/assets" + "github.com/argoproj/argo-cd/v2/util/db" + "github.com/argoproj/argo-cd/v2/util/errors" + "github.com/argoproj/argo-cd/v2/util/rbac" + "github.com/argoproj/argo-cd/v2/util/settings" +) + +const ( + testNamespace = "default" + fakeRepoURL = "https://git.com/repo.git" +) + +func fakeRepo() *appsv1.Repository { + return &appsv1.Repository{ + Repo: fakeRepoURL, + } +} + +func fakeCluster() *appsv1.Cluster { + return &appsv1.Cluster{ + Server: "https://cluster-api.com", + Name: "fake-cluster", + Config: appsv1.ClusterConfig{}, + } +} + +// return an ApplicationServiceServer which returns fake data +func newTestAppSetServer(objects ...runtime.Object) *Server { + f := func(enf *rbac.Enforcer) { + _ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV) + enf.SetDefaultRole("role:admin") + } + return newTestAppSetServerWithEnforcerConfigure(f, objects...) +} + +func newTestAppSetServerWithEnforcerConfigure(f func(*rbac.Enforcer), objects ...runtime.Object) *Server { + kubeclientset := fake.NewSimpleClientset(&v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "argocd-cm", + Labels: map[string]string{ + "app.kubernetes.io/part-of": "argocd", + }, + }, + }, &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argocd-secret", + Namespace: testNamespace, + }, + Data: map[string][]byte{ + "admin.password": []byte("test"), + "server.secretkey": []byte("test"), + }, + }) + ctx := context.Background() + db := db.NewDB(testNamespace, settings.NewSettingsManager(ctx, kubeclientset, testNamespace), kubeclientset) + _, err := db.CreateRepository(ctx, fakeRepo()) + errors.CheckError(err) + _, err = db.CreateCluster(ctx, fakeCluster()) + errors.CheckError(err) + + defaultProj := &appsv1.AppProject{ + ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "default"}, + Spec: appsv1.AppProjectSpec{ + SourceRepos: []string{"*"}, + Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}}, + }, + } + myProj := &appsv1.AppProject{ + ObjectMeta: metav1.ObjectMeta{Name: "my-proj", Namespace: "default"}, + Spec: appsv1.AppProjectSpec{ + SourceRepos: []string{"*"}, + Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}}, + }, + } + + objects = append(objects, defaultProj, myProj) + + fakeAppsClientset := apps.NewSimpleClientset(objects...) + factory := appinformer.NewSharedInformerFactoryWithOptions(fakeAppsClientset, 0, appinformer.WithNamespace(""), appinformer.WithTweakListOptions(func(options *metav1.ListOptions) {})) + fakeProjLister := factory.Argoproj().V1alpha1().AppProjects().Lister().AppProjects(testNamespace) + + enforcer := rbac.NewEnforcer(kubeclientset, testNamespace, common.ArgoCDRBACConfigMapName, nil) + f(enforcer) + enforcer.SetClaimsEnforcerFunc(rbacpolicy.NewRBACPolicyEnforcer(enforcer, fakeProjLister).EnforceClaims) + + settingsMgr := settings.NewSettingsManager(ctx, kubeclientset, testNamespace) + + // populate the app informer with the fake objects + appInformer := factory.Argoproj().V1alpha1().Applications().Informer() + // TODO(jessesuen): probably should return cancel function so tests can stop background informer + //ctx, cancel := context.WithCancel(context.Background()) + go appInformer.Run(ctx.Done()) + if !k8scache.WaitForCacheSync(ctx.Done(), appInformer.HasSynced) { + panic("Timed out waiting for caches to sync") + } + + projInformer := factory.Argoproj().V1alpha1().AppProjects().Informer() + go projInformer.Run(ctx.Done()) + if !k8scache.WaitForCacheSync(ctx.Done(), projInformer.HasSynced) { + panic("Timed out waiting for caches to sync") + } + + server := NewServer( + db, + kubeclientset, + enforcer, + nil, + fakeAppsClientset, + factory.Argoproj().V1alpha1().Applications().Lister(), + appInformer, + factory.Argoproj().V1alpha1().ApplicationSets().Lister().ApplicationSets(testNamespace), + fakeProjLister, + settingsMgr, + testNamespace, + sync.NewKeyLock(), + []string{testNamespace, "external-namespace"}, + ) + return server.(*Server) +} + +func newTestAppSet(opts ...func(appset *appsv1.ApplicationSet)) *appsv1.ApplicationSet { + appset := appsv1.ApplicationSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + }, + Spec: appsv1.ApplicationSetSpec{ + Template: appsv1.ApplicationSetTemplate{ + Spec: appsv1.ApplicationSpec{ + Project: "default", + }, + }, + }, + } + for i := range opts { + opts[i](&appset) + } + return &appset +} + +func testListAppsetsWithLabels(t *testing.T, appsetQuery applicationset.ApplicationSetListQuery, appServer *Server) { + validTests := []struct { + testName string + label string + expectedResult []string + }{ + {testName: "Equality based filtering using '=' operator", + label: "key1=value1", + expectedResult: []string{"AppSet1"}}, + {testName: "Equality based filtering using '==' operator", + label: "key1==value1", + expectedResult: []string{"AppSet1"}}, + {testName: "Equality based filtering using '!=' operator", + label: "key1!=value1", + expectedResult: []string{"AppSet2", "AppSet3"}}, + {testName: "Set based filtering using 'in' operator", + label: "key1 in (value1, value3)", + expectedResult: []string{"AppSet1", "AppSet3"}}, + {testName: "Set based filtering using 'notin' operator", + label: "key1 notin (value1, value3)", + expectedResult: []string{"AppSet2"}}, + {testName: "Set based filtering using 'exists' operator", + label: "key1", + expectedResult: []string{"AppSet1", "AppSet2", "AppSet3"}}, + {testName: "Set based filtering using 'not exists' operator", + label: "!key2", + expectedResult: []string{"AppSet2", "AppSet3"}}, + } + //test valid scenarios + for _, validTest := range validTests { + t.Run(validTest.testName, func(t *testing.T) { + appsetQuery.Selector = validTest.label + res, err := appServer.List(context.Background(), &appsetQuery) + assert.NoError(t, err) + apps := []string{} + for i := range res.Items { + apps = append(apps, res.Items[i].Name) + } + assert.Equal(t, validTest.expectedResult, apps) + }) + } + + invalidTests := []struct { + testName string + label string + errorMesage string + }{ + {testName: "Set based filtering using '>' operator", + label: "key1>value1", + errorMesage: "error parsing the selector"}, + {testName: "Set based filtering using '<' operator", + label: "key1 0 settingsService := settings.NewServer(a.settingsMgr, a.RepoClientset, a, a.DisableAuth, appsInAnyNamespaceEnabled) diff --git a/test/e2e/app_management_test.go b/test/e2e/app_management_test.go index a1152d0f6495b..0ff668b8cde89 100644 --- a/test/e2e/app_management_test.go +++ b/test/e2e/app_management_test.go @@ -1179,7 +1179,7 @@ func TestPermissions(t *testing.T) { And(func(app *Application) { closer, cdClient := ArgoCDClientset.NewApplicationClientOrDie() defer io.Close(closer) - appName, appNs := argo.ParseAppQualifiedName(app.Name, "") + appName, appNs := argo.ParseFromQualifiedName(app.Name, "") fmt.Printf("APP NAME: %s\n", appName) tree, err := cdClient.ResourceTree(context.Background(), &applicationpkg.ResourcesQuery{ApplicationName: &appName, AppNamespace: &appNs}) require.NoError(t, err) diff --git a/test/e2e/applicationset_test.go b/test/e2e/applicationset_test.go index 913eb73079de1..60b837fd74ffd 100644 --- a/test/e2e/applicationset_test.go +++ b/test/e2e/applicationset_test.go @@ -43,6 +43,102 @@ var ( } ) +func TestSimpleListGeneratorExternalNamespace(t *testing.T) { + + expectedApp := argov1alpha1.Application{ + TypeMeta: metav1.TypeMeta{ + Kind: "Application", + APIVersion: "argoproj.io/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-cluster-guestbook", + Namespace: utils.ArgoCDExternalNamespace, + Finalizers: []string{"resources-finalizer.argocd.argoproj.io"}, + }, + Spec: argov1alpha1.ApplicationSpec{ + Project: "default", + Source: &argov1alpha1.ApplicationSource{ + RepoURL: "https://github.com/argoproj/argocd-example-apps.git", + TargetRevision: "HEAD", + Path: "guestbook", + }, + Destination: argov1alpha1.ApplicationDestination{ + Server: "https://kubernetes.default.svc", + Namespace: "guestbook", + }, + }, + } + var expectedAppNewNamespace *argov1alpha1.Application + var expectedAppNewMetadata *argov1alpha1.Application + + Given(t). + // Create a ListGenerator-based ApplicationSet + When(). + SwitchToExternalNamespace(). + CreateNamespace(utils.ArgoCDExternalNamespace).Create(v1alpha1.ApplicationSet{ObjectMeta: metav1.ObjectMeta{ + Name: "simple-list-generator-external", + Namespace: utils.ArgoCDExternalNamespace, + }, + Spec: v1alpha1.ApplicationSetSpec{ + GoTemplate: true, + Template: v1alpha1.ApplicationSetTemplate{ + ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{.cluster}}-guestbook"}, + Spec: argov1alpha1.ApplicationSpec{ + Project: "default", + Source: &argov1alpha1.ApplicationSource{ + RepoURL: "https://github.com/argoproj/argocd-example-apps.git", + TargetRevision: "HEAD", + Path: "guestbook", + }, + Destination: argov1alpha1.ApplicationDestination{ + Server: "{{.url}}", + Namespace: "guestbook", + }, + }, + }, + Generators: []v1alpha1.ApplicationSetGenerator{ + { + List: &v1alpha1.ListGenerator{ + Elements: []apiextensionsv1.JSON{{ + Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`), + }}, + }, + }, + }, + }, + }).Then().Expect(ApplicationsExist([]argov1alpha1.Application{expectedApp})). + + // Update the ApplicationSet template namespace, and verify it updates the Applications + When(). + And(func() { + expectedAppNewNamespace = expectedApp.DeepCopy() + expectedAppNewNamespace.Spec.Destination.Namespace = "guestbook2" + }). + Update(func(appset *v1alpha1.ApplicationSet) { + appset.Spec.Template.Spec.Destination.Namespace = "guestbook2" + }).Then().Expect(ApplicationsExist([]argov1alpha1.Application{*expectedAppNewNamespace})). + + // Update the metadata fields in the appset template, and make sure it propagates to the apps + When(). + And(func() { + expectedAppNewMetadata = expectedAppNewNamespace.DeepCopy() + expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"} + expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{"label-key": "label-value"} + }). + Update(func(appset *v1alpha1.ApplicationSet) { + appset.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"} + appset.Spec.Template.Labels = map[string]string{"label-key": "label-value"} + }).Then().Expect(ApplicationsExist([]argov1alpha1.Application{*expectedAppNewMetadata})). + + // verify the ApplicationSet status conditions were set correctly + Expect(ApplicationSetHasConditions("simple-list-generator-external", ExpectedConditions)). + + // Delete the ApplicationSet, and verify it deletes the Applications + When(). + Delete().Then().Expect(ApplicationsDoNotExist([]argov1alpha1.Application{*expectedAppNewMetadata})) + +} + func TestSimpleListGenerator(t *testing.T) { expectedApp := argov1alpha1.Application{ @@ -670,7 +766,7 @@ func TestSimpleGitFilesPreserveResourcesOnDeletion(t *testing.T) { Given(t). When(). - CreateNamespace(). + CreateNamespace(utils.ApplicationsResourcesNamespace). // Create a GitGenerator-based ApplicationSet Create(v1alpha1.ApplicationSet{ObjectMeta: metav1.ObjectMeta{ Name: "simple-git-generator", @@ -687,7 +783,7 @@ func TestSimpleGitFilesPreserveResourcesOnDeletion(t *testing.T) { }, Destination: argov1alpha1.ApplicationDestination{ Server: "https://kubernetes.default.svc", - Namespace: utils.ApplicationSetNamespace, + Namespace: utils.ApplicationsResourcesNamespace, }, // Automatically create resources @@ -730,7 +826,7 @@ func TestSimpleGitFilesPreserveResourcesOnDeletionGoTemplate(t *testing.T) { Given(t). When(). - CreateNamespace(). + CreateNamespace(utils.ApplicationsResourcesNamespace). // Create a GitGenerator-based ApplicationSet Create(v1alpha1.ApplicationSet{ObjectMeta: metav1.ObjectMeta{ Name: "simple-git-generator", @@ -748,7 +844,7 @@ func TestSimpleGitFilesPreserveResourcesOnDeletionGoTemplate(t *testing.T) { }, Destination: argov1alpha1.ApplicationDestination{ Server: "https://kubernetes.default.svc", - Namespace: utils.ApplicationSetNamespace, + Namespace: utils.ApplicationsResourcesNamespace, }, // Automatically create resources diff --git a/test/e2e/cluster_generator_test.go b/test/e2e/cluster_generator_test.go index c09fc9ed2fa34..6423e98499292 100644 --- a/test/e2e/cluster_generator_test.go +++ b/test/e2e/cluster_generator_test.go @@ -12,6 +12,102 @@ import ( "github.com/argoproj/argo-cd/v2/test/e2e/fixture/applicationsets/utils" ) +func TestSimpleClusterGeneratorExternalNamespace(t *testing.T) { + + expectedApp := argov1alpha1.Application{ + TypeMeta: metav1.TypeMeta{ + Kind: "Application", + APIVersion: "argoproj.io/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster1-guestbook", + Namespace: utils.ArgoCDExternalNamespace, + Finalizers: []string{"resources-finalizer.argocd.argoproj.io"}, + }, + Spec: argov1alpha1.ApplicationSpec{ + Project: "default", + Source: &argov1alpha1.ApplicationSource{ + RepoURL: "https://github.com/argoproj/argocd-example-apps.git", + TargetRevision: "HEAD", + Path: "guestbook", + }, + Destination: argov1alpha1.ApplicationDestination{ + Name: "cluster1", + Namespace: "guestbook", + }, + }, + } + + var expectedAppNewNamespace *argov1alpha1.Application + var expectedAppNewMetadata *argov1alpha1.Application + + Given(t). + // Create a ClusterGenerator-based ApplicationSet + When(). + CreateClusterSecret("my-secret", "cluster1", "https://kubernetes.default.svc"). + SwitchToExternalNamespace(). + CreateNamespace(utils.ArgoCDExternalNamespace). + Create(v1alpha1.ApplicationSet{ObjectMeta: metav1.ObjectMeta{ + Name: "simple-cluster-generator", + }, + Spec: v1alpha1.ApplicationSetSpec{ + Template: v1alpha1.ApplicationSetTemplate{ + ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{name}}-guestbook"}, + Spec: argov1alpha1.ApplicationSpec{ + Project: "default", + Source: &argov1alpha1.ApplicationSource{ + RepoURL: "https://github.com/argoproj/argocd-example-apps.git", + TargetRevision: "HEAD", + Path: "guestbook", + }, + Destination: argov1alpha1.ApplicationDestination{ + Name: "{{name}}", + // Server: "{{server}}", + Namespace: "guestbook", + }, + }, + }, + Generators: []v1alpha1.ApplicationSetGenerator{ + { + Clusters: &v1alpha1.ClusterGenerator{ + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "argocd.argoproj.io/secret-type": "cluster", + }, + }, + }, + }, + }, + }, + }).Then().Expect(ApplicationsExist([]argov1alpha1.Application{expectedApp})). + + // Update the ApplicationSet template namespace, and verify it updates the Applications + When(). + And(func() { + expectedAppNewNamespace = expectedApp.DeepCopy() + expectedAppNewNamespace.Spec.Destination.Namespace = "guestbook2" + }). + Update(func(appset *v1alpha1.ApplicationSet) { + appset.Spec.Template.Spec.Destination.Namespace = "guestbook2" + }).Then().Expect(ApplicationsExist([]argov1alpha1.Application{*expectedAppNewNamespace})). + + // Update the metadata fields in the appset template, and make sure it propagates to the apps + When(). + And(func() { + expectedAppNewMetadata = expectedAppNewNamespace.DeepCopy() + expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"} + expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{"label-key": "label-value"} + }). + Update(func(appset *v1alpha1.ApplicationSet) { + appset.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"} + appset.Spec.Template.Labels = map[string]string{"label-key": "label-value"} + }).Then().Expect(ApplicationsExist([]argov1alpha1.Application{*expectedAppNewMetadata})). + + // Delete the ApplicationSet, and verify it deletes the Applications + When(). + Delete().Then().Expect(ApplicationsDoNotExist([]argov1alpha1.Application{*expectedAppNewNamespace})) +} + func TestSimpleClusterGenerator(t *testing.T) { expectedApp := argov1alpha1.Application{ diff --git a/test/e2e/clusterdecisiongenerator_e2e_test.go b/test/e2e/clusterdecisiongenerator_e2e_test.go index 44993dd409fde..af4038463a3da 100644 --- a/test/e2e/clusterdecisiongenerator_e2e_test.go +++ b/test/e2e/clusterdecisiongenerator_e2e_test.go @@ -13,6 +13,110 @@ import ( var tenSec = int64(10) +func TestSimpleClusterDecisionResourceGeneratorExternalNamespace(t *testing.T) { + + expectedApp := argov1alpha1.Application{ + TypeMeta: metav1.TypeMeta{ + Kind: "Application", + APIVersion: "argoproj.io/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster1-guestbook", + Namespace: utils.ArgoCDExternalNamespace, + Finalizers: []string{"resources-finalizer.argocd.argoproj.io"}, + }, + Spec: argov1alpha1.ApplicationSpec{ + Project: "default", + Source: &argov1alpha1.ApplicationSource{ + RepoURL: "https://github.com/argoproj/argocd-example-apps.git", + TargetRevision: "HEAD", + Path: "guestbook", + }, + Destination: argov1alpha1.ApplicationDestination{ + Name: "cluster1", + Namespace: "guestbook", + }, + }, + } + + var expectedAppNewNamespace *argov1alpha1.Application + var expectedAppNewMetadata *argov1alpha1.Application + + clusterList := []interface{}{ + map[string]interface{}{ + "clusterName": "cluster1", + "reason": "argotest", + }, + } + + Given(t). + // Create a ClusterGenerator-based ApplicationSet + When(). + CreateClusterSecret("my-secret", "cluster1", "https://kubernetes.default.svc"). + CreatePlacementRoleAndRoleBinding(). + CreatePlacementDecisionConfigMap("my-configmap"). + CreatePlacementDecision("my-placementdecision"). + StatusUpdatePlacementDecision("my-placementdecision", clusterList). + CreateNamespace(utils.ArgoCDExternalNamespace). + SwitchToExternalNamespace(). + Create(v1alpha1.ApplicationSet{ObjectMeta: metav1.ObjectMeta{ + Name: "simple-cluster-generator", + }, + Spec: v1alpha1.ApplicationSetSpec{ + Template: v1alpha1.ApplicationSetTemplate{ + ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{name}}-guestbook"}, + Spec: argov1alpha1.ApplicationSpec{ + Project: "default", + Source: &argov1alpha1.ApplicationSource{ + RepoURL: "https://github.com/argoproj/argocd-example-apps.git", + TargetRevision: "HEAD", + Path: "guestbook", + }, + Destination: argov1alpha1.ApplicationDestination{ + Name: "{{clusterName}}", + // Server: "{{server}}", + Namespace: "guestbook", + }, + }, + }, + Generators: []v1alpha1.ApplicationSetGenerator{ + { + ClusterDecisionResource: &v1alpha1.DuckTypeGenerator{ + ConfigMapRef: "my-configmap", + Name: "my-placementdecision", + }, + }, + }, + }, + }).Then().Expect(ApplicationsExist([]argov1alpha1.Application{expectedApp})). + + // Update the ApplicationSet template namespace, and verify it updates the Applications + When(). + And(func() { + expectedAppNewNamespace = expectedApp.DeepCopy() + expectedAppNewNamespace.Spec.Destination.Namespace = "guestbook2" + }). + Update(func(appset *v1alpha1.ApplicationSet) { + appset.Spec.Template.Spec.Destination.Namespace = "guestbook2" + }).Then().Expect(ApplicationsExist([]argov1alpha1.Application{*expectedAppNewNamespace})). + + // Update the metadata fields in the appset template, and make sure it propagates to the apps + When(). + And(func() { + expectedAppNewMetadata = expectedAppNewNamespace.DeepCopy() + expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"} + expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{"label-key": "label-value"} + }). + Update(func(appset *v1alpha1.ApplicationSet) { + appset.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"} + appset.Spec.Template.Labels = map[string]string{"label-key": "label-value"} + }).Then().Expect(ApplicationsExist([]argov1alpha1.Application{*expectedAppNewMetadata})). + + // Delete the ApplicationSet, and verify it deletes the Applications + When(). + Delete().Then().Expect(ApplicationsDoNotExist([]argov1alpha1.Application{*expectedAppNewNamespace})) +} + func TestSimpleClusterDecisionResourceGenerator(t *testing.T) { expectedApp := argov1alpha1.Application{ diff --git a/test/e2e/fixture/applicationsets/actions.go b/test/e2e/fixture/applicationsets/actions.go index 13198890e0efe..a95f959f2aa23 100644 --- a/test/e2e/fixture/applicationsets/actions.go +++ b/test/e2e/fixture/applicationsets/actions.go @@ -14,6 +14,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/dynamic" "github.com/argoproj/argo-cd/v2/common" argocommon "github.com/argoproj/argo-cd/v2/common" @@ -62,6 +63,18 @@ func (a *Actions) Then() *Consequences { return &Consequences{a.context, a} } +func (a *Actions) SwitchToExternalNamespace() *Actions { + a.context.useExternalNamespace = true + log.Infof("switched to external namespace: %s", utils.ArgoCDExternalNamespace) + return a +} + +func (a *Actions) SwitchToArgoCDNamespace() *Actions { + a.context.useExternalNamespace = false + log.Infof("switched to argocd namespace: %s", utils.ArgoCDNamespace) + return a +} + // CreateClusterSecret creates a faux cluster secret, with the given cluster server and cluster name (this cluster // will not actually be used by the Argo CD controller, but that's not needed for our E2E tests) func (a *Actions) CreateClusterSecret(secretName string, clusterName string, clusterServer string) *Actions { @@ -176,15 +189,15 @@ func (a *Actions) DeletePlacementDecision(placementDecisionName string) *Actions // Create a temporary namespace, from utils.ApplicationSet, for use by the test. // This namespace will be deleted on subsequent tests. -func (a *Actions) CreateNamespace() *Actions { +func (a *Actions) CreateNamespace(namespace string) *Actions { a.context.t.Helper() fixtureClient := utils.GetE2EFixtureK8sClient() _, err := fixtureClient.KubeClientset.CoreV1().Namespaces().Create(context.Background(), - &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: utils.ApplicationSetNamespace}}, metav1.CreateOptions{}) + &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}, metav1.CreateOptions{}) - a.describeAction = fmt.Sprintf("creating namespace '%s'", utils.ApplicationSetNamespace) + a.describeAction = fmt.Sprintf("creating namespace '%s'", namespace) a.lastOutput, a.lastError = "", err a.verifyAction() @@ -195,17 +208,27 @@ func (a *Actions) CreateNamespace() *Actions { func (a *Actions) Create(appSet v1alpha1.ApplicationSet) *Actions { a.context.t.Helper() + fixtureClient := utils.GetE2EFixtureK8sClient() + appSet.APIVersion = "argoproj.io/v1alpha1" appSet.Kind = "ApplicationSet" - fixtureClient := utils.GetE2EFixtureK8sClient() - newResource, err := fixtureClient.AppSetClientset.Create(context.Background(), utils.MustToUnstructured(&appSet), metav1.CreateOptions{}) + var appSetClientSet dynamic.ResourceInterface + + if a.context.useExternalNamespace { + appSetClientSet = fixtureClient.ExternalAppSetClientset + } else { + appSetClientSet = fixtureClient.AppSetClientset + } + + newResource, err := appSetClientSet.Create(context.Background(), utils.MustToUnstructured(&appSet), metav1.CreateOptions{}) if err == nil { a.context.name = newResource.GetName() + a.context.namespace = newResource.GetNamespace() } - a.describeAction = fmt.Sprintf("creating ApplicationSet '%s'", appSet.Name) + a.describeAction = fmt.Sprintf("creating ApplicationSet '%s/%s'", appSet.Namespace, appSet.Name) a.lastOutput, a.lastError = "", err a.verifyAction() @@ -364,9 +387,17 @@ func (a *Actions) Delete() *Actions { fixtureClient := utils.GetE2EFixtureK8sClient() + var appSetClientSet dynamic.ResourceInterface + + if a.context.useExternalNamespace { + appSetClientSet = fixtureClient.ExternalAppSetClientset + } else { + appSetClientSet = fixtureClient.AppSetClientset + } + deleteProp := metav1.DeletePropagationForeground - err := fixtureClient.AppSetClientset.Delete(context.Background(), a.context.name, metav1.DeleteOptions{PropagationPolicy: &deleteProp}) - a.describeAction = fmt.Sprintf("Deleting ApplicationSet '%s' %v", a.context.name, err) + err := appSetClientSet.Delete(context.Background(), a.context.name, metav1.DeleteOptions{PropagationPolicy: &deleteProp}) + a.describeAction = fmt.Sprintf("Deleting ApplicationSet '%s/%s' %v", a.context.namespace, a.context.name, err) a.lastOutput, a.lastError = "", err a.verifyAction() @@ -378,7 +409,16 @@ func (a *Actions) get() (*v1alpha1.ApplicationSet, error) { appSet := v1alpha1.ApplicationSet{} fixtureClient := utils.GetE2EFixtureK8sClient() - newResource, err := fixtureClient.AppSetClientset.Get(context.Background(), a.context.name, metav1.GetOptions{}) + + var appSetClientSet dynamic.ResourceInterface + + if a.context.useExternalNamespace { + appSetClientSet = fixtureClient.ExternalAppSetClientset + } else { + appSetClientSet = fixtureClient.AppSetClientset + } + + newResource, err := appSetClientSet.Get(context.Background(), a.context.name, metav1.GetOptions{}) if err != nil { return nil, err } @@ -413,10 +453,19 @@ func (a *Actions) Update(toUpdate func(*v1alpha1.ApplicationSet)) *Actions { if err == nil { // Keep trying to update until it succeeds, or the test times out toUpdate(appSet) - a.describeAction = fmt.Sprintf("updating ApplicationSet '%s'", appSet.Name) + a.describeAction = fmt.Sprintf("updating ApplicationSet '%s/%s'", appSet.Namespace, appSet.Name) fixtureClient := utils.GetE2EFixtureK8sClient() - _, err = fixtureClient.AppSetClientset.Update(context.Background(), utils.MustToUnstructured(&appSet), metav1.UpdateOptions{}) + + var appSetClientSet dynamic.ResourceInterface + + if a.context.useExternalNamespace { + appSetClientSet = fixtureClient.ExternalAppSetClientset + } else { + appSetClientSet = fixtureClient.AppSetClientset + } + + _, err = appSetClientSet.Update(context.Background(), utils.MustToUnstructured(&appSet), metav1.UpdateOptions{}) if err != nil { mostRecentError = err diff --git a/test/e2e/fixture/applicationsets/consequences.go b/test/e2e/fixture/applicationsets/consequences.go index e5d45cdf29f2e..8267c5c5e7dc5 100644 --- a/test/e2e/fixture/applicationsets/consequences.go +++ b/test/e2e/fixture/applicationsets/consequences.go @@ -8,6 +8,7 @@ import ( "github.com/argoproj/pkg/errors" log "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/dynamic" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" @@ -74,8 +75,16 @@ func (c *Consequences) app(name string) *argov1alpha1.Application { func (c *Consequences) apps() []argov1alpha1.Application { + var namespace string + if c.context.useExternalNamespace { + namespace = utils.ArgoCDExternalNamespace + } else { + namespace = utils.ArgoCDNamespace + } + fixtureClient := utils.GetE2EFixtureK8sClient() - list, err := fixtureClient.AppClientset.ArgoprojV1alpha1().Applications(utils.ArgoCDNamespace).List(context.Background(), metav1.ListOptions{}) + + list, err := fixtureClient.AppClientset.ArgoprojV1alpha1().Applications(namespace).List(context.Background(), metav1.ListOptions{}) errors.CheckError(err) if list == nil { @@ -88,7 +97,16 @@ func (c *Consequences) apps() []argov1alpha1.Application { func (c *Consequences) applicationSet(applicationSetName string) *v1alpha1.ApplicationSet { fixtureClient := utils.GetE2EFixtureK8sClient() - list, err := fixtureClient.AppSetClientset.Get(context.Background(), c.actions.context.name, metav1.GetOptions{}) + + var appSetClientSet dynamic.ResourceInterface + + if c.context.useExternalNamespace { + appSetClientSet = fixtureClient.ExternalAppSetClientset + } else { + appSetClientSet = fixtureClient.AppSetClientset + } + + list, err := appSetClientSet.Get(context.Background(), c.actions.context.name, metav1.GetOptions{}) errors.CheckError(err) var appSet v1alpha1.ApplicationSet diff --git a/test/e2e/fixture/applicationsets/context.go b/test/e2e/fixture/applicationsets/context.go index c6269c4b36b61..d2a0479a62aee 100644 --- a/test/e2e/fixture/applicationsets/context.go +++ b/test/e2e/fixture/applicationsets/context.go @@ -12,7 +12,9 @@ type Context struct { t *testing.T // name is the ApplicationSet's name, created by a Create action - name string + name string + namespace string + useExternalNamespace bool } func Given(t *testing.T) *Context { diff --git a/test/e2e/fixture/applicationsets/expectation.go b/test/e2e/fixture/applicationsets/expectation.go index e8be81b906d7e..d94770284cf61 100644 --- a/test/e2e/fixture/applicationsets/expectation.go +++ b/test/e2e/fixture/applicationsets/expectation.go @@ -64,7 +64,7 @@ func ApplicationsExist(expectedApps []argov1alpha1.Application) Expectation { for _, expectedApp := range expectedApps { foundApp := c.app(expectedApp.Name) if foundApp == nil { - return pending, fmt.Sprintf("missing app '%s'", expectedApp.Name) + return pending, fmt.Sprintf("missing app '%s'", expectedApp.QualifiedName()) } if !appsAreEqual(expectedApp, *foundApp) { @@ -74,7 +74,7 @@ func ApplicationsExist(expectedApps []argov1alpha1.Application) Expectation { return failed, err.Error() } - return pending, fmt.Sprintf("apps are not equal: '%s', diff: %s\n", expectedApp.Name, diff) + return pending, fmt.Sprintf("apps are not equal: '%s', diff: %s\n", expectedApp.QualifiedName(), diff) } @@ -113,7 +113,7 @@ func ApplicationsDoNotExist(expectedApps []argov1alpha1.Application) Expectation for _, expectedApp := range expectedApps { foundApp := c.app(expectedApp.Name) if foundApp != nil { - return pending, fmt.Sprintf("app '%s' should no longer exist", expectedApp.Name) + return pending, fmt.Sprintf("app '%s' should no longer exist", expectedApp.QualifiedName()) } } @@ -124,7 +124,7 @@ func ApplicationsDoNotExist(expectedApps []argov1alpha1.Application) Expectation // Pod checks whether a specified condition is true for any of the pods in the namespace func Pod(predicate func(p corev1.Pod) bool) Expectation { return func(c *Consequences) (state, string) { - pods, err := pods(utils.ApplicationSetNamespace) + pods, err := pods(utils.ApplicationsResourcesNamespace) if err != nil { return failed, err.Error() } diff --git a/test/e2e/fixture/applicationsets/utils/fixture.go b/test/e2e/fixture/applicationsets/utils/fixture.go index dc70ac2a02b4e..53e8888bd100c 100644 --- a/test/e2e/fixture/applicationsets/utils/fixture.go +++ b/test/e2e/fixture/applicationsets/utils/fixture.go @@ -32,10 +32,13 @@ const ( // and in which Application resources should be created. ArgoCDNamespace = "argocd-e2e" - // ApplicationSetNamespace is the namespace into which temporary resources (such as Deployments/Pods/etc) + // ArgoCDExternalNamespace is an external namespace to test additional namespaces + ArgoCDExternalNamespace = "argocd-e2e-external" + + // ApplicationsResourcesNamespace is the namespace into which temporary resources (such as Deployments/Pods/etc) // can be deployed, such as using it as the target namespace in an Application resource. // Note: this is NOT the namespace the ApplicationSet controller is deployed to; see ArgoCDNamespace. - ApplicationSetNamespace = "applicationset-e2e" + ApplicationsResourcesNamespace = "applicationset-e2e" TmpDir = "/tmp/applicationset-e2e" TestingLabel = "e2e.argoproj.io" @@ -51,16 +54,16 @@ var ( // E2EFixtureK8sClient contains Kubernetes clients initialized from local k8s configuration type E2EFixtureK8sClient struct { - KubeClientset kubernetes.Interface - DynamicClientset dynamic.Interface - AppClientset appclientset.Interface - AppSetClientset dynamic.ResourceInterface + KubeClientset kubernetes.Interface + DynamicClientset dynamic.Interface + AppClientset appclientset.Interface + AppSetClientset dynamic.ResourceInterface + ExternalAppSetClientset dynamic.ResourceInterface } // GetE2EFixtureK8sClient initializes the Kubernetes clients (if needed), and returns the most recently initalized value. // Note: this requires a local Kubernetes configuration (for example, while running the E2E tests). func GetE2EFixtureK8sClient() *E2EFixtureK8sClient { - // Initialize the Kubernetes clients only on first use clientInitialized.Do(func() { @@ -74,7 +77,7 @@ func GetE2EFixtureK8sClient() *E2EFixtureK8sClient { } internalClientVars.AppSetClientset = internalClientVars.DynamicClientset.Resource(v1alpha1.SchemeGroupVersion.WithResource("applicationsets")).Namespace(ArgoCDNamespace) - + internalClientVars.ExternalAppSetClientset = internalClientVars.DynamicClientset.Resource(v1alpha1.SchemeGroupVersion.WithResource("applicationsets")).Namespace(ArgoCDExternalNamespace) }) return internalClientVars } @@ -89,11 +92,17 @@ func EnsureCleanState(t *testing.T) { policy := v1.DeletePropagationForeground // Delete the applicationset-e2e namespace, if it exists - err := fixtureClient.KubeClientset.CoreV1().Namespaces().Delete(context.Background(), ApplicationSetNamespace, v1.DeleteOptions{PropagationPolicy: &policy}) + err := fixtureClient.KubeClientset.CoreV1().Namespaces().Delete(context.Background(), ApplicationsResourcesNamespace, v1.DeleteOptions{PropagationPolicy: &policy}) if err != nil && !strings.Contains(err.Error(), "not found") { // 'not found' error is expected CheckError(err) } + // Delete the argocd-e2e-external namespace, if it exists + err2 := fixtureClient.KubeClientset.CoreV1().Namespaces().Delete(context.Background(), ArgoCDExternalNamespace, v1.DeleteOptions{PropagationPolicy: &policy}) + if err2 != nil && !strings.Contains(err2.Error(), "not found") { // 'not found' error is expected + CheckError(err) + } + // delete resources // kubectl delete applicationsets --all CheckError(fixtureClient.AppSetClientset.DeleteCollection(context.Background(), v1.DeleteOptions{PropagationPolicy: &policy}, v1.ListOptions{})) @@ -185,12 +194,37 @@ func waitForExpectedClusterState() error { // Wait up to 120 seconds for namespace to not exist if err := waitForSuccess(func() error { - _, err := fixtureClient.KubeClientset.CoreV1().Namespaces().Get(context.Background(), ApplicationSetNamespace, v1.GetOptions{}) + _, err := fixtureClient.KubeClientset.CoreV1().Namespaces().Get(context.Background(), ApplicationsResourcesNamespace, v1.GetOptions{}) + + msg := "" + + if err == nil { + msg = fmt.Sprintf("namespace '%s' still exists, after delete", ApplicationsResourcesNamespace) + } + + if msg == "" && err != nil && strings.Contains(err.Error(), "not found") { + // Success is an error containing 'applicationset-e2e' not found. + return nil + } + + if msg == "" { + msg = err.Error() + } + + return fmt.Errorf(msg) + + }, time.Now().Add(120*time.Second)); err != nil { + return err + } + + // Wait up to 120 seconds for namespace to not exist + if err := waitForSuccess(func() error { + _, err := fixtureClient.KubeClientset.CoreV1().Namespaces().Get(context.Background(), ArgoCDExternalNamespace, v1.GetOptions{}) msg := "" if err == nil { - msg = fmt.Sprintf("namespace '%s' still exists, after delete", ApplicationSetNamespace) + msg = fmt.Sprintf("namespace '%s' still exists, after delete", ArgoCDExternalNamespace) } if msg == "" && err != nil && strings.Contains(err.Error(), "not found") { diff --git a/util/argo/argo.go b/util/argo/argo.go index 80f4c83a9d8f0..c5d4bc2b6e555 100644 --- a/util/argo/argo.go +++ b/util/argo/argo.go @@ -919,8 +919,8 @@ func GetDifferentPathsBetweenStructs(a, b interface{}) ([]string, error) { return difference, nil } -// parseAppName will -func parseAppName(appName string, defaultNs string, delim string) (string, string) { +// parseName will +func parseName(appName string, defaultNs string, delim string) (string, string) { var ns string var name string t := strings.SplitN(appName, delim, 2) @@ -937,15 +937,15 @@ func parseAppName(appName string, defaultNs string, delim string) (string, strin // ParseAppNamespacedName parses a namespaced name in the format namespace/name // and returns the components. If name wasn't namespaced, defaultNs will be // returned as namespace component. -func ParseAppQualifiedName(appName string, defaultNs string) (string, string) { - return parseAppName(appName, defaultNs, "/") +func ParseFromQualifiedName(appName string, defaultNs string) (string, string) { + return parseName(appName, defaultNs, "/") } -// ParseAppInstanceName parses a namespaced name in the format namespace_name +// ParseInstanceName parses a namespaced name in the format namespace_name // and returns the components. If name wasn't namespaced, defaultNs will be // returned as namespace component. -func ParseAppInstanceName(appName string, defaultNs string) (string, string) { - return parseAppName(appName, defaultNs, "_") +func ParseInstanceName(appName string, defaultNs string) (string, string) { + return parseName(appName, defaultNs, "_") } // AppInstanceName returns the value to be used for app instance labels from @@ -958,9 +958,9 @@ func AppInstanceName(appName, appNs, defaultNs string) string { } } -// AppInstanceNameFromQualified returns the value to be used for app -func AppInstanceNameFromQualified(name string, defaultNs string) string { - appName, appNs := ParseAppQualifiedName(name, defaultNs) +// InstanceNameFromQualified returns the value to be used for app +func InstanceNameFromQualified(name string, defaultNs string) string { + appName, appNs := ParseFromQualifiedName(name, defaultNs) return AppInstanceName(appName, appNs, defaultNs) } diff --git a/util/argo/argo_test.go b/util/argo/argo_test.go index b356751426768..51cb00eb450ec 100644 --- a/util/argo/argo_test.go +++ b/util/argo/argo_test.go @@ -1075,7 +1075,7 @@ func Test_ParseAppQualifiedName(t *testing.T) { for _, tt := range testcases { t.Run(tt.name, func(t *testing.T) { - appName, appNs := ParseAppQualifiedName(tt.input, tt.implicitNs) + appName, appNs := ParseFromQualifiedName(tt.input, tt.implicitNs) assert.Equal(t, tt.appName, appName) assert.Equal(t, tt.appNs, appNs) }) @@ -1099,7 +1099,7 @@ func Test_ParseAppInstanceName(t *testing.T) { for _, tt := range testcases { t.Run(tt.name, func(t *testing.T) { - appName, appNs := ParseAppInstanceName(tt.input, tt.implicitNs) + appName, appNs := ParseInstanceName(tt.input, tt.implicitNs) assert.Equal(t, tt.appName, appName) assert.Equal(t, tt.appNs, appNs) }) @@ -1143,7 +1143,7 @@ func Test_AppInstanceNameFromQualified(t *testing.T) { for _, tt := range testcases { t.Run(tt.name, func(t *testing.T) { - result := AppInstanceNameFromQualified(tt.appName, tt.defaultNs) + result := InstanceNameFromQualified(tt.appName, tt.defaultNs) assert.Equal(t, tt.result, result) }) } diff --git a/util/security/rbac.go b/util/security/rbac.go index ebfdde01c399e..d80cbbadb3817 100644 --- a/util/security/rbac.go +++ b/util/security/rbac.go @@ -4,8 +4,8 @@ import ( "fmt" ) -// AppRBACName constructs name of the app for use in RBAC checks. -func AppRBACName(defaultNS string, project string, namespace string, name string) string { +// RBACName constructs name of the app for use in RBAC checks. +func RBACName(defaultNS string, project string, namespace string, name string) string { if defaultNS != "" && namespace != defaultNS && namespace != "" { return fmt.Sprintf("%s/%s/%s", project, namespace, name) } else { diff --git a/util/security/rbac_test.go b/util/security/rbac_test.go index a3a447d4d5096..ca8e6dec77020 100644 --- a/util/security/rbac_test.go +++ b/util/security/rbac_test.go @@ -45,7 +45,7 @@ func Test_AppRBACName(t *testing.T) { tcc := tc t.Run(tcc.name, func(t *testing.T) { t.Parallel() - result := AppRBACName(tcc.defaultNS, tcc.project, tcc.namespace, tcc.appName) + result := RBACName(tcc.defaultNS, tcc.project, tcc.namespace, tcc.appName) assert.Equal(t, tcc.expectedResult, result) }) }