Skip to content

Commit

Permalink
feat(appset): ApplicationSet in any namespace (argoproj#12378)
Browse files Browse the repository at this point in the history
* 12107: ApplicationSet in any namespaces

Signed-off-by: gmuselli <geoffrey.muselli@gmail.com>

* 12107: fix build

Signed-off-by: Geoffrey Muselli <geoffrey.muselli@gmail.com>

* 12107: Fix lint

Signed-off-by: gmuselli <geoffrey.muselli@gmail.com>

* 12107: Fix After review 2

Signed-off-by: gmuselli <geoffrey.muselli@gmail.com>

* 12107: Fix After review 2

Signed-off-by: gmuselli <geoffrey.muselli@gmail.com>

* 12107: Fix after rebase

Signed-off-by: gmuselli <geoffrey.muselli@gmail.com>

* 12107: Fix syncspolicy after rebase

Signed-off-by: gmuselli <geoffrey.muselli@gmail.com>

* 12107: Fix tests labels

Signed-off-by: gmuselli <geoffrey.muselli@gmail.com>

* 12107: Fix tests labels 2

Signed-off-by: gmuselli <geoffrey.muselli@gmail.com>

* 12107: Fix after review

Signed-off-by: gmuselli <geoffrey.muselli@gmail.com>

* match existing appset controller arg pattern

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* remove unused env var

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

---------

Signed-off-by: gmuselli <geoffrey.muselli@gmail.com>
Signed-off-by: Geoffrey Muselli <geoffrey.muselli@gmail.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
  • Loading branch information
2 people authored and tesla59 committed Dec 16, 2023
1 parent 8e4bd15 commit d17e856
Show file tree
Hide file tree
Showing 50 changed files with 1,682 additions and 256 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand Down
27 changes: 19 additions & 8 deletions applicationset/controllers/applicationset_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand All @@ -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
}
Expand Down Expand Up @@ -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...
Expand All @@ -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{
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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 {
Expand Down
7 changes: 6 additions & 1 deletion applicationset/controllers/applicationset_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -1923,6 +1924,7 @@ func TestReconcilerValidationErrorBehaviour(t *testing.T) {
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
KubeClientset: kubeclientset,
Policy: v1alpha1.ApplicationsSyncPolicySync,
ArgoCDNamespace: "argocd",
}

req := ctrl.Request{
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -2543,6 +2547,7 @@ func TestPolicies(t *testing.T) {
"List": generators.NewListGenerator(),
},
ArgoDB: &argoDBMock,
ArgoCDNamespace: "argocd",
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
KubeClientset: kubeclientset,
Policy: policy,
Expand Down
8 changes: 4 additions & 4 deletions applicationset/utils/clusterUtils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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
}
Expand Down
18 changes: 18 additions & 0 deletions assets/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -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": {
Expand Down Expand Up @@ -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": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -52,7 +52,7 @@ func NewCommand() *cobra.Command {
probeBindAddr string
webhookAddr string
enableLeaderElection bool
namespace string
applicationSetNamespaces []string
argocdRepoServer string
policy string
enablePolicyOverride bool
Expand All @@ -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",
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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")
Expand Down
Loading

0 comments on commit d17e856

Please sign in to comment.