github.com/argoproj/argo-cd/v2@v2.10.9/server/repository/repository.go (about)

     1  package repository
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"reflect"
     7  
     8  	"github.com/argoproj/gitops-engine/pkg/utils/kube"
     9  	"github.com/argoproj/gitops-engine/pkg/utils/text"
    10  	log "github.com/sirupsen/logrus"
    11  	"google.golang.org/grpc/codes"
    12  	"google.golang.org/grpc/status"
    13  	apierr "k8s.io/apimachinery/pkg/api/errors"
    14  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    15  	"k8s.io/client-go/tools/cache"
    16  
    17  	"github.com/argoproj/argo-cd/v2/common"
    18  	repositorypkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/repository"
    19  	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    20  	appsv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    21  	applisters "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1"
    22  	"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
    23  	servercache "github.com/argoproj/argo-cd/v2/server/cache"
    24  	"github.com/argoproj/argo-cd/v2/server/rbacpolicy"
    25  	"github.com/argoproj/argo-cd/v2/util/argo"
    26  	"github.com/argoproj/argo-cd/v2/util/db"
    27  	"github.com/argoproj/argo-cd/v2/util/errors"
    28  	"github.com/argoproj/argo-cd/v2/util/io"
    29  	"github.com/argoproj/argo-cd/v2/util/rbac"
    30  	"github.com/argoproj/argo-cd/v2/util/settings"
    31  )
    32  
    33  // Server provides a Repository service
    34  type Server struct {
    35  	db            db.ArgoDB
    36  	repoClientset apiclient.Clientset
    37  	enf           *rbac.Enforcer
    38  	cache         *servercache.Cache
    39  	appLister     applisters.ApplicationLister
    40  	projLister    cache.SharedIndexInformer
    41  	settings      *settings.SettingsManager
    42  	namespace     string
    43  }
    44  
    45  // NewServer returns a new instance of the Repository service
    46  func NewServer(
    47  	repoClientset apiclient.Clientset,
    48  	db db.ArgoDB,
    49  	enf *rbac.Enforcer,
    50  	cache *servercache.Cache,
    51  	appLister applisters.ApplicationLister,
    52  	projLister cache.SharedIndexInformer,
    53  	namespace string,
    54  	settings *settings.SettingsManager,
    55  ) *Server {
    56  	return &Server{
    57  		db:            db,
    58  		repoClientset: repoClientset,
    59  		enf:           enf,
    60  		cache:         cache,
    61  		appLister:     appLister,
    62  		projLister:    projLister,
    63  		namespace:     namespace,
    64  		settings:      settings,
    65  	}
    66  }
    67  
    68  var (
    69  	errPermissionDenied = status.Error(codes.PermissionDenied, "permission denied")
    70  )
    71  
    72  func (s *Server) getRepo(ctx context.Context, url string) (*appsv1.Repository, error) {
    73  	repo, err := s.db.GetRepository(ctx, url)
    74  	if err != nil {
    75  		return nil, errPermissionDenied
    76  	}
    77  	return repo, nil
    78  }
    79  
    80  func createRBACObject(project string, repo string) string {
    81  	if project != "" {
    82  		return project + "/" + repo
    83  	}
    84  	return repo
    85  }
    86  
    87  // Get the connection state for a given repository URL by connecting to the
    88  // repo and evaluate the results. Unless forceRefresh is set to true, the
    89  // result may be retrieved out of the cache.
    90  func (s *Server) getConnectionState(ctx context.Context, url string, forceRefresh bool) appsv1.ConnectionState {
    91  	if !forceRefresh {
    92  		if connectionState, err := s.cache.GetRepoConnectionState(url); err == nil {
    93  			return connectionState
    94  		}
    95  	}
    96  	now := metav1.Now()
    97  	connectionState := appsv1.ConnectionState{
    98  		Status:     appsv1.ConnectionStatusSuccessful,
    99  		ModifiedAt: &now,
   100  	}
   101  	var err error
   102  	repo, err := s.db.GetRepository(ctx, url)
   103  	if err == nil {
   104  		err = s.testRepo(ctx, repo)
   105  	}
   106  	if err != nil {
   107  		connectionState.Status = appsv1.ConnectionStatusFailed
   108  		if errors.IsCredentialsConfigurationError(err) {
   109  			connectionState.Message = "Configuration error - please check the server logs"
   110  			log.Warnf("could not retrieve repo: %s", err.Error())
   111  		} else {
   112  			connectionState.Message = fmt.Sprintf("Unable to connect to repository: %v", err)
   113  		}
   114  	}
   115  	err = s.cache.SetRepoConnectionState(url, &connectionState)
   116  	if err != nil {
   117  		log.Warnf("getConnectionState cache set error %s: %v", url, err)
   118  	}
   119  	return connectionState
   120  }
   121  
   122  // List returns list of repositories
   123  // Deprecated: Use ListRepositories instead
   124  func (s *Server) List(ctx context.Context, q *repositorypkg.RepoQuery) (*appsv1.RepositoryList, error) {
   125  	return s.ListRepositories(ctx, q)
   126  }
   127  
   128  // Get return the requested configured repository by URL and the state of its connections.
   129  func (s *Server) Get(ctx context.Context, q *repositorypkg.RepoQuery) (*appsv1.Repository, error) {
   130  	repo, err := s.getRepo(ctx, q.Repo)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceRepositories, rbacpolicy.ActionGet, createRBACObject(repo.Project, repo.Repo)); err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	// getRepo does not return an error for unconfigured repositories, so we are checking here
   140  	exists, err := s.db.RepositoryExists(ctx, q.Repo)
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  	if !exists {
   145  		return nil, status.Errorf(codes.NotFound, "repo '%s' not found", q.Repo)
   146  	}
   147  
   148  	// For backwards compatibility, if we have no repo type set assume a default
   149  	rType := repo.Type
   150  	if rType == "" {
   151  		rType = common.DefaultRepoType
   152  	}
   153  	// remove secrets
   154  	item := appsv1.Repository{
   155  		Repo:                       repo.Repo,
   156  		Type:                       rType,
   157  		Name:                       repo.Name,
   158  		Username:                   repo.Username,
   159  		Insecure:                   repo.IsInsecure(),
   160  		EnableLFS:                  repo.EnableLFS,
   161  		GithubAppId:                repo.GithubAppId,
   162  		GithubAppInstallationId:    repo.GithubAppInstallationId,
   163  		GitHubAppEnterpriseBaseURL: repo.GitHubAppEnterpriseBaseURL,
   164  		Proxy:                      repo.Proxy,
   165  		Project:                    repo.Project,
   166  		InheritedCreds:             repo.InheritedCreds,
   167  	}
   168  
   169  	item.ConnectionState = s.getConnectionState(ctx, item.Repo, q.ForceRefresh)
   170  
   171  	return &item, nil
   172  }
   173  
   174  // ListRepositories returns a list of all configured repositories and the state of their connections
   175  func (s *Server) ListRepositories(ctx context.Context, q *repositorypkg.RepoQuery) (*appsv1.RepositoryList, error) {
   176  	repos, err := s.db.ListRepositories(ctx)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  	items := appsv1.Repositories{}
   181  	for _, repo := range repos {
   182  		if s.enf.Enforce(ctx.Value("claims"), rbacpolicy.ResourceRepositories, rbacpolicy.ActionGet, createRBACObject(repo.Project, repo.Repo)) {
   183  			// For backwards compatibility, if we have no repo type set assume a default
   184  			rType := repo.Type
   185  			if rType == "" {
   186  				rType = common.DefaultRepoType
   187  			}
   188  			// remove secrets
   189  			items = append(items, &appsv1.Repository{
   190  				Repo:               repo.Repo,
   191  				Type:               rType,
   192  				Name:               repo.Name,
   193  				Username:           repo.Username,
   194  				Insecure:           repo.IsInsecure(),
   195  				EnableLFS:          repo.EnableLFS,
   196  				EnableOCI:          repo.EnableOCI,
   197  				Proxy:              repo.Proxy,
   198  				Project:            repo.Project,
   199  				ForceHttpBasicAuth: repo.ForceHttpBasicAuth,
   200  				InheritedCreds:     repo.InheritedCreds,
   201  			})
   202  		}
   203  	}
   204  	err = kube.RunAllAsync(len(items), func(i int) error {
   205  		items[i].ConnectionState = s.getConnectionState(ctx, items[i].Repo, q.ForceRefresh)
   206  		return nil
   207  	})
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  	return &appsv1.RepositoryList{Items: items}, nil
   212  }
   213  
   214  func (s *Server) ListRefs(ctx context.Context, q *repositorypkg.RepoQuery) (*apiclient.Refs, error) {
   215  	repo, err := s.getRepo(ctx, q.Repo)
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  
   220  	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceRepositories, rbacpolicy.ActionGet, createRBACObject(repo.Project, repo.Repo)); err != nil {
   221  		return nil, err
   222  	}
   223  
   224  	conn, repoClient, err := s.repoClientset.NewRepoServerClient()
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  	defer io.Close(conn)
   229  
   230  	return repoClient.ListRefs(ctx, &apiclient.ListRefsRequest{
   231  		Repo: repo,
   232  	})
   233  }
   234  
   235  // ListApps performs discovery of a git repository for potential sources of applications. Used
   236  // as a convenience to the UI for auto-complete.
   237  func (s *Server) ListApps(ctx context.Context, q *repositorypkg.RepoAppsQuery) (*repositorypkg.RepoAppsResponse, error) {
   238  	repo, err := s.getRepo(ctx, q.Repo)
   239  	if err != nil {
   240  		return nil, err
   241  	}
   242  
   243  	claims := ctx.Value("claims")
   244  	if err := s.enf.EnforceErr(claims, rbacpolicy.ResourceRepositories, rbacpolicy.ActionGet, createRBACObject(repo.Project, repo.Repo)); err != nil {
   245  		return nil, err
   246  	}
   247  
   248  	// This endpoint causes us to clone git repos & invoke config management tooling for the purposes
   249  	// of app discovery. Only allow this to happen if user has privileges to create or update the
   250  	// application which it wants to retrieve these details for.
   251  	appRBACresource := fmt.Sprintf("%s/%s", q.AppProject, q.AppName)
   252  	if !s.enf.Enforce(claims, rbacpolicy.ResourceApplications, rbacpolicy.ActionCreate, appRBACresource) &&
   253  		!s.enf.Enforce(claims, rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, appRBACresource) {
   254  		return nil, errPermissionDenied
   255  	}
   256  	// Also ensure the repo is actually allowed in the project in question
   257  	if err := s.isRepoPermittedInProject(ctx, q.Repo, q.AppProject); err != nil {
   258  		return nil, err
   259  	}
   260  
   261  	// Test the repo
   262  	conn, repoClient, err := s.repoClientset.NewRepoServerClient()
   263  	if err != nil {
   264  		return nil, err
   265  	}
   266  	defer io.Close(conn)
   267  
   268  	apps, err := repoClient.ListApps(ctx, &apiclient.ListAppsRequest{
   269  		Repo:     repo,
   270  		Revision: q.Revision,
   271  	})
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  	items := make([]*repositorypkg.AppInfo, 0)
   276  	for app, appType := range apps.Apps {
   277  		items = append(items, &repositorypkg.AppInfo{Path: app, Type: appType})
   278  	}
   279  	return &repositorypkg.RepoAppsResponse{Items: items}, nil
   280  }
   281  
   282  // GetAppDetails shows parameter values to various config tools (e.g. helm/kustomize values)
   283  // This is used by UI for parameter form fields during app create & edit pages.
   284  // It is also used when showing history of parameters used in previous syncs in the app history.
   285  func (s *Server) GetAppDetails(ctx context.Context, q *repositorypkg.RepoAppDetailsQuery) (*apiclient.RepoAppDetailsResponse, error) {
   286  	if q.Source == nil {
   287  		return nil, status.Errorf(codes.InvalidArgument, "missing payload in request")
   288  	}
   289  	repo, err := s.getRepo(ctx, q.Source.RepoURL)
   290  	if err != nil {
   291  		return nil, err
   292  	}
   293  	claims := ctx.Value("claims")
   294  	if err := s.enf.EnforceErr(claims, rbacpolicy.ResourceRepositories, rbacpolicy.ActionGet, createRBACObject(repo.Project, repo.Repo)); err != nil {
   295  		return nil, err
   296  	}
   297  	appName, appNs := argo.ParseFromQualifiedName(q.AppName, s.settings.GetNamespace())
   298  	app, err := s.appLister.Applications(appNs).Get(appName)
   299  	appRBACObj := createRBACObject(q.AppProject, q.AppName)
   300  	// ensure caller has read privileges to app
   301  	if err := s.enf.EnforceErr(claims, rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, appRBACObj); err != nil {
   302  		return nil, err
   303  	}
   304  	if apierr.IsNotFound(err) {
   305  		// app doesn't exist since it still is being formulated. verify they can create the app
   306  		// before we reveal repo details
   307  		if err := s.enf.EnforceErr(claims, rbacpolicy.ResourceApplications, rbacpolicy.ActionCreate, appRBACObj); err != nil {
   308  			return nil, err
   309  		}
   310  	} else {
   311  		// if we get here we are returning repo details of an existing app
   312  		if q.AppProject != app.Spec.Project {
   313  			return nil, errPermissionDenied
   314  		}
   315  		// verify caller is not making a request with arbitrary source values which were not in our history
   316  		if !isSourceInHistory(app, *q.Source) {
   317  			return nil, errPermissionDenied
   318  		}
   319  	}
   320  	// Ensure the repo is actually allowed in the project in question
   321  	if err := s.isRepoPermittedInProject(ctx, q.Source.RepoURL, q.AppProject); err != nil {
   322  		return nil, err
   323  	}
   324  
   325  	conn, repoClient, err := s.repoClientset.NewRepoServerClient()
   326  	if err != nil {
   327  		return nil, err
   328  	}
   329  	defer io.Close(conn)
   330  	helmRepos, err := s.db.ListHelmRepositories(ctx)
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  	kustomizeSettings, err := s.settings.GetKustomizeSettings()
   335  	if err != nil {
   336  		return nil, err
   337  	}
   338  	kustomizeOptions, err := kustomizeSettings.GetOptions(*q.Source)
   339  	if err != nil {
   340  		return nil, err
   341  	}
   342  	helmOptions, err := s.settings.GetHelmSettings()
   343  	if err != nil {
   344  		return nil, err
   345  	}
   346  	return repoClient.GetAppDetails(ctx, &apiclient.RepoServerAppDetailsQuery{
   347  		Repo:             repo,
   348  		Source:           q.Source,
   349  		Repos:            helmRepos,
   350  		KustomizeOptions: kustomizeOptions,
   351  		HelmOptions:      helmOptions,
   352  		AppName:          q.AppName,
   353  	})
   354  }
   355  
   356  // GetHelmCharts returns list of helm charts in the specified repository
   357  func (s *Server) GetHelmCharts(ctx context.Context, q *repositorypkg.RepoQuery) (*apiclient.HelmChartsResponse, error) {
   358  	repo, err := s.getRepo(ctx, q.Repo)
   359  	if err != nil {
   360  		return nil, err
   361  	}
   362  	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceRepositories, rbacpolicy.ActionGet, createRBACObject(repo.Project, repo.Repo)); err != nil {
   363  		return nil, err
   364  	}
   365  	conn, repoClient, err := s.repoClientset.NewRepoServerClient()
   366  	if err != nil {
   367  		return nil, err
   368  	}
   369  	defer io.Close(conn)
   370  	return repoClient.GetHelmCharts(ctx, &apiclient.HelmChartsRequest{Repo: repo})
   371  }
   372  
   373  // Create creates a repository or repository credential set
   374  // Deprecated: Use CreateRepository() instead
   375  func (s *Server) Create(ctx context.Context, q *repositorypkg.RepoCreateRequest) (*appsv1.Repository, error) {
   376  	return s.CreateRepository(ctx, q)
   377  }
   378  
   379  // CreateRepository creates a repository configuration
   380  func (s *Server) CreateRepository(ctx context.Context, q *repositorypkg.RepoCreateRequest) (*appsv1.Repository, error) {
   381  	if q.Repo == nil {
   382  		return nil, status.Errorf(codes.InvalidArgument, "missing payload in request")
   383  	}
   384  
   385  	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceRepositories, rbacpolicy.ActionCreate, createRBACObject(q.Repo.Project, q.Repo.Repo)); err != nil {
   386  		return nil, err
   387  	}
   388  
   389  	var repo *appsv1.Repository
   390  	var err error
   391  
   392  	// check we can connect to the repo, copying any existing creds (not supported for project scoped repositories)
   393  	if q.Repo.Project == "" {
   394  		repo := q.Repo.DeepCopy()
   395  		if !repo.HasCredentials() {
   396  			creds, err := s.db.GetRepositoryCredentials(ctx, repo.Repo)
   397  			if err != nil {
   398  				return nil, err
   399  			}
   400  			repo.CopyCredentialsFrom(creds)
   401  		}
   402  
   403  		err = s.testRepo(ctx, repo)
   404  		if err != nil {
   405  			return nil, err
   406  		}
   407  	}
   408  
   409  	r := q.Repo
   410  	r.ConnectionState = appsv1.ConnectionState{Status: appsv1.ConnectionStatusSuccessful}
   411  	repo, err = s.db.CreateRepository(ctx, r)
   412  	if status.Convert(err).Code() == codes.AlreadyExists {
   413  		// act idempotent if existing spec matches new spec
   414  		existing, getErr := s.db.GetRepository(ctx, r.Repo)
   415  		if getErr != nil {
   416  			return nil, status.Errorf(codes.Internal, "unable to check existing repository details: %v", getErr)
   417  		}
   418  
   419  		existing.Type = text.FirstNonEmpty(existing.Type, "git")
   420  		// repository ConnectionState may differ, so make consistent before testing
   421  		existing.ConnectionState = r.ConnectionState
   422  		if reflect.DeepEqual(existing, r) {
   423  			repo, err = existing, nil
   424  		} else if q.Upsert {
   425  			r.Project = q.Repo.Project
   426  			return s.UpdateRepository(ctx, &repositorypkg.RepoUpdateRequest{Repo: r})
   427  		} else {
   428  			return nil, status.Errorf(codes.InvalidArgument, argo.GenerateSpecIsDifferentErrorMessage("repository", existing, r))
   429  		}
   430  	}
   431  	if err != nil {
   432  		return nil, err
   433  	}
   434  	return &appsv1.Repository{Repo: repo.Repo, Type: repo.Type, Name: repo.Name}, nil
   435  }
   436  
   437  // Update updates a repository or credential set
   438  // Deprecated: Use UpdateRepository() instead
   439  func (s *Server) Update(ctx context.Context, q *repositorypkg.RepoUpdateRequest) (*appsv1.Repository, error) {
   440  	return s.UpdateRepository(ctx, q)
   441  }
   442  
   443  // UpdateRepository updates a repository configuration
   444  func (s *Server) UpdateRepository(ctx context.Context, q *repositorypkg.RepoUpdateRequest) (*appsv1.Repository, error) {
   445  	if q.Repo == nil {
   446  		return nil, status.Errorf(codes.InvalidArgument, "missing payload in request")
   447  	}
   448  
   449  	repo, err := s.getRepo(ctx, q.Repo.Repo)
   450  	if err != nil {
   451  		return nil, err
   452  	}
   453  
   454  	// verify that user can do update inside project where repository is located
   455  	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceRepositories, rbacpolicy.ActionUpdate, createRBACObject(repo.Project, repo.Repo)); err != nil {
   456  		return nil, err
   457  	}
   458  	// verify that user can do update inside project where repository will be located
   459  	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceRepositories, rbacpolicy.ActionUpdate, createRBACObject(q.Repo.Project, q.Repo.Repo)); err != nil {
   460  		return nil, err
   461  	}
   462  	_, err = s.db.UpdateRepository(ctx, q.Repo)
   463  	return &appsv1.Repository{Repo: q.Repo.Repo, Type: q.Repo.Type, Name: q.Repo.Name}, err
   464  }
   465  
   466  // Delete removes a repository from the configuration
   467  // Deprecated: Use DeleteRepository() instead
   468  func (s *Server) Delete(ctx context.Context, q *repositorypkg.RepoQuery) (*repositorypkg.RepoResponse, error) {
   469  	return s.DeleteRepository(ctx, q)
   470  }
   471  
   472  // DeleteRepository removes a repository from the configuration
   473  func (s *Server) DeleteRepository(ctx context.Context, q *repositorypkg.RepoQuery) (*repositorypkg.RepoResponse, error) {
   474  	repo, err := s.getRepo(ctx, q.Repo)
   475  	if err != nil {
   476  		return nil, err
   477  	}
   478  
   479  	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceRepositories, rbacpolicy.ActionDelete, createRBACObject(repo.Project, repo.Repo)); err != nil {
   480  		return nil, err
   481  	}
   482  
   483  	// invalidate cache
   484  	if err := s.cache.SetRepoConnectionState(q.Repo, nil); err == nil {
   485  		log.Errorf("error invalidating cache: %v", err)
   486  	}
   487  
   488  	err = s.db.DeleteRepository(ctx, q.Repo)
   489  	return &repositorypkg.RepoResponse{}, err
   490  }
   491  
   492  // ValidateAccess checks whether access to a repository is possible with the
   493  // given URL and credentials.
   494  func (s *Server) ValidateAccess(ctx context.Context, q *repositorypkg.RepoAccessQuery) (*repositorypkg.RepoResponse, error) {
   495  	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceRepositories, rbacpolicy.ActionCreate, createRBACObject(q.Project, q.Repo)); err != nil {
   496  		return nil, err
   497  	}
   498  
   499  	repo := &appsv1.Repository{
   500  		Repo:                       q.Repo,
   501  		Type:                       q.Type,
   502  		Name:                       q.Name,
   503  		Username:                   q.Username,
   504  		Password:                   q.Password,
   505  		SSHPrivateKey:              q.SshPrivateKey,
   506  		Insecure:                   q.Insecure,
   507  		TLSClientCertData:          q.TlsClientCertData,
   508  		TLSClientCertKey:           q.TlsClientCertKey,
   509  		EnableOCI:                  q.EnableOci,
   510  		GithubAppPrivateKey:        q.GithubAppPrivateKey,
   511  		GithubAppId:                q.GithubAppID,
   512  		GithubAppInstallationId:    q.GithubAppInstallationID,
   513  		GitHubAppEnterpriseBaseURL: q.GithubAppEnterpriseBaseUrl,
   514  		Proxy:                      q.Proxy,
   515  		GCPServiceAccountKey:       q.GcpServiceAccountKey,
   516  	}
   517  
   518  	// If repo does not have credentials, check if there are credentials stored
   519  	// for it and if yes, copy them
   520  	if !repo.HasCredentials() {
   521  		repoCreds, err := s.db.GetRepositoryCredentials(ctx, q.Repo)
   522  		if err != nil {
   523  			return nil, err
   524  		}
   525  		if repoCreds != nil {
   526  			repo.CopyCredentialsFrom(repoCreds)
   527  		}
   528  	}
   529  	err := s.testRepo(ctx, repo)
   530  	if err != nil {
   531  		return nil, err
   532  	}
   533  	return &repositorypkg.RepoResponse{}, nil
   534  }
   535  
   536  func (s *Server) testRepo(ctx context.Context, repo *appsv1.Repository) error {
   537  	conn, repoClient, err := s.repoClientset.NewRepoServerClient()
   538  	if err != nil {
   539  		return err
   540  	}
   541  	defer io.Close(conn)
   542  
   543  	_, err = repoClient.TestRepository(ctx, &apiclient.TestRepositoryRequest{
   544  		Repo: repo,
   545  	})
   546  	return err
   547  }
   548  
   549  func (s *Server) isRepoPermittedInProject(ctx context.Context, repo string, projName string) error {
   550  	proj, err := argo.GetAppProjectByName(projName, applisters.NewAppProjectLister(s.projLister.GetIndexer()), s.namespace, s.settings, s.db, ctx)
   551  	if err != nil {
   552  		return err
   553  	}
   554  	if !proj.IsSourcePermitted(appsv1.ApplicationSource{RepoURL: repo}) {
   555  		return status.Errorf(codes.PermissionDenied, "repository '%s' not permitted in project '%s'", repo, projName)
   556  	}
   557  	return nil
   558  }
   559  
   560  // isSourceInHistory checks if the supplied application source is either our current application
   561  // source, or was something which we synced to previously.
   562  func isSourceInHistory(app *v1alpha1.Application, source v1alpha1.ApplicationSource) bool {
   563  	appSource := app.Spec.GetSource()
   564  	if source.Equals(&appSource) {
   565  		return true
   566  	}
   567  	// Iterate history. When comparing items in our history, use the actual synced revision to
   568  	// compare with the supplied source.targetRevision in the request. This is because
   569  	// history[].source.targetRevision is ambiguous (e.g. HEAD), whereas
   570  	// history[].revision will contain the explicit SHA
   571  	for _, h := range app.Status.History {
   572  		h.Source.TargetRevision = h.Revision
   573  		if source.Equals(&h.Source) {
   574  			return true
   575  		}
   576  	}
   577  	return false
   578  }