diff --git a/Makefile b/Makefile index a1b3e72b5a148..4786ac88340f1 100644 --- a/Makefile +++ b/Makefile @@ -459,6 +459,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_CONTROLLER_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 e0749449dc2d4..245262fe184ac 100644 --- a/applicationset/controllers/applicationset_controller.go +++ b/applicationset/controllers/applicationset_controller.go @@ -43,6 +43,7 @@ import ( "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" @@ -82,8 +83,9 @@ type ApplicationSetReconciler struct { Policy argov1alpha1.ApplicationsSyncPolicy EnablePolicyOverride bool 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 @@ -126,7 +128,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 @@ -417,7 +419,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 { @@ -429,7 +431,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) @@ -438,7 +440,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 } @@ -537,6 +539,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, enableProgressiveSyncs bool, maxConcurrentReconciliations int) error { if err := mgr.GetFieldIndexer().IndexField(context.TODO(), &argov1alpha1.Application{}, ".metadata.controller", func(rawObj client.Object) []string { // grab the job object, extract the owner... @@ -562,6 +572,7 @@ func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager, enableProg MaxConcurrentReconciles: maxConcurrentReconciliations, }).For(&argov1alpha1.ApplicationSet{}). Owns(&argov1alpha1.Application{}, builder.WithPredicates(ownsHandler)). + WithEventFilter(ignoreNotAllowedNamespaces(r.ApplicationSetNamespaces)). Watches( &source.Kind{Type: &corev1.Secret{}}, &clusterSecretEventHandler{ @@ -689,7 +700,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) } @@ -750,7 +761,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 7dc8d50181096..45a60237efcb8 100644 --- a/applicationset/controllers/applicationset_controller_test.go +++ b/applicationset/controllers/applicationset_controller_test.go @@ -1815,13 +1815,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 := v1alpha1.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()) @@ -1923,6 +1924,7 @@ func TestReconcilerValidationErrorBehaviour(t *testing.T) { ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...), KubeClientset: kubeclientset, Policy: v1alpha1.ApplicationsSyncPolicySync, + ArgoCDNamespace: "argocd", } req := ctrl.Request{ @@ -2069,6 +2071,7 @@ func applicationsUpdateSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alp "List": generators.NewListGenerator(), }, ArgoDB: &argoDBMock, + ArgoCDNamespace: "argocd", ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...), KubeClientset: kubeclientset, Policy: v1alpha1.ApplicationsSyncPolicySync, @@ -2239,6 +2242,7 @@ func applicationsDeleteSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alp "List": generators.NewListGenerator(), }, ArgoDB: &argoDBMock, + ArgoCDNamespace: "argocd", ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...), KubeClientset: kubeclientset, Policy: v1alpha1.ApplicationsSyncPolicySync, @@ -2543,6 +2547,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 5f09e83a44c22..62d3ca354eae1 100644 --- a/assets/swagger.json +++ b/assets/swagger.json @@ -1778,6 +1778,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": { @@ -1846,6 +1852,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": { @@ -1875,6 +1887,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 dcbe09ad80c7e..368328ecd8ca7 100644 --- a/cmd/argocd-applicationset-controller/commands/applicationset_controller.go +++ b/cmd/argocd-applicationset-controller/commands/applicationset_controller.go @@ -7,12 +7,12 @@ import ( "os" "time" - "github.com/argoproj/argo-cd/v2/reposerver/apiclient" - "github.com/argoproj/argo-cd/v2/util/tls" "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/reposerver/apiclient" + "github.com/argoproj/argo-cd/v2/util/tls" "github.com/argoproj/argo-cd/v2/applicationset/controllers" "github.com/argoproj/argo-cd/v2/applicationset/generators" @@ -52,7 +52,7 @@ func NewCommand() *cobra.Command { probeBindAddr string webhookAddr string enableLeaderElection bool - namespace string + applicationSetNamespaces []string argocdRepoServer string policy string enablePolicyOverride bool @@ -76,6 +76,8 @@ func NewCommand() *cobra.Command { vers := common.GetVersion() namespace, _, err := clientConfig.Namespace() + applicationSetNamespaces = append(applicationSetNamespaces, namespace) + errors.CheckError(err) vers.LogStartupInfo( "ArgoCD ApplicationSet Controller", @@ -98,19 +100,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) @@ -190,17 +198,19 @@ func NewCommand() *cobra.Command { } if err = (&controllers.ApplicationSetReconciler{ - Generators: topLevelGenerators, - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - Recorder: mgr.GetEventRecorderFor("applicationset-controller"), - Renderer: &utils.Render{}, - Policy: policyObj, - EnablePolicyOverride: enablePolicyOverride, - 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, + EnablePolicyOverride: enablePolicyOverride, + ArgoAppClientset: appSetConfig, + KubeClientset: k8sClient, + ArgoDB: argoCDDB, + ArgoCDNamespace: namespace, + ApplicationSetNamespaces: applicationSetNamespaces, + EnableProgressiveSyncs: enableProgressiveSyncs, }).SetupWithManager(mgr, enableProgressiveSyncs, maxConcurrentReconciliations); err != nil { log.Error(err, "unable to create controller", "controller", "ApplicationSet") os.Exit(1) @@ -222,7 +232,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)") + command.Flags().StringSliceVar(&applicationSetNamespaces, "applicationset-namespaces", env.StringsFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_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", ""), "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(&enablePolicyOverride, "enable-policy-override", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_POLICY_OVERRIDE", policy == ""), "For security reason if 'policy' is set, it is not possible to override it at applicationSet level. 'allow-policy-override' allows user to define their own policy") diff --git a/cmd/argocd/commands/app.go b/cmd/argocd/commands/app.go index 1889343a997e9..8bb7b0b8e8968 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, &application.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) @@ -688,7 +688,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, &application.ApplicationQuery{Name: &appName, AppNamespace: &appNs}) @@ -943,7 +943,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, &application.ApplicationQuery{ Name: &appName, Refresh: getRefreshType(refresh, hardRefresh), @@ -1187,7 +1187,7 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra. } for _, appFullName := range appNames { - appName, appNs := argo.ParseAppQualifiedName(appFullName, "") + appName, appNs := argo.ParseFromQualifiedName(appFullName, "") appDeleteReq := application.ApplicationDeleteRequest{ Name: &appName, AppNamespace: &appNs, @@ -1615,7 +1615,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 := application.ApplicationManifestQuery{ @@ -2012,7 +2012,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 @@ -2214,7 +2214,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, &application.ApplicationQuery{ Name: &appName, AppNamespace: &appNs, @@ -2265,7 +2265,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 { @@ -2347,7 +2347,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) @@ -2430,7 +2430,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, &application.OperationTerminateRequest{ @@ -2455,7 +2455,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, &application.ApplicationQuery{ @@ -2517,7 +2517,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 ed036f2d368f6..48a5e4ce58bed 100644 --- a/cmd/argocd/commands/app_actions.go +++ b/cmd/argocd/commands/app_actions.go @@ -66,7 +66,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) @@ -152,7 +152,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 8a60d0ea23361..a328842a67bb5 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 0c511f8fd68e8..ce6fab64526c3 100644 --- a/cmd/argocd/commands/applicationset_test.go +++ b/cmd/argocd/commands/applicationset_test.go @@ -17,10 +17,16 @@ func TestPrintApplicationSetNames(t *testing.T) { Name: "test", }, } - printApplicationSetNames([]v1alpha1.ApplicationSet{*appSet, *appSet}) + appSet2 := &v1alpha1.ApplicationSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "team-one", + Name: "test", + }, + } + printApplicationSetNames([]v1alpha1.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) } @@ -61,12 +67,47 @@ func TestPrintApplicationSetTable(t *testing.T) { }, }, } + + app2 := &v1alpha1.ApplicationSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "app-name", + Namespace: "team-two", + }, + Spec: v1alpha1.ApplicationSetSpec{ + Generators: []v1alpha1.ApplicationSetGenerator{ + v1alpha1.ApplicationSetGenerator{ + Git: &v1alpha1.GitGenerator{ + RepoURL: "https://github.com/argoproj/argo-cd.git", + Revision: "head", + Directories: []v1alpha1.GitDirectoryGeneratorItem{ + v1alpha1.GitDirectoryGeneratorItem{ + Path: "applicationset/examples/git-generator-directory/cluster-addons/*", + }, + }, + }, + }, + }, + Template: v1alpha1.ApplicationSetTemplate{ + Spec: v1alpha1.ApplicationSpec{ + Project: "default", + }, + }, + }, + Status: v1alpha1.ApplicationSetStatus{ + Conditions: []v1alpha1.ApplicationSetCondition{ + v1alpha1.ApplicationSetCondition{ + Status: v1alpha1.ApplicationSetConditionStatusTrue, + Type: v1alpha1.ApplicationSetConditionResourcesUpToDate, + }, + }, + }, + } output := "table" - printApplicationSetTable([]v1alpha1.ApplicationSet{*app, *app}, &output) + printApplicationSetTable([]v1alpha1.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 b7dbf49c7690e..d64c5ed02e6cb 100644 --- a/cmd/util/app.go +++ b/cmd/util/app.go @@ -600,7 +600,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..86f0655f6a7e4 --- /dev/null +++ b/docs/operator-manual/applicationset/Appset-Any-Namespace.md @@ -0,0 +1,180 @@ +# 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.8, 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 namespaces 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 namespace list from which `argocd-applicationset-controller` can source `ApplicationSets` must be explicitly set using environment variable `ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACES` or alternatively using parameter `--applicationset-namespaces`. +2. The enabled 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 + +It can be achieved by setting the environment variable `ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACES` to argocd-cmd-params-cm `applicationsetcontroller.namespaces` + +`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: + generators: + 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/operator-manual/argocd-cmd-params-cm.yaml b/docs/operator-manual/argocd-cmd-params-cm.yaml index bbb139581b297..f45b9bf44f548 100644 --- a/docs/operator-manual/argocd-cmd-params-cm.yaml +++ b/docs/operator-manual/argocd-cmd-params-cm.yaml @@ -162,8 +162,6 @@ data: ## ApplicationSet Controller Properties # Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager. applicationsetcontroller.enable.leader.election: "false" - # Argo CD repo namespace (default: argocd) - applicationsetcontroller.namespace: "" # "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)" applicationsetcontroller.policy: "sync" # Print debug logs. Takes precedence over loglevel @@ -178,6 +176,8 @@ data: applicationsetcontroller.enable.git.submodule: "true" # Enables use of the Progressive Syncs capability applicationsetcontroller.enable.progressive.syncs: "false" + # A list of glob patterns specifying where to look for ApplicationSet resources. (default is only the ns where the controller is installed) + applicationsetcontroller.namespaces: "argocd,argocd-appsets-*" ## Argo CD Notifications Controller Properties # Set the logging level. One of: debug|info|warn|error (default "info") 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/manifests/base/applicationset-controller/argocd-applicationset-controller-deployment.yaml b/manifests/base/applicationset-controller/argocd-applicationset-controller-deployment.yaml index a3d1c09523e1d..429d6d8c1e923 100644 --- a/manifests/base/applicationset-controller/argocd-applicationset-controller-deployment.yaml +++ b/manifests/base/applicationset-controller/argocd-applicationset-controller-deployment.yaml @@ -37,12 +37,6 @@ spec: key: applicationsetcontroller.enable.leader.election name: argocd-cmd-params-cm optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACE - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.namespace - name: argocd-cmd-params-cm - optional: true - name: ARGOCD_APPLICATIONSET_CONTROLLER_REPO_SERVER valueFrom: configMapKeyRef: @@ -127,6 +121,12 @@ spec: name: argocd-cmd-params-cm key: applicationsetcontroller.concurrent.reconciliations.max optional: true + - name: ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACES + valueFrom: + configMapKeyRef: + key: applicationsetcontroller.namespaces + name: argocd-cmd-params-cm + optional: true volumeMounts: - mountPath: /app/config/ssh name: ssh-known-hosts diff --git a/manifests/core-install.yaml b/manifests/core-install.yaml index 27f8f084fd2df..5d6c680cd8ae1 100644 --- a/manifests/core-install.yaml +++ b/manifests/core-install.yaml @@ -18730,12 +18730,6 @@ spec: key: applicationsetcontroller.enable.leader.election name: argocd-cmd-params-cm optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACE - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.namespace - name: argocd-cmd-params-cm - optional: true - name: ARGOCD_APPLICATIONSET_CONTROLLER_REPO_SERVER valueFrom: configMapKeyRef: @@ -18820,6 +18814,12 @@ spec: key: applicationsetcontroller.concurrent.reconciliations.max name: argocd-cmd-params-cm optional: true + - name: ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACES + valueFrom: + configMapKeyRef: + key: applicationsetcontroller.namespaces + name: argocd-cmd-params-cm + optional: true image: quay.io/argoproj/argocd:latest imagePullPolicy: Always name: argocd-applicationset-controller diff --git a/manifests/ha/install.yaml b/manifests/ha/install.yaml index e637b3a3fe679..f8b895f1ca745 100644 --- a/manifests/ha/install.yaml +++ b/manifests/ha/install.yaml @@ -19967,12 +19967,6 @@ spec: key: applicationsetcontroller.enable.leader.election name: argocd-cmd-params-cm optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACE - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.namespace - name: argocd-cmd-params-cm - optional: true - name: ARGOCD_APPLICATIONSET_CONTROLLER_REPO_SERVER valueFrom: configMapKeyRef: @@ -20057,6 +20051,12 @@ spec: key: applicationsetcontroller.concurrent.reconciliations.max name: argocd-cmd-params-cm optional: true + - name: ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACES + valueFrom: + configMapKeyRef: + key: applicationsetcontroller.namespaces + name: argocd-cmd-params-cm + optional: true image: quay.io/argoproj/argocd:latest imagePullPolicy: Always name: argocd-applicationset-controller diff --git a/manifests/ha/namespace-install.yaml b/manifests/ha/namespace-install.yaml index 7a9c1e750493c..144c25ae3a7d6 100644 --- a/manifests/ha/namespace-install.yaml +++ b/manifests/ha/namespace-install.yaml @@ -1533,12 +1533,6 @@ spec: key: applicationsetcontroller.enable.leader.election name: argocd-cmd-params-cm optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACE - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.namespace - name: argocd-cmd-params-cm - optional: true - name: ARGOCD_APPLICATIONSET_CONTROLLER_REPO_SERVER valueFrom: configMapKeyRef: @@ -1623,6 +1617,12 @@ spec: key: applicationsetcontroller.concurrent.reconciliations.max name: argocd-cmd-params-cm optional: true + - name: ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACES + valueFrom: + configMapKeyRef: + key: applicationsetcontroller.namespaces + name: argocd-cmd-params-cm + optional: true image: quay.io/argoproj/argocd:latest imagePullPolicy: Always name: argocd-applicationset-controller diff --git a/manifests/install.yaml b/manifests/install.yaml index 6aa1936ab2b50..fec96e7a9ea76 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -19068,12 +19068,6 @@ spec: key: applicationsetcontroller.enable.leader.election name: argocd-cmd-params-cm optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACE - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.namespace - name: argocd-cmd-params-cm - optional: true - name: ARGOCD_APPLICATIONSET_CONTROLLER_REPO_SERVER valueFrom: configMapKeyRef: @@ -19158,6 +19152,12 @@ spec: key: applicationsetcontroller.concurrent.reconciliations.max name: argocd-cmd-params-cm optional: true + - name: ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACES + valueFrom: + configMapKeyRef: + key: applicationsetcontroller.namespaces + name: argocd-cmd-params-cm + optional: true image: quay.io/argoproj/argocd:latest imagePullPolicy: Always name: argocd-applicationset-controller diff --git a/manifests/namespace-install.yaml b/manifests/namespace-install.yaml index 2bd9f51e2895f..76a2971d68f5f 100644 --- a/manifests/namespace-install.yaml +++ b/manifests/namespace-install.yaml @@ -634,12 +634,6 @@ spec: key: applicationsetcontroller.enable.leader.election name: argocd-cmd-params-cm optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACE - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.namespace - name: argocd-cmd-params-cm - optional: true - name: ARGOCD_APPLICATIONSET_CONTROLLER_REPO_SERVER valueFrom: configMapKeyRef: @@ -724,6 +718,12 @@ spec: key: applicationsetcontroller.concurrent.reconciliations.max name: argocd-cmd-params-cm optional: true + - name: ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACES + valueFrom: + configMapKeyRef: + key: applicationsetcontroller.namespaces + name: argocd-cmd-params-cm + optional: true image: quay.io/argoproj/argocd:latest imagePullPolicy: Always name: argocd-applicationset-controller diff --git a/mkdocs.yml b/mkdocs.yml index 9c63d4f5c6e1a..e0ced6bbb343b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -113,6 +113,7 @@ nav: - Progressive Syncs: operator-manual/applicationset/Progressive-Syncs.md - Git File Generator Globbing: operator-manual/applicationset/Generators-Git-File-Globbing.md - ApplicationSet Specification Reference: operator-manual/applicationset/applicationset-specification.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 b450503e017c9..de48d10923261 100644 --- a/pkg/apiclient/apiclient.go +++ b/pkg/apiclient/apiclient.go @@ -805,7 +805,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 a1d76f6e5ae6b..3c8b3a34a018b 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. @@ -814,3 +815,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 338598791f9b0..62ff3aac4e95d 100644 --- a/pkg/apis/application/v1alpha1/applicationset_types_test.go +++ b/pkg/apis/application/v1alpha1/applicationset_types_test.go @@ -49,6 +49,31 @@ func TestApplicationsSyncPolicy(t *testing.T) { assert.True(t, ApplicationsSyncPolicySync.AllowUpdate()) } +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 7a721d8c0266b..2f8617be915ac 100644 --- a/pkg/apis/application/v1alpha1/types.go +++ b/pkg/apis/application/v1alpha1/types.go @@ -3015,5 +3015,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 5bcbde968a6f0..813caaa750585 100644 --- a/reposerver/repository/repository.go +++ b/reposerver/repository/repository.go @@ -1057,7 +1057,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 c620bb8e97ce7..2dcefc121dfca 100644 --- a/server/application/application_test.go +++ b/server/application/application_test.go @@ -1879,7 +1879,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 abe95358c2a3b..26de80530e93b 100644 --- a/server/applicationset/applicationset.go +++ b/server/applicationset/applicationset.go @@ -30,23 +30,25 @@ import ( "github.com/argoproj/argo-cd/v2/util/argo" "github.com/argoproj/argo-cd/v2/util/db" "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 @@ -63,31 +65,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 } @@ -96,20 +107,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 !security.IsNamespaceEnabled(a.Namespace, s.ns, s.enabledNamespaces) { + continue + } + + if s.enf.Enforce(ctx.Value("claims"), rbacpolicy.ResourceApplicationSets, rbacpolicy.ActionGet, a.RBACName(s.ns)) { newItems = append(newItems, a) } } @@ -143,6 +161,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) } @@ -150,7 +174,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) @@ -180,7 +204,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) @@ -208,11 +232,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 } } @@ -247,19 +271,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) } @@ -288,7 +314,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 } @@ -345,3 +371,15 @@ func (s *Server) logAppSetEvent(a *v1alpha1.ApplicationSet, ctx context.Context, message := fmt.Sprintf("%s %s", user, action) s.auditLogger.LogAppSetEvent(a, eventInfo, message, user) } + +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 67cea4681c4d9..a9cdf4346ffdd 100644 --- a/test/e2e/app_management_test.go +++ b/test/e2e/app_management_test.go @@ -1459,7 +1459,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 4922a50d29858..7cabe65a5637b 100644 --- a/test/e2e/applicationset_test.go +++ b/test/e2e/applicationset_test.go @@ -46,6 +46,111 @@ var ( LabelKeyAppSetInstance = "argocd.argoproj.io/application-set-name" ) +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, + Labels: map[string]string{ + LabelKeyAppSetInstance: "simple-list-generator-external", + }, + 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", + LabelKeyAppSetInstance: "simple-list-generator-external", + } + }). + 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", + LabelKeyAppSetInstance: "simple-list-generator-external", + } + }).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{ @@ -1030,7 +1135,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", @@ -1047,7 +1152,7 @@ func TestSimpleGitFilesPreserveResourcesOnDeletion(t *testing.T) { }, Destination: argov1alpha1.ApplicationDestination{ Server: "https://kubernetes.default.svc", - Namespace: utils.ApplicationSetNamespace, + Namespace: utils.ApplicationsResourcesNamespace, }, // Automatically create resources @@ -1090,7 +1195,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", @@ -1108,7 +1213,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 4906ba9a4f641..9b744241adf75 100644 --- a/test/e2e/cluster_generator_test.go +++ b/test/e2e/cluster_generator_test.go @@ -11,10 +11,116 @@ import ( "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" . "github.com/argoproj/argo-cd/v2/test/e2e/fixture/applicationsets" + "github.com/argoproj/argo-cd/v2/test/e2e/fixture/applicationsets/utils" "github.com/argoproj/argo-cd/v2/pkg/apis/application" ) +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, + Labels: map[string]string{ + LabelKeyAppSetInstance: "simple-cluster-generator", + }, + 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", + LabelKeyAppSetInstance: "simple-cluster-generator", + } + }). + 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", + LabelKeyAppSetInstance: "simple-cluster-generator", + } + }).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 df5e753e3e032..94081d705fd77 100644 --- a/test/e2e/clusterdecisiongenerator_e2e_test.go +++ b/test/e2e/clusterdecisiongenerator_e2e_test.go @@ -10,12 +10,126 @@ import ( "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" . "github.com/argoproj/argo-cd/v2/test/e2e/fixture/applicationsets" + "github.com/argoproj/argo-cd/v2/test/e2e/fixture/applicationsets/utils" "github.com/argoproj/argo-cd/v2/pkg/apis/application" ) 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, + Labels: map[string]string{ + LabelKeyAppSetInstance: "simple-cluster-generator", + }, + 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", + LabelKeyAppSetInstance: "simple-cluster-generator", + } + }). + 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", + LabelKeyAppSetInstance: "simple-cluster-generator", + } + }).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 956d62ad86707..ee9a857988f99 100644 --- a/test/e2e/fixture/applicationsets/actions.go +++ b/test/e2e/fixture/applicationsets/actions.go @@ -15,6 +15,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" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" @@ -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 305a06a4d7174..2672b58fe9317 100644 --- a/test/e2e/fixture/applicationsets/consequences.go +++ b/test/e2e/fixture/applicationsets/consequences.go @@ -3,12 +3,14 @@ package applicationsets import ( "context" "encoding/json" - "github.com/argoproj/argo-cd/v2/test/e2e/fixture" "time" + "github.com/argoproj/argo-cd/v2/test/e2e/fixture" + "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" "github.com/argoproj/argo-cd/v2/test/e2e/fixture/applicationsets/utils" @@ -74,8 +76,15 @@ func (c *Consequences) app(name string) *v1alpha1.Application { func (c *Consequences) apps() []v1alpha1.Application { + var namespace string + if c.context.useExternalNamespace { + namespace = utils.ArgoCDExternalNamespace + } else { + namespace = fixture.TestNamespace() + } + fixtureClient := utils.GetE2EFixtureK8sClient() - list, err := fixtureClient.AppClientset.ArgoprojV1alpha1().Applications(fixture.TestNamespace()).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() []v1alpha1.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 fce722b70c9e3..990ad5f33dbfb 100644 --- a/test/e2e/fixture/applicationsets/expectation.go +++ b/test/e2e/fixture/applicationsets/expectation.go @@ -63,7 +63,7 @@ func ApplicationsExist(expectedApps []v1alpha1.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) { @@ -73,7 +73,7 @@ func ApplicationsExist(expectedApps []v1alpha1.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) } @@ -112,7 +112,7 @@ func ApplicationsDoNotExist(expectedApps []v1alpha1.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()) } } @@ -123,7 +123,7 @@ func ApplicationsDoNotExist(expectedApps []v1alpha1.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 1f782f5fc3ea4..6cf984f99afc7 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,10 +54,11 @@ 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 } func GetEnvWithDefault(envName, defaultValue string) string { @@ -74,7 +78,6 @@ func TestNamespace() string { // 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() { @@ -88,7 +91,7 @@ func GetE2EFixtureK8sClient() *E2EFixtureK8sClient { } internalClientVars.AppSetClientset = internalClientVars.DynamicClientset.Resource(v1alpha1.SchemeGroupVersion.WithResource("applicationsets")).Namespace(TestNamespace()) - + internalClientVars.ExternalAppSetClientset = internalClientVars.DynamicClientset.Resource(v1alpha1.SchemeGroupVersion.WithResource("applicationsets")).Namespace(ArgoCDExternalNamespace) }) return internalClientVars } @@ -103,11 +106,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{})) @@ -199,12 +208,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 194f8a46c07d6..5319fb6fbb277 100644 --- a/util/argo/argo.go +++ b/util/argo/argo.go @@ -1014,8 +1014,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) @@ -1032,15 +1032,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 @@ -1053,9 +1053,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 1d8deca0f8be2..021e161b6e0dd 100644 --- a/util/argo/argo_test.go +++ b/util/argo/argo_test.go @@ -1183,7 +1183,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) }) @@ -1207,7 +1207,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) }) @@ -1251,7 +1251,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) }) }