github.com/argoproj/argo-cd/v3@v3.2.1/server/applicationset/applicationset.go (about)

     1  package applicationset
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"reflect"
     9  	"sort"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/argoproj/pkg/v2/sync"
    15  	log "github.com/sirupsen/logrus"
    16  	"google.golang.org/grpc/codes"
    17  	"google.golang.org/grpc/status"
    18  	corev1 "k8s.io/api/core/v1"
    19  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    20  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    21  	"k8s.io/apimachinery/pkg/labels"
    22  	"k8s.io/client-go/dynamic"
    23  	"k8s.io/client-go/kubernetes"
    24  	"k8s.io/client-go/tools/cache"
    25  	"sigs.k8s.io/controller-runtime/pkg/client"
    26  
    27  	appsettemplate "github.com/argoproj/argo-cd/v3/applicationset/controllers/template"
    28  	"github.com/argoproj/argo-cd/v3/applicationset/generators"
    29  	"github.com/argoproj/argo-cd/v3/applicationset/services"
    30  	appsetstatus "github.com/argoproj/argo-cd/v3/applicationset/status"
    31  	appsetutils "github.com/argoproj/argo-cd/v3/applicationset/utils"
    32  	"github.com/argoproj/argo-cd/v3/pkg/apiclient/applicationset"
    33  	"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    34  	appclientset "github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned"
    35  	applisters "github.com/argoproj/argo-cd/v3/pkg/client/listers/application/v1alpha1"
    36  	repoapiclient "github.com/argoproj/argo-cd/v3/reposerver/apiclient"
    37  	"github.com/argoproj/argo-cd/v3/util/argo"
    38  	"github.com/argoproj/argo-cd/v3/util/collections"
    39  	"github.com/argoproj/argo-cd/v3/util/db"
    40  	"github.com/argoproj/argo-cd/v3/util/github_app"
    41  	"github.com/argoproj/argo-cd/v3/util/rbac"
    42  	"github.com/argoproj/argo-cd/v3/util/security"
    43  	"github.com/argoproj/argo-cd/v3/util/session"
    44  )
    45  
    46  type Server struct {
    47  	ns                       string
    48  	db                       db.ArgoDB
    49  	enf                      *rbac.Enforcer
    50  	k8sClient                kubernetes.Interface
    51  	dynamicClient            dynamic.Interface
    52  	client                   client.Client
    53  	repoClientSet            repoapiclient.Clientset
    54  	appclientset             appclientset.Interface
    55  	appsetInformer           cache.SharedIndexInformer
    56  	appsetLister             applisters.ApplicationSetLister
    57  	auditLogger              *argo.AuditLogger
    58  	projectLock              sync.KeyLock
    59  	enabledNamespaces        []string
    60  	GitSubmoduleEnabled      bool
    61  	EnableNewGitFileGlobbing bool
    62  	ScmRootCAPath            string
    63  	AllowedScmProviders      []string
    64  	EnableScmProviders       bool
    65  	EnableGitHubAPIMetrics   bool
    66  }
    67  
    68  // NewServer returns a new instance of the ApplicationSet service
    69  func NewServer(
    70  	db db.ArgoDB,
    71  	kubeclientset kubernetes.Interface,
    72  	dynamicClientset dynamic.Interface,
    73  	kubeControllerClientset client.Client,
    74  	enf *rbac.Enforcer,
    75  	repoClientSet repoapiclient.Clientset,
    76  	appclientset appclientset.Interface,
    77  	appsetInformer cache.SharedIndexInformer,
    78  	appsetLister applisters.ApplicationSetLister,
    79  	namespace string,
    80  	projectLock sync.KeyLock,
    81  	enabledNamespaces []string,
    82  	gitSubmoduleEnabled bool,
    83  	enableNewGitFileGlobbing bool,
    84  	scmRootCAPath string,
    85  	allowedScmProviders []string,
    86  	enableScmProviders bool,
    87  	enableGitHubAPIMetrics bool,
    88  	enableK8sEvent []string,
    89  ) applicationset.ApplicationSetServiceServer {
    90  	s := &Server{
    91  		ns:                       namespace,
    92  		db:                       db,
    93  		enf:                      enf,
    94  		dynamicClient:            dynamicClientset,
    95  		client:                   kubeControllerClientset,
    96  		k8sClient:                kubeclientset,
    97  		repoClientSet:            repoClientSet,
    98  		appclientset:             appclientset,
    99  		appsetInformer:           appsetInformer,
   100  		appsetLister:             appsetLister,
   101  		projectLock:              projectLock,
   102  		auditLogger:              argo.NewAuditLogger(kubeclientset, "argocd-server", enableK8sEvent),
   103  		enabledNamespaces:        enabledNamespaces,
   104  		GitSubmoduleEnabled:      gitSubmoduleEnabled,
   105  		EnableNewGitFileGlobbing: enableNewGitFileGlobbing,
   106  		ScmRootCAPath:            scmRootCAPath,
   107  		AllowedScmProviders:      allowedScmProviders,
   108  		EnableScmProviders:       enableScmProviders,
   109  		EnableGitHubAPIMetrics:   enableGitHubAPIMetrics,
   110  	}
   111  	return s
   112  }
   113  
   114  func (s *Server) Get(ctx context.Context, q *applicationset.ApplicationSetGetQuery) (*v1alpha1.ApplicationSet, error) {
   115  	namespace := s.appsetNamespaceOrDefault(q.AppsetNamespace)
   116  
   117  	if !s.isNamespaceEnabled(namespace) {
   118  		return nil, security.NamespaceNotPermittedError(namespace)
   119  	}
   120  
   121  	a, err := s.appsetLister.ApplicationSets(namespace).Get(q.Name)
   122  	if err != nil {
   123  		return nil, fmt.Errorf("error getting ApplicationSet: %w", err)
   124  	}
   125  	err = s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceApplicationSets, rbac.ActionGet, a.RBACName(s.ns))
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	return a, nil
   131  }
   132  
   133  // List returns list of ApplicationSets
   134  func (s *Server) List(ctx context.Context, q *applicationset.ApplicationSetListQuery) (*v1alpha1.ApplicationSetList, error) {
   135  	selector, err := labels.Parse(q.GetSelector())
   136  	if err != nil {
   137  		return nil, fmt.Errorf("error parsing the selector: %w", err)
   138  	}
   139  
   140  	var appsets []*v1alpha1.ApplicationSet
   141  	if q.AppsetNamespace == "" {
   142  		appsets, err = s.appsetLister.List(selector)
   143  	} else {
   144  		appsets, err = s.appsetLister.ApplicationSets(q.AppsetNamespace).List(selector)
   145  	}
   146  
   147  	if err != nil {
   148  		return nil, fmt.Errorf("error listing ApplicationSets with selectors: %w", err)
   149  	}
   150  
   151  	newItems := make([]v1alpha1.ApplicationSet, 0)
   152  	for _, a := range appsets {
   153  		// Skip any application that is neither in the conrol plane's namespace
   154  		// nor in the list of enabled namespaces.
   155  		if !security.IsNamespaceEnabled(a.Namespace, s.ns, s.enabledNamespaces) {
   156  			continue
   157  		}
   158  
   159  		if s.enf.Enforce(ctx.Value("claims"), rbac.ResourceApplicationSets, rbac.ActionGet, a.RBACName(s.ns)) {
   160  			newItems = append(newItems, *a)
   161  		}
   162  	}
   163  
   164  	newItems = argo.FilterAppSetsByProjects(newItems, q.Projects)
   165  
   166  	// Sort found applicationsets by name
   167  	sort.Slice(newItems, func(i, j int) bool {
   168  		return newItems[i].Name < newItems[j].Name
   169  	})
   170  
   171  	appsetList := &v1alpha1.ApplicationSetList{
   172  		ListMeta: metav1.ListMeta{
   173  			ResourceVersion: s.appsetInformer.LastSyncResourceVersion(),
   174  		},
   175  		Items: newItems,
   176  	}
   177  	return appsetList, nil
   178  }
   179  
   180  func (s *Server) Create(ctx context.Context, q *applicationset.ApplicationSetCreateRequest) (*v1alpha1.ApplicationSet, error) {
   181  	appset := q.GetApplicationset()
   182  
   183  	if appset == nil {
   184  		return nil, errors.New("error creating ApplicationSets: ApplicationSets is nil in request")
   185  	}
   186  
   187  	projectName, err := s.validateAppSet(appset)
   188  	if err != nil {
   189  		return nil, fmt.Errorf("error validating ApplicationSets: %w", err)
   190  	}
   191  
   192  	namespace := s.appsetNamespaceOrDefault(appset.Namespace)
   193  
   194  	if !s.isNamespaceEnabled(namespace) {
   195  		return nil, security.NamespaceNotPermittedError(namespace)
   196  	}
   197  
   198  	if err := s.checkCreatePermissions(ctx, appset, projectName); err != nil {
   199  		return nil, fmt.Errorf("error checking create permissions for ApplicationSets %s : %w", appset.Name, err)
   200  	}
   201  
   202  	if q.GetDryRun() {
   203  		apps, err := s.generateApplicationSetApps(ctx, log.WithField("applicationset", appset.Name), *appset)
   204  		if err != nil {
   205  			return nil, fmt.Errorf("unable to generate Applications of ApplicationSet: %w", err)
   206  		}
   207  
   208  		statusMap := appsetstatus.GetResourceStatusMap(appset)
   209  		statusMap = appsetstatus.BuildResourceStatus(statusMap, apps)
   210  
   211  		statuses := []v1alpha1.ResourceStatus{}
   212  		for _, status := range statusMap {
   213  			statuses = append(statuses, status)
   214  		}
   215  		appset.Status.Resources = statuses
   216  		return appset, nil
   217  	}
   218  
   219  	s.projectLock.RLock(projectName)
   220  	defer s.projectLock.RUnlock(projectName)
   221  
   222  	created, err := s.appclientset.ArgoprojV1alpha1().ApplicationSets(namespace).Create(ctx, appset, metav1.CreateOptions{})
   223  	if err == nil {
   224  		s.logAppSetEvent(ctx, created, argo.EventReasonResourceCreated, "created ApplicationSet")
   225  		s.waitSync(created)
   226  		return created, nil
   227  	}
   228  
   229  	if !apierrors.IsAlreadyExists(err) {
   230  		return nil, fmt.Errorf("error creating ApplicationSet: %w", err)
   231  	}
   232  	// act idempotent if existing spec matches new spec
   233  	existing, err := s.appclientset.ArgoprojV1alpha1().ApplicationSets(namespace).Get(ctx, appset.Name, metav1.GetOptions{
   234  		ResourceVersion: "",
   235  	})
   236  	if err != nil {
   237  		return nil, status.Errorf(codes.Internal, "unable to check existing ApplicationSet details: %v", err)
   238  	}
   239  
   240  	equalSpecs := reflect.DeepEqual(existing.Spec, appset.Spec) &&
   241  		reflect.DeepEqual(existing.Labels, appset.Labels) &&
   242  		reflect.DeepEqual(existing.Annotations, appset.Annotations) &&
   243  		reflect.DeepEqual(existing.Finalizers, appset.Finalizers)
   244  
   245  	if equalSpecs {
   246  		return existing, nil
   247  	}
   248  
   249  	if !q.Upsert {
   250  		return nil, status.Errorf(codes.InvalidArgument, "existing ApplicationSet spec is different, use upsert flag to force update")
   251  	}
   252  	err = s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceApplicationSets, rbac.ActionUpdate, appset.RBACName(s.ns))
   253  	if err != nil {
   254  		return nil, err
   255  	}
   256  	updated, err := s.updateAppSet(ctx, existing, appset, true)
   257  	if err != nil {
   258  		return nil, fmt.Errorf("error updating ApplicationSets: %w", err)
   259  	}
   260  	return updated, nil
   261  }
   262  
   263  func (s *Server) generateApplicationSetApps(ctx context.Context, logEntry *log.Entry, appset v1alpha1.ApplicationSet) ([]v1alpha1.Application, error) {
   264  	argoCDDB := s.db
   265  
   266  	scmConfig := generators.NewSCMConfig(s.ScmRootCAPath, s.AllowedScmProviders, s.EnableScmProviders, s.EnableGitHubAPIMetrics, github_app.NewAuthCredentials(argoCDDB.(db.RepoCredsDB)), true)
   267  	argoCDService := services.NewArgoCDService(s.db, s.GitSubmoduleEnabled, s.repoClientSet, s.EnableNewGitFileGlobbing)
   268  	appSetGenerators := generators.GetGenerators(ctx, s.client, s.k8sClient, s.ns, argoCDService, s.dynamicClient, scmConfig)
   269  
   270  	apps, _, err := appsettemplate.GenerateApplications(logEntry, appset, appSetGenerators, &appsetutils.Render{}, s.client)
   271  	if err != nil {
   272  		return nil, fmt.Errorf("error generating applications: %w", err)
   273  	}
   274  	return apps, nil
   275  }
   276  
   277  func (s *Server) updateAppSet(ctx context.Context, appset *v1alpha1.ApplicationSet, newAppset *v1alpha1.ApplicationSet, merge bool) (*v1alpha1.ApplicationSet, error) {
   278  	if appset != nil && appset.Spec.Template.Spec.Project != newAppset.Spec.Template.Spec.Project {
   279  		// When changing projects, caller must have applicationset create and update privileges in new project
   280  		// NOTE: the update check was already verified in the caller to this function
   281  		if err := s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceApplicationSets, rbac.ActionCreate, newAppset.RBACName(s.ns)); err != nil {
   282  			return nil, err
   283  		}
   284  		// They also need 'update' privileges in the old project
   285  		if err := s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceApplicationSets, rbac.ActionUpdate, appset.RBACName(s.ns)); err != nil {
   286  			return nil, err
   287  		}
   288  	}
   289  
   290  	for i := 0; i < 10; i++ {
   291  		appset.Spec = newAppset.Spec
   292  		if merge {
   293  			appset.Labels = collections.Merge(appset.Labels, newAppset.Labels)
   294  			appset.Annotations = collections.Merge(appset.Annotations, newAppset.Annotations)
   295  		} else {
   296  			appset.Labels = newAppset.Labels
   297  			appset.Annotations = newAppset.Annotations
   298  		}
   299  		appset.Finalizers = newAppset.Finalizers
   300  		res, err := s.appclientset.ArgoprojV1alpha1().ApplicationSets(appset.Namespace).Update(ctx, appset, metav1.UpdateOptions{})
   301  		if err == nil {
   302  			s.logAppSetEvent(ctx, appset, argo.EventReasonResourceUpdated, "updated ApplicationSets spec")
   303  			s.waitSync(res)
   304  			return res, nil
   305  		}
   306  		if !apierrors.IsConflict(err) {
   307  			return nil, err
   308  		}
   309  
   310  		appset, err = s.appclientset.ArgoprojV1alpha1().ApplicationSets(appset.Namespace).Get(ctx, appset.Name, metav1.GetOptions{})
   311  		if err != nil {
   312  			return nil, fmt.Errorf("error getting ApplicationSets: %w", err)
   313  		}
   314  	}
   315  	return nil, status.Errorf(codes.Internal, "Failed to update ApplicationSets. Too many conflicts")
   316  }
   317  
   318  func (s *Server) Delete(ctx context.Context, q *applicationset.ApplicationSetDeleteRequest) (*applicationset.ApplicationSetResponse, error) {
   319  	namespace := s.appsetNamespaceOrDefault(q.AppsetNamespace)
   320  
   321  	appset, err := s.appclientset.ArgoprojV1alpha1().ApplicationSets(namespace).Get(ctx, q.Name, metav1.GetOptions{})
   322  	if err != nil {
   323  		return nil, fmt.Errorf("error getting ApplicationSets: %w", err)
   324  	}
   325  
   326  	if err := s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceApplicationSets, rbac.ActionDelete, appset.RBACName(s.ns)); err != nil {
   327  		return nil, err
   328  	}
   329  
   330  	s.projectLock.RLock(appset.Spec.Template.Spec.Project)
   331  	defer s.projectLock.RUnlock(appset.Spec.Template.Spec.Project)
   332  
   333  	err = s.appclientset.ArgoprojV1alpha1().ApplicationSets(namespace).Delete(ctx, q.Name, metav1.DeleteOptions{})
   334  	if err != nil {
   335  		return nil, fmt.Errorf("error deleting ApplicationSets: %w", err)
   336  	}
   337  	s.logAppSetEvent(ctx, appset, argo.EventReasonResourceDeleted, "deleted ApplicationSets")
   338  	return &applicationset.ApplicationSetResponse{}, nil
   339  }
   340  
   341  func (s *Server) ResourceTree(ctx context.Context, q *applicationset.ApplicationSetTreeQuery) (*v1alpha1.ApplicationSetTree, error) {
   342  	namespace := s.appsetNamespaceOrDefault(q.AppsetNamespace)
   343  
   344  	if !s.isNamespaceEnabled(namespace) {
   345  		return nil, security.NamespaceNotPermittedError(namespace)
   346  	}
   347  
   348  	a, err := s.appclientset.ArgoprojV1alpha1().ApplicationSets(namespace).Get(ctx, q.Name, metav1.GetOptions{})
   349  	if err != nil {
   350  		return nil, fmt.Errorf("error getting ApplicationSet: %w", err)
   351  	}
   352  	err = s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceApplicationSets, rbac.ActionGet, a.RBACName(s.ns))
   353  	if err != nil {
   354  		return nil, err
   355  	}
   356  
   357  	return s.buildApplicationSetTree(a)
   358  }
   359  
   360  func (s *Server) Generate(ctx context.Context, q *applicationset.ApplicationSetGenerateRequest) (*applicationset.ApplicationSetGenerateResponse, error) {
   361  	appset := q.GetApplicationSet()
   362  
   363  	if appset == nil {
   364  		return nil, errors.New("error creating ApplicationSets: ApplicationSets is nil in request")
   365  	}
   366  
   367  	// The RBAC check needs to be performed against the appset namespace
   368  	// However, when trying to generate params, the server namespace needs
   369  	// to be passed.
   370  	namespace := s.appsetNamespaceOrDefault(appset.Namespace)
   371  	if !s.isNamespaceEnabled(namespace) {
   372  		return nil, security.NamespaceNotPermittedError(namespace)
   373  	}
   374  
   375  	projectName, err := s.validateAppSet(appset)
   376  	if err != nil {
   377  		return nil, fmt.Errorf("error validating ApplicationSets: %w", err)
   378  	}
   379  	if err := s.checkCreatePermissions(ctx, appset, projectName); err != nil {
   380  		return nil, fmt.Errorf("error checking create permissions for ApplicationSets %s : %w", appset.Name, err)
   381  	}
   382  
   383  	logs := bytes.NewBuffer(nil)
   384  	logger := log.New()
   385  	logger.SetOutput(logs)
   386  
   387  	// The server namespace will be used in the function
   388  	// since this is the exact namespace that is being used
   389  	// to generate parameters (especially for git generator).
   390  	//
   391  	// In case of Git generator, if the namespace is set to
   392  	// appset namespace, we'll look for a project in the appset
   393  	// namespace that would lead to error when generating params
   394  	// for an appset in any namespace feature.
   395  	// See https://github.com/argoproj/argo-cd/issues/22942
   396  	apps, err := s.generateApplicationSetApps(ctx, logger.WithField("applicationset", appset.Name), *appset)
   397  	if err != nil {
   398  		return nil, fmt.Errorf("unable to generate Applications of ApplicationSet: %w\n%s", err, logs.String())
   399  	}
   400  	res := &applicationset.ApplicationSetGenerateResponse{}
   401  	for i := range apps {
   402  		res.Applications = append(res.Applications, &apps[i])
   403  	}
   404  	return res, nil
   405  }
   406  
   407  func (s *Server) buildApplicationSetTree(a *v1alpha1.ApplicationSet) (*v1alpha1.ApplicationSetTree, error) {
   408  	var tree v1alpha1.ApplicationSetTree
   409  
   410  	gvk := v1alpha1.ApplicationSetSchemaGroupVersionKind
   411  	parentRefs := []v1alpha1.ResourceRef{
   412  		{Group: gvk.Group, Version: gvk.Version, Kind: gvk.Kind, Name: a.Name, Namespace: a.Namespace, UID: string(a.UID)},
   413  	}
   414  
   415  	apps := a.Status.Resources
   416  	for _, app := range apps {
   417  		tree.Nodes = append(tree.Nodes, v1alpha1.ResourceNode{
   418  			Health: app.Health,
   419  			ResourceRef: v1alpha1.ResourceRef{
   420  				Name:      app.Name,
   421  				Group:     app.Group,
   422  				Version:   app.Version,
   423  				Kind:      app.Kind,
   424  				Namespace: a.Namespace,
   425  			},
   426  			ParentRefs: parentRefs,
   427  		})
   428  	}
   429  	tree.Normalize()
   430  
   431  	return &tree, nil
   432  }
   433  
   434  func (s *Server) validateAppSet(appset *v1alpha1.ApplicationSet) (string, error) {
   435  	if appset == nil {
   436  		return "", errors.New("ApplicationSet cannot be validated for nil value")
   437  	}
   438  
   439  	projectName := appset.Spec.Template.Spec.Project
   440  
   441  	if strings.Contains(projectName, "{{") {
   442  		return "", errors.New("the Argo CD API does not currently support creating ApplicationSets with templated `project` fields")
   443  	}
   444  
   445  	if err := appsetutils.CheckInvalidGenerators(appset); err != nil {
   446  		return "", err
   447  	}
   448  
   449  	return projectName, nil
   450  }
   451  
   452  func (s *Server) checkCreatePermissions(ctx context.Context, appset *v1alpha1.ApplicationSet, projectName string) error {
   453  	if err := s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceApplicationSets, rbac.ActionCreate, appset.RBACName(s.ns)); err != nil {
   454  		return err
   455  	}
   456  
   457  	_, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, projectName, metav1.GetOptions{})
   458  	if err != nil {
   459  		if apierrors.IsNotFound(err) {
   460  			return status.Errorf(codes.InvalidArgument, "ApplicationSet references project %s which does not exist", projectName)
   461  		}
   462  		return fmt.Errorf("error getting ApplicationSet's project %q: %w", projectName, err)
   463  	}
   464  
   465  	return nil
   466  }
   467  
   468  var informerSyncTimeout = 2 * time.Second
   469  
   470  // waitSync is a helper to wait until the application informer cache is synced after create/update.
   471  // It waits until the app in the informer, has a resource version greater than the version in the
   472  // supplied app, or after 2 seconds, whichever comes first. Returns true if synced.
   473  // We use an informer cache for read operations (Get, List). Since the cache is only
   474  // eventually consistent, it is possible that it doesn't reflect an application change immediately
   475  // after a mutating API call (create/update). This function should be called after a creates &
   476  // update to give a probable (but not guaranteed) chance of being up-to-date after the create/update.
   477  func (s *Server) waitSync(appset *v1alpha1.ApplicationSet) {
   478  	logCtx := log.WithField("applicationset", appset.Name)
   479  	deadline := time.Now().Add(informerSyncTimeout)
   480  	minVersion, err := strconv.Atoi(appset.ResourceVersion)
   481  	if err != nil {
   482  		logCtx.Warnf("waitSync failed: could not parse resource version %s", appset.ResourceVersion)
   483  		time.Sleep(50 * time.Millisecond) // sleep anyway
   484  		return
   485  	}
   486  	for {
   487  		if currAppset, err := s.appsetLister.ApplicationSets(appset.Namespace).Get(appset.Name); err == nil {
   488  			currVersion, err := strconv.Atoi(currAppset.ResourceVersion)
   489  			if err == nil && currVersion >= minVersion {
   490  				return
   491  			}
   492  		}
   493  		if time.Now().After(deadline) {
   494  			break
   495  		}
   496  		time.Sleep(20 * time.Millisecond)
   497  	}
   498  	logCtx.Warnf("waitSync failed: timed out")
   499  }
   500  
   501  func (s *Server) logAppSetEvent(ctx context.Context, a *v1alpha1.ApplicationSet, reason string, action string) {
   502  	eventInfo := argo.EventInfo{Type: corev1.EventTypeNormal, Reason: reason}
   503  	user := session.Username(ctx)
   504  	if user == "" {
   505  		user = "Unknown user"
   506  	}
   507  	message := fmt.Sprintf("%s %s", user, action)
   508  	s.auditLogger.LogAppSetEvent(a, eventInfo, message, user)
   509  }
   510  
   511  func (s *Server) appsetNamespaceOrDefault(appNs string) string {
   512  	if appNs == "" {
   513  		return s.ns
   514  	}
   515  	return appNs
   516  }
   517  
   518  func (s *Server) isNamespaceEnabled(namespace string) bool {
   519  	return security.IsNamespaceEnabled(namespace, s.ns, s.enabledNamespaces)
   520  }