diff --git a/applicationset/generators/git.go b/applicationset/generators/git.go index 5132ddfc115d08..afc62c117f0ee4 100644 --- a/applicationset/generators/git.go +++ b/applicationset/generators/git.go @@ -84,9 +84,9 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic var err error var res []map[string]interface{} if len(appSetGenerator.Git.Directories) != 0 { - res, err = g.generateParamsForGitDirectories(appSetGenerator, noRevisionCache, verifyCommit, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions) + res, err = g.generateParamsForGitDirectories(appSetGenerator, noRevisionCache, verifyCommit, appSet.Spec.GoTemplate, appSet.Spec.Template.Spec.Project, appSet.Spec.GoTemplateOptions) } else if len(appSetGenerator.Git.Files) != 0 { - res, err = g.generateParamsForGitFiles(appSetGenerator, noRevisionCache, verifyCommit, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions) + res, err = g.generateParamsForGitFiles(appSetGenerator, noRevisionCache, verifyCommit, appSet.Spec.GoTemplate, appSet.Spec.Template.Spec.Project, appSet.Spec.GoTemplateOptions) } else { return nil, EmptyAppSetGeneratorError } @@ -97,9 +97,9 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic return res, nil } -func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit bool, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) { +func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit, useGoTemplate bool, project string, goTemplateOptions []string) ([]map[string]interface{}, error) { // Directories, not files - allPaths, err := g.repos.GetDirectories(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, "", noRevisionCache, verifyCommit) + allPaths, err := g.repos.GetDirectories(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, project, noRevisionCache, verifyCommit) if err != nil { return nil, fmt.Errorf("error getting directories from repo: %w", err) } @@ -122,11 +122,11 @@ func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoproj return res, nil } -func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit bool, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) { +func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit, useGoTemplate bool, project string, goTemplateOptions []string) ([]map[string]interface{}, error) { // Get all files that match the requested path string, removing duplicates allFiles := make(map[string][]byte) for _, requestedPath := range appSetGenerator.Git.Files { - files, err := g.repos.GetFiles(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, "", requestedPath.Path, noRevisionCache, verifyCommit) + files, err := g.repos.GetFiles(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, project, requestedPath.Path, noRevisionCache, verifyCommit) if err != nil { return nil, err } diff --git a/applicationset/services/repo_service.go b/applicationset/services/repo_service.go index ebace7ec3bb3de..223c699688ef2d 100644 --- a/applicationset/services/repo_service.go +++ b/applicationset/services/repo_service.go @@ -2,21 +2,17 @@ package services import ( "context" - "errors" "fmt" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" + "strings" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/v2/reposerver/apiclient" "github.com/argoproj/argo-cd/v2/util/git" "github.com/argoproj/argo-cd/v2/util/io" - "github.com/argoproj/argo-cd/v2/util/repository" ) type argoCDService struct { - listRepositories func(ctx context.Context) ([]*v1alpha1.Repository, error) + getRepository func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) storecreds git.CredsStore submoduleEnabled bool newFileGlobbingEnabled bool @@ -32,9 +28,9 @@ type Repos interface { GetDirectories(ctx context.Context, repoURL, revision, project string, noRevisionCache, verifyCommit bool) ([]string, error) } -func NewArgoCDService(listRepositories func(ctx context.Context) ([]*v1alpha1.Repository, error), submoduleEnabled bool, repoClientset apiclient.Clientset, newFileGlobbingEnabled bool) (Repos, error) { +func NewArgoCDService(getRepository func(ctx context.Context, url, project string) (*v1alpha1.Repository, error), submoduleEnabled bool, repoClientset apiclient.Clientset, newFileGlobbingEnabled bool) (Repos, error) { return &argoCDService{ - listRepositories: listRepositories, + getRepository: getRepository, submoduleEnabled: submoduleEnabled, newFileGlobbingEnabled: newFileGlobbingEnabled, getGitFiles: func(ctx context.Context, fileRequest *apiclient.GitFilesRequest) (*apiclient.GitFilesResponse, error) { @@ -57,14 +53,9 @@ func NewArgoCDService(listRepositories func(ctx context.Context) ([]*v1alpha1.Re } func (a *argoCDService) GetFiles(ctx context.Context, repoURL, revision, project, pattern string, noRevisionCache, verifyCommit bool) (map[string][]byte, error) { - repos, err := a.listRepositories(ctx) - if err != nil { - return nil, fmt.Errorf("error in ListRepositories: %w", err) - } - - repo, err := getRepo(repos, repoURL, project) + repo, err := a.getRepository(ctx, repoURL, resolveProjectName(project)) if err != nil { - return nil, fmt.Errorf("error retrieving Git files: %w", err) + return nil, fmt.Errorf("error in GetRepository: %w", err) } fileRequest := &apiclient.GitFilesRequest{ @@ -84,14 +75,9 @@ func (a *argoCDService) GetFiles(ctx context.Context, repoURL, revision, project } func (a *argoCDService) GetDirectories(ctx context.Context, repoURL, revision, project string, noRevisionCache, verifyCommit bool) ([]string, error) { - repos, err := a.listRepositories(ctx) - if err != nil { - return nil, fmt.Errorf("error in ListRepositories: %w", err) - } - - repo, err := getRepo(repos, repoURL, project) + repo, err := a.getRepository(ctx, repoURL, resolveProjectName(project)) if err != nil { - return nil, fmt.Errorf("error retrieving Git Directories: %w", err) + return nil, fmt.Errorf("error in GetRepository: %w", err) } dirRequest := &apiclient.GitDirectoriesRequest{ @@ -109,34 +95,10 @@ func (a *argoCDService) GetDirectories(ctx context.Context, repoURL, revision, p return dirResponse.GetPaths(), nil } -func getRepo(repos []*v1alpha1.Repository, repoURL string, project string) (*v1alpha1.Repository, error) { - repo, err := repository.FilterRepositoryByProjectAndURL(repos, repoURL, project) - if err != nil { - if errors.Is(err, status.Error(codes.PermissionDenied, "permission denied")) { - // No repo found with a matching URL - attempt fallback without any actual credentials - return &v1alpha1.Repository{Repo: repoURL}, nil - } else if project == "" { - for _, r := range repos { - if git.SameURL(r.Repo, repoURL) { - // Prioritize using a repository with an unset project. - if r.Project == "" { - return r, nil - } - - if repo == nil { - repo = r - } - } - } - - // Try any repo matching the same repoURL - if repo != nil { - return repo, nil - } - - // No repo found with a matching URL - attempt fallback without any actual credentials - return &v1alpha1.Repository{Repo: repoURL}, nil - } +func resolveProjectName(project string) string { + if strings.Contains(project, "{{") { + return "" } - return repo, err + + return project } diff --git a/applicationset/services/repo_service_test.go b/applicationset/services/repo_service_test.go index 35d9af6ac655d6..1f648bd4099653 100644 --- a/applicationset/services/repo_service_test.go +++ b/applicationset/services/repo_service_test.go @@ -5,12 +5,18 @@ import ( "fmt" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - + "github.com/argoproj/argo-cd/v2/common" "github.com/argoproj/argo-cd/v2/reposerver/apiclient" repo_mocks "github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks" + "github.com/argoproj/argo-cd/v2/util/db" "github.com/argoproj/argo-cd/v2/util/git" + "github.com/argoproj/argo-cd/v2/util/settings" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + 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" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" ) @@ -19,7 +25,7 @@ func TestGetDirectories(t *testing.T) { type fields struct { storecreds git.CredsStore submoduleEnabled bool - listRepositories func(ctx context.Context) ([]*v1alpha1.Repository, error) + getRepository func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) getGitDirectories func(ctx context.Context, req *apiclient.GitDirectoriesRequest) (*apiclient.GitDirectoriesResponse, error) } type args struct { @@ -37,23 +43,23 @@ func TestGetDirectories(t *testing.T) { wantErr assert.ErrorAssertionFunc }{ {name: "ErrorGettingRepos", fields: fields{ - listRepositories: func(ctx context.Context) ([]*v1alpha1.Repository, error) { + getRepository: func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) { return nil, fmt.Errorf("unable to get repos") }, }, args: args{}, want: nil, wantErr: assert.Error}, {name: "ErrorGettingDirs", fields: fields{ - listRepositories: func(ctx context.Context) ([]*v1alpha1.Repository, error) { - return []*v1alpha1.Repository{{}}, nil + getRepository: func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) { + return &v1alpha1.Repository{}, nil }, getGitDirectories: func(ctx context.Context, req *apiclient.GitDirectoriesRequest) (*apiclient.GitDirectoriesResponse, error) { return nil, fmt.Errorf("unable to get dirs") }, }, args: args{}, want: nil, wantErr: assert.Error}, {name: "HappyCase", fields: fields{ - listRepositories: func(ctx context.Context) ([]*v1alpha1.Repository, error) { - return []*v1alpha1.Repository{{ + getRepository: func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) { + return &v1alpha1.Repository{ Repo: "foo", - }}, nil + }, nil }, getGitDirectories: func(ctx context.Context, req *apiclient.GitDirectoriesRequest) (*apiclient.GitDirectoriesResponse, error) { return &apiclient.GitDirectoriesResponse{ @@ -64,8 +70,8 @@ func TestGetDirectories(t *testing.T) { repoURL: "foo", }, want: []string{"foo", "foo/bar", "bar/foo"}, wantErr: assert.NoError}, {name: "ErrorVerifyingCommit", fields: fields{ - listRepositories: func(ctx context.Context) ([]*v1alpha1.Repository, error) { - return []*v1alpha1.Repository{{}}, nil + getRepository: func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) { + return &v1alpha1.Repository{}, nil }, getGitDirectories: func(ctx context.Context, req *apiclient.GitDirectoriesRequest) (*apiclient.GitDirectoriesResponse, error) { return nil, fmt.Errorf("revision HEAD is not signed") @@ -75,7 +81,7 @@ func TestGetDirectories(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { a := &argoCDService{ - listRepositories: tt.fields.listRepositories, + getRepository: tt.fields.getRepository, storecreds: tt.fields.storecreds, submoduleEnabled: tt.fields.submoduleEnabled, getGitDirectories: tt.fields.getGitDirectories, @@ -93,7 +99,7 @@ func TestGetFiles(t *testing.T) { type fields struct { storecreds git.CredsStore submoduleEnabled bool - listRepositories func(ctx context.Context) ([]*v1alpha1.Repository, error) + getRepository func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) getGitFiles func(ctx context.Context, req *apiclient.GitFilesRequest) (*apiclient.GitFilesResponse, error) } type args struct { @@ -112,23 +118,23 @@ func TestGetFiles(t *testing.T) { wantErr assert.ErrorAssertionFunc }{ {name: "ErrorGettingRepos", fields: fields{ - listRepositories: func(ctx context.Context) ([]*v1alpha1.Repository, error) { + getRepository: func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) { return nil, fmt.Errorf("unable to get repos") }, }, args: args{}, want: nil, wantErr: assert.Error}, {name: "ErrorGettingFiles", fields: fields{ - listRepositories: func(ctx context.Context) ([]*v1alpha1.Repository, error) { - return []*v1alpha1.Repository{{}}, nil + getRepository: func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) { + return &v1alpha1.Repository{}, nil }, getGitFiles: func(ctx context.Context, req *apiclient.GitFilesRequest) (*apiclient.GitFilesResponse, error) { return nil, fmt.Errorf("unable to get files") }, }, args: args{}, want: nil, wantErr: assert.Error}, {name: "HappyCase", fields: fields{ - listRepositories: func(ctx context.Context) ([]*v1alpha1.Repository, error) { - return []*v1alpha1.Repository{{ + getRepository: func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) { + return &v1alpha1.Repository{ Repo: "foo", - }}, nil + }, nil }, getGitFiles: func(ctx context.Context, req *apiclient.GitFilesRequest) (*apiclient.GitFilesResponse, error) { return &apiclient.GitFilesResponse{ @@ -144,87 +150,320 @@ func TestGetFiles(t *testing.T) { "foo.json": []byte("hello: world!"), "bar.yaml": []byte("yay: appsets"), }, wantErr: assert.NoError}, - {name: "NoRepoFoundFallback", fields: fields{ - listRepositories: func(ctx context.Context) ([]*v1alpha1.Repository, error) { - return []*v1alpha1.Repository{}, nil + {name: "ErrorVerifyingCommit", fields: fields{ + getRepository: func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) { + return &v1alpha1.Repository{}, nil + }, + getGitFiles: func(ctx context.Context, req *apiclient.GitFilesRequest) (*apiclient.GitFilesResponse, error) { + return nil, fmt.Errorf("revision HEAD is not signed") }, + }, args: args{}, want: nil, wantErr: assert.Error}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &argoCDService{ + getRepository: tt.fields.getRepository, + storecreds: tt.fields.storecreds, + submoduleEnabled: tt.fields.submoduleEnabled, + getGitFiles: tt.fields.getGitFiles, + } + got, err := a.GetFiles(tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern, "", tt.args.noRevisionCache, tt.args.verifyCommit) + if !tt.wantErr(t, err, fmt.Sprintf("GetFiles(%v, %v, %v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern, tt.args.noRevisionCache)) { + return + } + assert.Equalf(t, tt.want, got, "GetFiles(%v, %v, %v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern, tt.args.noRevisionCache) + }) + } +} + +func TestGetFilesWithRepoFiltering(t *testing.T) { + testNamespace := "test" + type fields struct { + storecreds git.CredsStore + submoduleEnabled bool + getRepository func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) + getGitFiles func(ctx context.Context, req *apiclient.GitFilesRequest) (*apiclient.GitFilesResponse, error) + repoSecrets []*v1.Secret + } + type args struct { + ctx context.Context + repoURL string + project string + } + tests := []struct { + name string + fields fields + args args + want map[string][]byte + wantErr assert.ErrorAssertionFunc + }{ + {name: "NonExistentRepoResolution", fields: fields{ getGitFiles: func(ctx context.Context, req *apiclient.GitFilesRequest) (*apiclient.GitFilesResponse, error) { - require.Equal(t, &v1alpha1.Repository{Repo: "foo"}, req.Repo) + require.Equal(t, &v1alpha1.Repository{Repo: "does-not-exist"}, req.Repo) return &apiclient.GitFilesResponse{ Map: map[string][]byte{}, }, nil }, + repoSecrets: []*v1.Secret{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "cred1", + Annotations: map[string]string{ + common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD, + }, + Labels: map[string]string{ + common.LabelKeySecretType: common.LabelValueSecretTypeRepository, + }, + }, + Data: map[string][]byte{ + "url": []byte("git@github.com:argoproj/argo-cd.git"), + "project": []byte("some-proj"), + "password": []byte("123456"), + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "cred2", + Annotations: map[string]string{ + common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD, + }, + Labels: map[string]string{ + common.LabelKeySecretType: common.LabelValueSecretTypeRepository, + }, + }, + Data: map[string][]byte{ + "url": []byte("git@github.com:argoproj/argo-cd.git"), + "password": []byte("123456"), + }, + }, + }, }, args: args{ - repoURL: "foo", + repoURL: "does-not-exist", }, want: map[string][]byte{}, wantErr: assert.NoError}, - {name: "RepoWithProjectFoundFallback", fields: fields{ - listRepositories: func(ctx context.Context) ([]*v1alpha1.Repository, error) { - return []*v1alpha1.Repository{{Repo: "foo", Project: "default"}}, nil - }, + {name: "DefaultRepoResolution", fields: fields{ getGitFiles: func(ctx context.Context, req *apiclient.GitFilesRequest) (*apiclient.GitFilesResponse, error) { - require.Equal(t, &v1alpha1.Repository{Repo: "foo", Project: "default"}, req.Repo) + require.Equal(t, &v1alpha1.Repository{Repo: "git@github.com:argoproj/argo-cd.git", Password: "123456"}, req.Repo) return &apiclient.GitFilesResponse{ Map: map[string][]byte{}, }, nil }, + repoSecrets: []*v1.Secret{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "cred1", + Annotations: map[string]string{ + common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD, + }, + Labels: map[string]string{ + common.LabelKeySecretType: common.LabelValueSecretTypeRepository, + }, + }, + Data: map[string][]byte{ + "url": []byte("git@github.com:argoproj/argo-cd.git"), + "project": []byte("some-proj"), + "password": []byte("123456"), + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "cred2", + Annotations: map[string]string{ + common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD, + }, + Labels: map[string]string{ + common.LabelKeySecretType: common.LabelValueSecretTypeRepository, + }, + }, + Data: map[string][]byte{ + "url": []byte("git@github.com:argoproj/argo-cd.git"), + "password": []byte("123456"), + }, + }, + }, }, args: args{ - repoURL: "foo", + repoURL: "git@github.com:argoproj/argo-cd.git", }, want: map[string][]byte{}, wantErr: assert.NoError}, - {name: "MultipleReposWithEmptyProjectFoundFallback", fields: fields{ - listRepositories: func(ctx context.Context) ([]*v1alpha1.Repository, error) { - return []*v1alpha1.Repository{{Repo: "foo", Project: "default"}, {Repo: "foo", Project: ""}}, nil - }, + {name: "TemplatedRepoResolution", fields: fields{ getGitFiles: func(ctx context.Context, req *apiclient.GitFilesRequest) (*apiclient.GitFilesResponse, error) { - require.Equal(t, &v1alpha1.Repository{Repo: "foo", Project: ""}, req.Repo) + require.Equal(t, &v1alpha1.Repository{Repo: "git@github.com:argoproj/argo-cd.git", Password: "123456"}, req.Repo) return &apiclient.GitFilesResponse{ Map: map[string][]byte{}, }, nil }, + repoSecrets: []*v1.Secret{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "cred1", + Annotations: map[string]string{ + common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD, + }, + Labels: map[string]string{ + common.LabelKeySecretType: common.LabelValueSecretTypeRepository, + }, + }, + Data: map[string][]byte{ + "url": []byte("git@github.com:argoproj/argo-cd.git"), + "project": []byte("some-proj"), + "password": []byte("123456"), + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "cred2", + Annotations: map[string]string{ + common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD, + }, + Labels: map[string]string{ + common.LabelKeySecretType: common.LabelValueSecretTypeRepository, + }, + }, + Data: map[string][]byte{ + "url": []byte("git@github.com:argoproj/argo-cd.git"), + "password": []byte("123456"), + }, + }, + }, }, args: args{ - repoURL: "foo", + repoURL: "git@github.com:argoproj/argo-cd.git", + project: "{{ .some-proj }}", }, want: map[string][]byte{}, wantErr: assert.NoError}, - {name: "MultipleReposFoundFallback", fields: fields{ - listRepositories: func(ctx context.Context) ([]*v1alpha1.Repository, error) { - return []*v1alpha1.Repository{{Repo: "foo", Project: "default"}, {Repo: "foo", Project: "bar"}}, nil - }, + {name: "ProjectRepoResolutionHappyPath", fields: fields{ getGitFiles: func(ctx context.Context, req *apiclient.GitFilesRequest) (*apiclient.GitFilesResponse, error) { - require.Equal(t, &v1alpha1.Repository{Repo: "foo", Project: "default"}, req.Repo) + require.Equal(t, &v1alpha1.Repository{Repo: "git@github.com:argoproj/argo-cd.git", Project: "some-proj", Password: "123456"}, req.Repo) return &apiclient.GitFilesResponse{ Map: map[string][]byte{}, }, nil }, + repoSecrets: []*v1.Secret{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "cred1", + Annotations: map[string]string{ + common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD, + }, + Labels: map[string]string{ + common.LabelKeySecretType: common.LabelValueSecretTypeRepository, + }, + }, + Data: map[string][]byte{ + "url": []byte("git@github.com:argoproj/argo-cd.git"), + "project": []byte("some-proj"), + "password": []byte("123456"), + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "cred2", + Annotations: map[string]string{ + common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD, + }, + Labels: map[string]string{ + common.LabelKeySecretType: common.LabelValueSecretTypeRepository, + }, + }, + Data: map[string][]byte{ + "url": []byte("git@github.com:argoproj/argo-cd.git"), + "password": []byte("123456"), + }, + }, + }, }, args: args{ - repoURL: "foo", + project: "some-proj", + repoURL: "git@github.com:argoproj/argo-cd.git", }, want: map[string][]byte{}, wantErr: assert.NoError}, - {name: "ErrorVerifyingCommit", fields: fields{ - listRepositories: func(ctx context.Context) ([]*v1alpha1.Repository, error) { - return []*v1alpha1.Repository{{}}, nil - }, + {name: "NonExistingProjectRepoResolution", fields: fields{ getGitFiles: func(ctx context.Context, req *apiclient.GitFilesRequest) (*apiclient.GitFilesResponse, error) { - return nil, fmt.Errorf("revision HEAD is not signed") + require.Equal(t, &v1alpha1.Repository{Repo: "git@github.com:argoproj/argo-cd.git"}, req.Repo) + return &apiclient.GitFilesResponse{ + Map: map[string][]byte{}, + }, nil }, - }, args: args{}, want: nil, wantErr: assert.Error}, + repoSecrets: []*v1.Secret{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "cred1", + Annotations: map[string]string{ + common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD, + }, + Labels: map[string]string{ + common.LabelKeySecretType: common.LabelValueSecretTypeRepository, + }, + }, + Data: map[string][]byte{ + "url": []byte("git@github.com:argoproj/argo-cd.git"), + "project": []byte("some-proj"), + "password": []byte("123456"), + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "cred2", + Annotations: map[string]string{ + common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD, + }, + Labels: map[string]string{ + common.LabelKeySecretType: common.LabelValueSecretTypeRepository, + }, + }, + Data: map[string][]byte{ + "url": []byte("git@github.com:argoproj/argo-cd.git"), + "project": []byte("some-other-proj"), + "password": []byte("123456"), + }, + }, + }, + }, args: args{ + project: "does-not-exist-proj", + repoURL: "git@github.com:argoproj/argo-cd.git", + }, want: map[string][]byte{}, wantErr: assert.NoError}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + cm := v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argocd-cm", + Namespace: testNamespace, + Labels: map[string]string{ + "app.kubernetes.io/part-of": "argocd", + }, + }, + Data: nil, + } + var objs []runtime.Object + for _, secret := range tt.fields.repoSecrets { + objs = append(objs, secret) + } + + clientset := fake.NewClientset(append(objs, &cm)...) + testDB := db.NewDB(testNamespace, settings.NewSettingsManager(context.Background(), clientset, testNamespace), clientset) a := &argoCDService{ - listRepositories: tt.fields.listRepositories, + getRepository: testDB.GetRepository, storecreds: tt.fields.storecreds, submoduleEnabled: tt.fields.submoduleEnabled, getGitFiles: tt.fields.getGitFiles, } - got, err := a.GetFiles(tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern, "", tt.args.noRevisionCache, tt.args.verifyCommit) - if !tt.wantErr(t, err, fmt.Sprintf("GetFiles(%v, %v, %v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern, tt.args.noRevisionCache)) { + got, err := a.GetFiles(tt.args.ctx, tt.args.repoURL, "", tt.args.project, "", false, false) + if !tt.wantErr(t, err, fmt.Sprintf("GetFiles(%v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.project)) { return } - assert.Equalf(t, tt.want, got, "GetFiles(%v, %v, %v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern, tt.args.noRevisionCache) + assert.Equalf(t, tt.want, got, "GetFiles(%v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.project) }) } } func TestNewArgoCDService(t *testing.T) { - service, err := NewArgoCDService(func(ctx context.Context) ([]*v1alpha1.Repository, error) { - return []*v1alpha1.Repository{{}}, nil + service, err := NewArgoCDService(func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) { + return &v1alpha1.Repository{}, nil }, false, &repo_mocks.Clientset{}, false) require.NoError(t, err) assert.NotNil(t, service) diff --git a/cmd/argocd-applicationset-controller/commands/applicationset_controller.go b/cmd/argocd-applicationset-controller/commands/applicationset_controller.go index 0fa0ad025d409a..ce7b69c69b565b 100644 --- a/cmd/argocd-applicationset-controller/commands/applicationset_controller.go +++ b/cmd/argocd-applicationset-controller/commands/applicationset_controller.go @@ -189,7 +189,7 @@ func NewCommand() *cobra.Command { } repoClientset := apiclient.NewRepoServerClientset(argocdRepoServer, repoServerTimeoutSeconds, tlsConfig) - argoCDService, err := services.NewArgoCDService(argoCDDB.ListRepositories, gitSubmoduleEnabled, repoClientset, enableNewGitFileGlobbing) + argoCDService, err := services.NewArgoCDService(argoCDDB.GetRepository, gitSubmoduleEnabled, repoClientset, enableNewGitFileGlobbing) errors.CheckError(err) topLevelGenerators := generators.GetGenerators(ctx, mgr.GetClient(), k8sClient, namespace, argoCDService, dynamicClient, scmConfig) diff --git a/server/applicationset/applicationset.go b/server/applicationset/applicationset.go index 470c729b79dba0..248aaf9eecf433 100644 --- a/server/applicationset/applicationset.go +++ b/server/applicationset/applicationset.go @@ -266,7 +266,7 @@ func (s *Server) generateApplicationSetApps(ctx context.Context, logEntry *log.E argoCDDB := s.db scmConfig := generators.NewSCMConfig(s.ScmRootCAPath, s.AllowedScmProviders, s.EnableScmProviders, github_app.NewAuthCredentials(argoCDDB.(db.RepoCredsDB)), true) - argoCDService, err := services.NewArgoCDService(s.db.ListRepositories, s.GitSubmoduleEnabled, s.repoClientSet, s.EnableNewGitFileGlobbing) + argoCDService, err := services.NewArgoCDService(s.db.GetRepository, s.GitSubmoduleEnabled, s.repoClientSet, s.EnableNewGitFileGlobbing) if err != nil { return nil, fmt.Errorf("error creating ArgoCDService: %w", err) } diff --git a/server/repository/repository.go b/server/repository/repository.go index c285fcace45326..191b720733e0fa 100644 --- a/server/repository/repository.go +++ b/server/repository/repository.go @@ -16,8 +16,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/cache" - "github.com/argoproj/argo-cd/v2/util/repository" - repositorypkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/repository" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" appsv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" @@ -28,6 +26,7 @@ import ( "github.com/argoproj/argo-cd/v2/util/argo" "github.com/argoproj/argo-cd/v2/util/db" "github.com/argoproj/argo-cd/v2/util/errors" + "github.com/argoproj/argo-cd/v2/util/git" "github.com/argoproj/argo-cd/v2/util/io" "github.com/argoproj/argo-cd/v2/util/rbac" "github.com/argoproj/argo-cd/v2/util/settings" @@ -630,7 +629,34 @@ func getRepository(ctx context.Context, listRepositories func(context.Context, * return nil, err } - return repository.FilterRepositoryByProjectAndURL(repositories.Items, q.Repo, q.GetAppProject()) + var foundRepos []*v1alpha1.Repository + for _, repo := range repositories.Items { + if git.SameURL(repo.Repo, q.Repo) { + foundRepos = append(foundRepos, repo) + } + } + + if len(foundRepos) == 0 { + return nil, errPermissionDenied + } + + var foundRepo *v1alpha1.Repository + if len(foundRepos) == 1 && q.GetAppProject() == "" { + foundRepo = foundRepos[0] + } else if len(foundRepos) > 0 { + for _, repo := range foundRepos { + if repo.Project == q.GetAppProject() { + foundRepo = repo + break + } + } + } + + if foundRepo == nil { + return nil, fmt.Errorf("repository not found for url %q and project %q", q.Repo, q.GetAppProject()) + } + + return foundRepo, nil } // ValidateAccess checks whether access to a repository is possible with the diff --git a/util/repository/repository.go b/util/repository/repository.go deleted file mode 100644 index a5a79bf595a94e..00000000000000 --- a/util/repository/repository.go +++ /dev/null @@ -1,41 +0,0 @@ -package repository - -import ( - "fmt" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" - "github.com/argoproj/argo-cd/v2/util/git" -) - -var errPermissionDenied = status.Error(codes.PermissionDenied, "permission denied") - -// FilterRepositoryByProjectAndURL fetches a single repository which the user has access to. If only one repository can be found which -// matches the same URL, that will be returned (this is for backward compatibility reasons). If multiple repositories -// are matched, a repository is only returned if it matches the app project of the incoming request. -func FilterRepositoryByProjectAndURL(repositories v1alpha1.Repositories, repoURL, appProject string) (*v1alpha1.Repository, error) { - var foundRepos []*v1alpha1.Repository - for _, repo := range repositories { - if git.SameURL(repo.Repo, repoURL) { - foundRepos = append(foundRepos, repo) - } - } - - if len(foundRepos) == 0 { - return nil, errPermissionDenied - } - - if len(foundRepos) == 1 && appProject == "" { - return foundRepos[0], nil - } - - for _, repo := range foundRepos { - if repo.Project == appProject { - return repo, nil - } - } - - return nil, fmt.Errorf("repository not found for url %q and project %q", repoURL, appProject) -}