github.com/argoproj/argo-cd@v1.8.7/util/argo/argo.go (about)

     1  package argo
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/argoproj/gitops-engine/pkg/utils/kube"
    11  	log "github.com/sirupsen/logrus"
    12  	"google.golang.org/grpc/codes"
    13  	"google.golang.org/grpc/status"
    14  	apierr "k8s.io/apimachinery/pkg/api/errors"
    15  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    16  	"k8s.io/apimachinery/pkg/fields"
    17  	"k8s.io/apimachinery/pkg/runtime/schema"
    18  	"k8s.io/apimachinery/pkg/types"
    19  	"k8s.io/apimachinery/pkg/watch"
    20  
    21  	"github.com/argoproj/argo-cd/common"
    22  	argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
    23  	"github.com/argoproj/argo-cd/pkg/client/clientset/versioned/typed/application/v1alpha1"
    24  	applicationsv1 "github.com/argoproj/argo-cd/pkg/client/listers/application/v1alpha1"
    25  	"github.com/argoproj/argo-cd/reposerver/apiclient"
    26  	"github.com/argoproj/argo-cd/util/db"
    27  	"github.com/argoproj/argo-cd/util/git"
    28  	"github.com/argoproj/argo-cd/util/helm"
    29  	"github.com/argoproj/argo-cd/util/io"
    30  	"github.com/argoproj/argo-cd/util/settings"
    31  )
    32  
    33  const (
    34  	errDestinationMissing = "Destination server missing from app spec"
    35  )
    36  
    37  // FormatAppConditions returns string representation of give app condition list
    38  func FormatAppConditions(conditions []argoappv1.ApplicationCondition) string {
    39  	formattedConditions := make([]string, 0)
    40  	for _, condition := range conditions {
    41  		formattedConditions = append(formattedConditions, fmt.Sprintf("%s: %s", condition.Type, condition.Message))
    42  	}
    43  	return strings.Join(formattedConditions, ";")
    44  }
    45  
    46  // FilterByProjects returns applications which belongs to the specified project
    47  func FilterByProjects(apps []argoappv1.Application, projects []string) []argoappv1.Application {
    48  	if len(projects) == 0 {
    49  		return apps
    50  	}
    51  	projectsMap := make(map[string]bool)
    52  	for i := range projects {
    53  		projectsMap[projects[i]] = true
    54  	}
    55  	items := make([]argoappv1.Application, 0)
    56  	for i := 0; i < len(apps); i++ {
    57  		a := apps[i]
    58  		if _, ok := projectsMap[a.Spec.GetProject()]; ok {
    59  			items = append(items, a)
    60  		}
    61  	}
    62  	return items
    63  
    64  }
    65  
    66  // RefreshApp updates the refresh annotation of an application to coerce the controller to process it
    67  func RefreshApp(appIf v1alpha1.ApplicationInterface, name string, refreshType argoappv1.RefreshType) (*argoappv1.Application, error) {
    68  	metadata := map[string]interface{}{
    69  		"metadata": map[string]interface{}{
    70  			"annotations": map[string]string{
    71  				common.AnnotationKeyRefresh: string(refreshType),
    72  			},
    73  		},
    74  	}
    75  	var err error
    76  	patch, err := json.Marshal(metadata)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	for attempt := 0; attempt < 5; attempt++ {
    81  		app, err := appIf.Patch(context.Background(), name, types.MergePatchType, patch, metav1.PatchOptions{})
    82  		if err != nil {
    83  			if !apierr.IsConflict(err) {
    84  				return nil, err
    85  			}
    86  		} else {
    87  			log.Infof("Requested app '%s' refresh", name)
    88  			return app, nil
    89  		}
    90  		time.Sleep(100 * time.Millisecond)
    91  	}
    92  	return nil, err
    93  }
    94  
    95  // WaitForRefresh watches an application until its comparison timestamp is after the refresh timestamp
    96  // If refresh timestamp is not present, will use current timestamp at time of call
    97  func WaitForRefresh(ctx context.Context, appIf v1alpha1.ApplicationInterface, name string, timeout *time.Duration) (*argoappv1.Application, error) {
    98  	var cancel context.CancelFunc
    99  	if timeout != nil {
   100  		ctx, cancel = context.WithTimeout(ctx, *timeout)
   101  		defer cancel()
   102  	}
   103  	ch := kube.WatchWithRetry(ctx, func() (i watch.Interface, e error) {
   104  		fieldSelector := fields.ParseSelectorOrDie(fmt.Sprintf("metadata.name=%s", name))
   105  		listOpts := metav1.ListOptions{FieldSelector: fieldSelector.String()}
   106  		return appIf.Watch(ctx, listOpts)
   107  	})
   108  	for next := range ch {
   109  		if next.Error != nil {
   110  			return nil, next.Error
   111  		}
   112  		app, ok := next.Object.(*argoappv1.Application)
   113  		if !ok {
   114  			return nil, fmt.Errorf("Application event object failed conversion: %v", next)
   115  		}
   116  		annotations := app.GetAnnotations()
   117  		if annotations == nil {
   118  			annotations = make(map[string]string)
   119  		}
   120  		if _, ok := annotations[common.AnnotationKeyRefresh]; !ok {
   121  			return app, nil
   122  		}
   123  	}
   124  	return nil, fmt.Errorf("application refresh deadline exceeded")
   125  }
   126  
   127  func TestRepoWithKnownType(repo *argoappv1.Repository, isHelm bool, isHelmOci bool) error {
   128  	repo = repo.DeepCopy()
   129  	if isHelm {
   130  		repo.Type = "helm"
   131  	} else {
   132  		repo.Type = "git"
   133  	}
   134  	repo.EnableOCI = repo.EnableOCI || isHelmOci
   135  
   136  	return TestRepo(repo)
   137  }
   138  
   139  func TestRepo(repo *argoappv1.Repository) error {
   140  	checks := map[string]func() error{
   141  		"git": func() error {
   142  			return git.TestRepo(repo.Repo, repo.GetGitCreds(), repo.IsInsecure(), repo.IsLFSEnabled())
   143  		},
   144  		"helm": func() error {
   145  			if repo.EnableOCI {
   146  				_, err := helm.NewClient(repo.Repo, repo.GetHelmCreds(), repo.EnableOCI).TestHelmOCI()
   147  				return err
   148  			} else {
   149  				_, err := helm.NewClient(repo.Repo, repo.GetHelmCreds(), repo.EnableOCI).GetIndex()
   150  				return err
   151  			}
   152  		},
   153  	}
   154  	if check, ok := checks[repo.Type]; ok {
   155  		return check()
   156  	}
   157  	var err error
   158  	for _, check := range checks {
   159  		err = check()
   160  		if err == nil {
   161  			return nil
   162  		}
   163  	}
   164  	return err
   165  }
   166  
   167  // ValidateRepo validates the repository specified in application spec. Following is checked:
   168  // * the repository is accessible
   169  // * the path contains valid manifests
   170  // * there are parameters of only one app source type
   171  // * ksonnet: the specified environment exists
   172  func ValidateRepo(
   173  	ctx context.Context,
   174  	app *argoappv1.Application,
   175  	repoClientset apiclient.Clientset,
   176  	db db.ArgoDB,
   177  	kustomizeOptions *argoappv1.KustomizeOptions,
   178  	plugins []*argoappv1.ConfigManagementPlugin,
   179  	kubectl kube.Kubectl,
   180  ) ([]argoappv1.ApplicationCondition, error) {
   181  	spec := &app.Spec
   182  	conditions := make([]argoappv1.ApplicationCondition, 0)
   183  
   184  	// Test the repo
   185  	conn, repoClient, err := repoClientset.NewRepoServerClient()
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  	defer io.Close(conn)
   190  	repo, err := db.GetRepository(ctx, spec.Source.RepoURL)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	repoAccessible := false
   196  	err = TestRepoWithKnownType(repo, app.Spec.Source.IsHelm(), app.Spec.Source.IsHelmOci())
   197  	if err != nil {
   198  		conditions = append(conditions, argoappv1.ApplicationCondition{
   199  			Type:    argoappv1.ApplicationConditionInvalidSpecError,
   200  			Message: fmt.Sprintf("repository not accessible: %v", err),
   201  		})
   202  	} else {
   203  		repoAccessible = true
   204  	}
   205  
   206  	// Verify only one source type is defined
   207  	_, err = spec.Source.ExplicitType()
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  
   212  	// is the repo inaccessible - abort now
   213  	if !repoAccessible {
   214  		return conditions, nil
   215  	}
   216  
   217  	helmRepos, err := db.ListHelmRepositories(ctx)
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  
   222  	// get the app details, and populate the Ksonnet stuff from it
   223  	appDetails, err := repoClient.GetAppDetails(ctx, &apiclient.RepoServerAppDetailsQuery{
   224  		Repo:             repo,
   225  		Source:           &spec.Source,
   226  		Repos:            helmRepos,
   227  		KustomizeOptions: kustomizeOptions,
   228  	})
   229  	if err != nil {
   230  		conditions = append(conditions, argoappv1.ApplicationCondition{
   231  			Type:    argoappv1.ApplicationConditionInvalidSpecError,
   232  			Message: fmt.Sprintf("Unable to get app details: %v", err),
   233  		})
   234  		return conditions, nil
   235  	}
   236  
   237  	enrichSpec(spec, appDetails)
   238  
   239  	cluster, err := db.GetCluster(context.Background(), spec.Destination.Server)
   240  	if err != nil {
   241  		conditions = append(conditions, argoappv1.ApplicationCondition{
   242  			Type:    argoappv1.ApplicationConditionInvalidSpecError,
   243  			Message: fmt.Sprintf("Unable to get cluster: %v", err),
   244  		})
   245  		return conditions, nil
   246  	}
   247  	config := cluster.RESTConfig()
   248  	cluster.ServerVersion, err = kubectl.GetServerVersion(config)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  	apiGroups, err := kubectl.GetAPIGroups(config)
   253  	if err != nil {
   254  		return nil, err
   255  	}
   256  	conditions = append(conditions, verifyGenerateManifests(
   257  		ctx, repo, helmRepos, app, repoClient, kustomizeOptions, plugins, cluster.ServerVersion, APIGroupsToVersions(apiGroups))...)
   258  
   259  	return conditions, nil
   260  }
   261  
   262  func enrichSpec(spec *argoappv1.ApplicationSpec, appDetails *apiclient.RepoAppDetailsResponse) {
   263  	if spec.Source.Ksonnet != nil && appDetails.Ksonnet != nil {
   264  		env, ok := appDetails.Ksonnet.Environments[spec.Source.Ksonnet.Environment]
   265  		if ok {
   266  			// If server and namespace are not supplied, pull it from the app.yaml
   267  			if spec.Destination.Server == "" {
   268  				spec.Destination.Server = env.Destination.Server
   269  			}
   270  			if spec.Destination.Namespace == "" {
   271  				spec.Destination.Namespace = env.Destination.Namespace
   272  			}
   273  		}
   274  	}
   275  }
   276  
   277  // ValidateDestination checks:
   278  // if we used destination name we infer the server url
   279  // if we used both name and server then we return an invalid spec error
   280  func ValidateDestination(ctx context.Context, dest *argoappv1.ApplicationDestination, db db.ArgoDB) error {
   281  	if dest.Name != "" {
   282  		if dest.Server == "" {
   283  			server, err := getDestinationServer(ctx, db, dest.Name)
   284  			if err != nil {
   285  				return fmt.Errorf("unable to find destination server: %v", err)
   286  			}
   287  			if server == "" {
   288  				return fmt.Errorf("application references destination cluster %s which does not exist", dest.Name)
   289  			}
   290  			dest.SetInferredServer(server)
   291  		} else {
   292  			if !dest.IsServerInferred() {
   293  				return fmt.Errorf("application destination can't have both name and server defined: %s %s", dest.Name, dest.Server)
   294  			}
   295  		}
   296  	}
   297  	return nil
   298  }
   299  
   300  // ValidatePermissions ensures that the referenced cluster has been added to Argo CD and the app source repo and destination namespace/cluster are permitted in app project
   301  func ValidatePermissions(ctx context.Context, spec *argoappv1.ApplicationSpec, proj *argoappv1.AppProject, db db.ArgoDB) ([]argoappv1.ApplicationCondition, error) {
   302  	conditions := make([]argoappv1.ApplicationCondition, 0)
   303  	if spec.Source.RepoURL == "" || (spec.Source.Path == "" && spec.Source.Chart == "") {
   304  		conditions = append(conditions, argoappv1.ApplicationCondition{
   305  			Type:    argoappv1.ApplicationConditionInvalidSpecError,
   306  			Message: "spec.source.repoURL and spec.source.path either spec.source.chart are required",
   307  		})
   308  		return conditions, nil
   309  	}
   310  	if spec.Source.Chart != "" && spec.Source.TargetRevision == "" {
   311  		conditions = append(conditions, argoappv1.ApplicationCondition{
   312  			Type:    argoappv1.ApplicationConditionInvalidSpecError,
   313  			Message: "spec.source.targetRevision is required if the manifest source is a helm chart",
   314  		})
   315  		return conditions, nil
   316  	}
   317  
   318  	if !proj.IsSourcePermitted(spec.Source) {
   319  		conditions = append(conditions, argoappv1.ApplicationCondition{
   320  			Type:    argoappv1.ApplicationConditionInvalidSpecError,
   321  			Message: fmt.Sprintf("application repo %s is not permitted in project '%s'", spec.Source.RepoURL, spec.Project),
   322  		})
   323  	}
   324  
   325  	if spec.Destination.Server != "" {
   326  		if !proj.IsDestinationPermitted(spec.Destination) {
   327  			conditions = append(conditions, argoappv1.ApplicationCondition{
   328  				Type:    argoappv1.ApplicationConditionInvalidSpecError,
   329  				Message: fmt.Sprintf("application destination {%s %s} is not permitted in project '%s'", spec.Destination.Server, spec.Destination.Namespace, spec.Project),
   330  			})
   331  		}
   332  		// Ensure the k8s cluster the app is referencing, is configured in Argo CD
   333  		_, err := db.GetCluster(ctx, spec.Destination.Server)
   334  		if err != nil {
   335  			if errStatus, ok := status.FromError(err); ok && errStatus.Code() == codes.NotFound {
   336  				conditions = append(conditions, argoappv1.ApplicationCondition{
   337  					Type:    argoappv1.ApplicationConditionInvalidSpecError,
   338  					Message: fmt.Sprintf("cluster '%s' has not been configured", spec.Destination.Server),
   339  				})
   340  			} else {
   341  				return nil, err
   342  			}
   343  		}
   344  	} else if spec.Destination.Server == "" {
   345  		conditions = append(conditions, argoappv1.ApplicationCondition{Type: argoappv1.ApplicationConditionInvalidSpecError, Message: errDestinationMissing})
   346  	}
   347  	return conditions, nil
   348  }
   349  
   350  // APIGroupsToVersions converts list of API Groups into versions string list
   351  func APIGroupsToVersions(apiGroups []metav1.APIGroup) []string {
   352  	var apiVersions []string
   353  	for _, g := range apiGroups {
   354  		for _, v := range g.Versions {
   355  			apiVersions = append(apiVersions, v.GroupVersion)
   356  		}
   357  	}
   358  	return apiVersions
   359  }
   360  
   361  // GetAppProject returns a project from an application
   362  func GetAppProject(spec *argoappv1.ApplicationSpec, projLister applicationsv1.AppProjectLister, ns string, settingsManager *settings.SettingsManager) (*argoappv1.AppProject, error) {
   363  	projOrig, err := projLister.AppProjects(ns).Get(spec.GetProject())
   364  	if err != nil {
   365  		return nil, err
   366  	}
   367  	return GetAppVirtualProject(projOrig, projLister, settingsManager)
   368  }
   369  
   370  // verifyGenerateManifests verifies a repo path can generate manifests
   371  func verifyGenerateManifests(
   372  	ctx context.Context,
   373  	repoRes *argoappv1.Repository,
   374  	helmRepos argoappv1.Repositories,
   375  	app *argoappv1.Application,
   376  	repoClient apiclient.RepoServerServiceClient,
   377  	kustomizeOptions *argoappv1.KustomizeOptions,
   378  	plugins []*argoappv1.ConfigManagementPlugin,
   379  	kubeVersion string,
   380  	apiVersions []string,
   381  ) []argoappv1.ApplicationCondition {
   382  	spec := &app.Spec
   383  	var conditions []argoappv1.ApplicationCondition
   384  	if spec.Destination.Server == "" {
   385  		conditions = append(conditions, argoappv1.ApplicationCondition{
   386  			Type:    argoappv1.ApplicationConditionInvalidSpecError,
   387  			Message: errDestinationMissing,
   388  		})
   389  	}
   390  
   391  	req := apiclient.ManifestRequest{
   392  		Repo: &argoappv1.Repository{
   393  			Repo: spec.Source.RepoURL,
   394  			Type: repoRes.Type,
   395  			Name: repoRes.Name,
   396  		},
   397  		Repos:             helmRepos,
   398  		Revision:          spec.Source.TargetRevision,
   399  		AppLabelValue:     app.Name,
   400  		Namespace:         spec.Destination.Namespace,
   401  		ApplicationSource: &spec.Source,
   402  		Plugins:           plugins,
   403  		KustomizeOptions:  kustomizeOptions,
   404  		KubeVersion:       kubeVersion,
   405  		ApiVersions:       apiVersions,
   406  	}
   407  	req.Repo.CopyCredentialsFromRepo(repoRes)
   408  	req.Repo.CopySettingsFrom(repoRes)
   409  
   410  	// Only check whether we can access the application's path,
   411  	// and not whether it actually contains any manifests.
   412  	_, err := repoClient.GenerateManifest(ctx, &req)
   413  	if err != nil {
   414  		conditions = append(conditions, argoappv1.ApplicationCondition{
   415  			Type:    argoappv1.ApplicationConditionInvalidSpecError,
   416  			Message: fmt.Sprintf("Unable to generate manifests in %s: %v", spec.Source.Path, err),
   417  		})
   418  	}
   419  
   420  	return conditions
   421  }
   422  
   423  // SetAppOperation updates an application with the specified operation, retrying conflict errors
   424  func SetAppOperation(appIf v1alpha1.ApplicationInterface, appName string, op *argoappv1.Operation) (*argoappv1.Application, error) {
   425  	for {
   426  		a, err := appIf.Get(context.Background(), appName, metav1.GetOptions{})
   427  		if err != nil {
   428  			return nil, err
   429  		}
   430  		if a.Operation != nil {
   431  			return nil, status.Errorf(codes.FailedPrecondition, "another operation is already in progress")
   432  		}
   433  		a.Operation = op
   434  		a.Status.OperationState = nil
   435  		a, err = appIf.Update(context.Background(), a, metav1.UpdateOptions{})
   436  		if op.Sync == nil {
   437  			return nil, status.Errorf(codes.InvalidArgument, "Operation unspecified")
   438  		}
   439  		if err == nil {
   440  			return a, nil
   441  		}
   442  		if !apierr.IsConflict(err) {
   443  			return nil, err
   444  		}
   445  		log.Warnf("Failed to set operation for app '%s' due to update conflict. Retrying again...", appName)
   446  	}
   447  }
   448  
   449  // ContainsSyncResource determines if the given resource exists in the provided slice of sync operation resources.
   450  func ContainsSyncResource(name string, namespace string, gvk schema.GroupVersionKind, rr []argoappv1.SyncOperationResource) bool {
   451  	for _, r := range rr {
   452  		if r.HasIdentity(name, namespace, gvk) {
   453  			return true
   454  		}
   455  	}
   456  	return false
   457  }
   458  
   459  // NormalizeApplicationSpec will normalize an application spec to a preferred state. This is used
   460  // for migrating application objects which are using deprecated legacy fields into the new fields,
   461  // and defaulting fields in the spec (e.g. spec.project)
   462  func NormalizeApplicationSpec(spec *argoappv1.ApplicationSpec) *argoappv1.ApplicationSpec {
   463  	spec = spec.DeepCopy()
   464  	if spec.Project == "" {
   465  		spec.Project = common.DefaultAppProjectName
   466  	}
   467  
   468  	// 3. If any app sources are their zero values, then nil out the pointers to the source spec.
   469  	// This makes it easier for users to switch between app source types if they are not using
   470  	// any of the source-specific parameters.
   471  	if spec.Source.Kustomize != nil && spec.Source.Kustomize.IsZero() {
   472  		spec.Source.Kustomize = nil
   473  	}
   474  	if spec.Source.Helm != nil && spec.Source.Helm.IsZero() {
   475  		spec.Source.Helm = nil
   476  	}
   477  	if spec.Source.Ksonnet != nil && spec.Source.Ksonnet.IsZero() {
   478  		spec.Source.Ksonnet = nil
   479  	}
   480  	if spec.Source.Directory != nil && spec.Source.Directory.IsZero() {
   481  		if spec.Source.Directory.Exclude != "" {
   482  			spec.Source.Directory = &argoappv1.ApplicationSourceDirectory{Exclude: spec.Source.Directory.Exclude}
   483  		} else {
   484  			spec.Source.Directory = nil
   485  		}
   486  	}
   487  	return spec
   488  }
   489  
   490  func getDestinationServer(ctx context.Context, db db.ArgoDB, clusterName string) (string, error) {
   491  	clusterList, err := db.ListClusters(ctx)
   492  	if err != nil {
   493  		return "", err
   494  	}
   495  	var servers []string
   496  	for _, c := range clusterList.Items {
   497  		if c.Name == clusterName {
   498  			servers = append(servers, c.Server)
   499  		}
   500  	}
   501  	if len(servers) > 1 {
   502  		return "", fmt.Errorf("there are %d clusters with the same name: %v", len(servers), servers)
   503  	} else if len(servers) == 0 {
   504  		return "", fmt.Errorf("there are no clusters with this name: %s", clusterName)
   505  	}
   506  	return servers[0], nil
   507  }
   508  
   509  func GetGlobalProjects(proj *argoappv1.AppProject, projLister applicationsv1.AppProjectLister, settingsManager *settings.SettingsManager) []*argoappv1.AppProject {
   510  	gps, err := settingsManager.GetGlobalProjectsSettings()
   511  	globalProjects := make([]*argoappv1.AppProject, 0)
   512  
   513  	if err != nil {
   514  		log.Warnf("Failed to get global project settings: %v", err)
   515  		return globalProjects
   516  	}
   517  
   518  	for _, gp := range gps {
   519  		//The project itself is not its own the global project
   520  		if proj.Name == gp.ProjectName {
   521  			continue
   522  		}
   523  
   524  		selector, err := metav1.LabelSelectorAsSelector(&gp.LabelSelector)
   525  		if err != nil {
   526  			break
   527  		}
   528  		//Get projects which match the label selector, then see if proj is a match
   529  		projList, err := projLister.AppProjects(proj.Namespace).List(selector)
   530  		if err != nil {
   531  			break
   532  		}
   533  		var matchMe bool
   534  		for _, item := range projList {
   535  			if item.Name == proj.Name {
   536  				matchMe = true
   537  				break
   538  			}
   539  		}
   540  		if !matchMe {
   541  			break
   542  		}
   543  		//If proj is a match for this global project setting, then it is its global project
   544  		globalProj, err := projLister.AppProjects(proj.Namespace).Get(gp.ProjectName)
   545  		if err != nil {
   546  			break
   547  		}
   548  		globalProjects = append(globalProjects, globalProj)
   549  
   550  	}
   551  	return globalProjects
   552  }
   553  
   554  func GetAppVirtualProject(proj *argoappv1.AppProject, projLister applicationsv1.AppProjectLister, settingsManager *settings.SettingsManager) (*argoappv1.AppProject, error) {
   555  	virtualProj := proj.DeepCopy()
   556  	globalProjects := GetGlobalProjects(proj, projLister, settingsManager)
   557  
   558  	for _, gp := range globalProjects {
   559  		virtualProj = mergeVirtualProject(virtualProj, gp)
   560  	}
   561  	return virtualProj, nil
   562  }
   563  
   564  func mergeVirtualProject(proj *argoappv1.AppProject, globalProj *argoappv1.AppProject) *argoappv1.AppProject {
   565  	if globalProj == nil {
   566  		return proj
   567  	}
   568  	proj.Spec.ClusterResourceWhitelist = append(proj.Spec.ClusterResourceWhitelist, globalProj.Spec.ClusterResourceWhitelist...)
   569  	proj.Spec.ClusterResourceBlacklist = append(proj.Spec.ClusterResourceBlacklist, globalProj.Spec.ClusterResourceBlacklist...)
   570  
   571  	proj.Spec.NamespaceResourceWhitelist = append(proj.Spec.NamespaceResourceWhitelist, globalProj.Spec.NamespaceResourceWhitelist...)
   572  	proj.Spec.NamespaceResourceBlacklist = append(proj.Spec.NamespaceResourceBlacklist, globalProj.Spec.NamespaceResourceBlacklist...)
   573  
   574  	proj.Spec.SyncWindows = append(proj.Spec.SyncWindows, globalProj.Spec.SyncWindows...)
   575  
   576  	return proj
   577  }