diff --git a/applicationset/services/mocks/RepositoryDB.go b/applicationset/services/mocks/RepositoryDB.go deleted file mode 100644 index 79176385fc5e4..0000000000000 --- a/applicationset/services/mocks/RepositoryDB.go +++ /dev/null @@ -1,60 +0,0 @@ -// Code generated by mockery v2.40.2. DO NOT EDIT. - -package mocks - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" - - v1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" -) - -// RepositoryDB is an autogenerated mock type for the RepositoryDB type -type RepositoryDB struct { - mock.Mock -} - -// GetRepository provides a mock function with given fields: ctx, url -func (_m *RepositoryDB) GetRepository(ctx context.Context, url string) (*v1alpha1.Repository, error) { - ret := _m.Called(ctx, url) - - if len(ret) == 0 { - panic("no return value specified for GetRepository") - } - - var r0 *v1alpha1.Repository - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (*v1alpha1.Repository, error)); ok { - return rf(ctx, url) - } - if rf, ok := ret.Get(0).(func(context.Context, string) *v1alpha1.Repository); ok { - r0 = rf(ctx, url) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v1alpha1.Repository) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, url) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// NewRepositoryDB creates a new instance of RepositoryDB. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewRepositoryDB(t interface { - mock.TestingT - Cleanup(func()) -}) *RepositoryDB { - mock := &RepositoryDB{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/applicationset/services/repo_service.go b/applicationset/services/repo_service.go index 608d22f365637..90356eceff1bd 100644 --- a/applicationset/services/repo_service.go +++ b/applicationset/services/repo_service.go @@ -6,21 +6,12 @@ import ( "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/db" "github.com/argoproj/argo-cd/v2/util/git" "github.com/argoproj/argo-cd/v2/util/io" ) -//go:generate go run github.com/vektra/mockery/v2@v2.40.2 --name=RepositoryDB - -// RepositoryDB Is a lean facade for ArgoDB, -// Using a lean interface makes it easier to test the functionality of the git generator -type RepositoryDB interface { - GetRepository(ctx context.Context, url string) (*v1alpha1.Repository, error) -} - type argoCDService struct { - repositoriesDB RepositoryDB + getRepository func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) storecreds git.CredsStore submoduleEnabled bool repoServerClientSet apiclient.Clientset @@ -38,9 +29,9 @@ type Repos interface { GetDirectories(ctx context.Context, repoURL string, revision string, noRevisionCache bool) ([]string, error) } -func NewArgoCDService(db db.ArgoDB, 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{ - repositoriesDB: db.(RepositoryDB), + getRepository: getRepository, submoduleEnabled: submoduleEnabled, repoServerClientSet: repoClientset, newFileGlobbingEnabled: newFileGlobbingEnabled, @@ -48,7 +39,7 @@ func NewArgoCDService(db db.ArgoDB, submoduleEnabled bool, repoClientset apiclie } func (a *argoCDService) GetFiles(ctx context.Context, repoURL string, revision string, pattern string, noRevisionCache bool) (map[string][]byte, error) { - repo, err := a.repositoriesDB.GetRepository(ctx, repoURL) + repo, err := a.getRepository(ctx, repoURL, "") if err != nil { return nil, fmt.Errorf("error in GetRepository: %w", err) } @@ -75,7 +66,7 @@ func (a *argoCDService) GetFiles(ctx context.Context, repoURL string, revision s } func (a *argoCDService) GetDirectories(ctx context.Context, repoURL string, revision string, noRevisionCache bool) ([]string, error) { - repo, err := a.repositoriesDB.GetRepository(ctx, repoURL) + repo, err := a.getRepository(ctx, repoURL, "") if err != nil { return nil, fmt.Errorf("error in GetRepository: %w", err) } diff --git a/applicationset/services/repo_service_test.go b/applicationset/services/repo_service_test.go index 040fe57f96958..8e91c4c0637c3 100644 --- a/applicationset/services/repo_service_test.go +++ b/applicationset/services/repo_service_test.go @@ -5,10 +5,8 @@ import ( "fmt" "testing" - "github.com/argoproj/argo-cd/v2/applicationset/services/mocks" "github.com/argoproj/argo-cd/v2/reposerver/apiclient" repo_mocks "github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks" - db_mocks "github.com/argoproj/argo-cd/v2/util/db/mocks" "github.com/argoproj/argo-cd/v2/util/git" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -19,9 +17,9 @@ import ( func TestGetDirectories(t *testing.T) { type fields struct { - repositoriesDBFuncs []func(*mocks.RepositoryDB) storecreds git.CredsStore submoduleEnabled bool + getRepository func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) repoServerClientFuncs []func(*repo_mocks.RepoServerServiceClient) } type args struct { @@ -38,17 +36,13 @@ func TestGetDirectories(t *testing.T) { wantErr assert.ErrorAssertionFunc }{ {name: "ErrorGettingRepos", fields: fields{ - repositoriesDBFuncs: []func(*mocks.RepositoryDB){ - func(db *mocks.RepositoryDB) { - db.On("GetRepository", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("unable to get repos")) - }, + 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{ - repositoriesDBFuncs: []func(*mocks.RepositoryDB){ - func(db *mocks.RepositoryDB) { - db.On("GetRepository", mock.Anything, mock.Anything).Return(&v1alpha1.Repository{}, nil) - }, + getRepository: func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) { + return &v1alpha1.Repository{}, nil }, repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){ func(client *repo_mocks.RepoServerServiceClient) { @@ -57,10 +51,8 @@ func TestGetDirectories(t *testing.T) { }, }, args: args{}, want: nil, wantErr: assert.Error}, {name: "HappyCase", fields: fields{ - repositoriesDBFuncs: []func(*mocks.RepositoryDB){ - func(db *mocks.RepositoryDB) { - db.On("GetRepository", mock.Anything, mock.Anything).Return(&v1alpha1.Repository{}, nil) - }, + getRepository: func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) { + return &v1alpha1.Repository{}, nil }, repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){ func(client *repo_mocks.RepoServerServiceClient) { @@ -73,18 +65,14 @@ func TestGetDirectories(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mockDb := &mocks.RepositoryDB{} mockRepoClient := &repo_mocks.RepoServerServiceClient{} // decorate the mocks - for i := range tt.fields.repositoriesDBFuncs { - tt.fields.repositoriesDBFuncs[i](mockDb) - } for i := range tt.fields.repoServerClientFuncs { tt.fields.repoServerClientFuncs[i](mockRepoClient) } a := &argoCDService{ - repositoriesDB: mockDb, + getRepository: tt.fields.getRepository, storecreds: tt.fields.storecreds, submoduleEnabled: tt.fields.submoduleEnabled, repoServerClientSet: &repo_mocks.Clientset{RepoServerServiceClient: mockRepoClient}, @@ -100,10 +88,10 @@ func TestGetDirectories(t *testing.T) { func TestGetFiles(t *testing.T) { type fields struct { - repositoriesDBFuncs []func(*mocks.RepositoryDB) storecreds git.CredsStore submoduleEnabled bool repoServerClientFuncs []func(*repo_mocks.RepoServerServiceClient) + getRepository func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) } type args struct { ctx context.Context @@ -120,17 +108,13 @@ func TestGetFiles(t *testing.T) { wantErr assert.ErrorAssertionFunc }{ {name: "ErrorGettingRepos", fields: fields{ - repositoriesDBFuncs: []func(*mocks.RepositoryDB){ - func(db *mocks.RepositoryDB) { - db.On("GetRepository", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("unable to get repos")) - }, + 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{ - repositoriesDBFuncs: []func(*mocks.RepositoryDB){ - func(db *mocks.RepositoryDB) { - db.On("GetRepository", mock.Anything, mock.Anything).Return(&v1alpha1.Repository{}, nil) - }, + getRepository: func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) { + return &v1alpha1.Repository{}, nil }, repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){ func(client *repo_mocks.RepoServerServiceClient) { @@ -139,10 +123,8 @@ func TestGetFiles(t *testing.T) { }, }, args: args{}, want: nil, wantErr: assert.Error}, {name: "HappyCase", fields: fields{ - repositoriesDBFuncs: []func(*mocks.RepositoryDB){ - func(db *mocks.RepositoryDB) { - db.On("GetRepository", mock.Anything, mock.Anything).Return(&v1alpha1.Repository{}, nil) - }, + getRepository: func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) { + return &v1alpha1.Repository{}, nil }, repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){ func(client *repo_mocks.RepoServerServiceClient) { @@ -161,18 +143,14 @@ func TestGetFiles(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mockDb := &mocks.RepositoryDB{} mockRepoClient := &repo_mocks.RepoServerServiceClient{} // decorate the mocks - for i := range tt.fields.repositoriesDBFuncs { - tt.fields.repositoriesDBFuncs[i](mockDb) - } for i := range tt.fields.repoServerClientFuncs { tt.fields.repoServerClientFuncs[i](mockRepoClient) } a := &argoCDService{ - repositoriesDB: mockDb, + getRepository: tt.fields.getRepository, storecreds: tt.fields.storecreds, submoduleEnabled: tt.fields.submoduleEnabled, repoServerClientSet: &repo_mocks.Clientset{RepoServerServiceClient: mockRepoClient}, @@ -187,7 +165,9 @@ func TestGetFiles(t *testing.T) { } func TestNewArgoCDService(t *testing.T) { - service, err := NewArgoCDService(&db_mocks.ArgoDB{}, false, &repo_mocks.Clientset{}, false) + service, err := NewArgoCDService(func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) { + return &v1alpha1.Repository{}, nil + }, false, &repo_mocks.Clientset{}, false) assert.NoError(t, err, err) assert.NotNil(t, service) } diff --git a/assets/swagger.json b/assets/swagger.json index d32d892de1afb..f9c83a63eb66e 100644 --- a/assets/swagger.json +++ b/assets/swagger.json @@ -3171,7 +3171,7 @@ "parameters": [ { "type": "string", - "description": "URL is the URL that this credentials matches to", + "description": "URL is the URL to which these credentials match", "name": "creds.url", "in": "path", "required": true @@ -3251,6 +3251,12 @@ "description": "Whether to force a cache refresh on repo's connection state.", "name": "forceRefresh", "in": "query" + }, + { + "type": "string", + "description": "App project for query.", + "name": "appProject", + "in": "query" } ], "responses": { @@ -3373,6 +3379,12 @@ "description": "Whether to force a cache refresh on repo's connection state.", "name": "forceRefresh", "in": "query" + }, + { + "type": "string", + "description": "App project for query.", + "name": "appProject", + "in": "query" } ], "responses": { @@ -3409,6 +3421,12 @@ "description": "Whether to force a cache refresh on repo's connection state.", "name": "forceRefresh", "in": "query" + }, + { + "type": "string", + "description": "App project for query.", + "name": "appProject", + "in": "query" } ], "responses": { @@ -3493,6 +3511,12 @@ "description": "Whether to force a cache refresh on repo's connection state.", "name": "forceRefresh", "in": "query" + }, + { + "type": "string", + "description": "App project for query.", + "name": "appProject", + "in": "query" } ], "responses": { @@ -3530,6 +3554,12 @@ "description": "Whether to force a cache refresh on repo's connection state.", "name": "forceRefresh", "in": "query" + }, + { + "type": "string", + "description": "App project for query.", + "name": "appProject", + "in": "query" } ], "responses": { @@ -8065,7 +8095,7 @@ }, "url": { "type": "string", - "title": "URL is the URL that this credentials matches to" + "title": "URL is the URL to which these credentials match" }, "username": { "type": "string", @@ -8151,7 +8181,7 @@ }, "project": { "type": "string", - "title": "Reference between project and repository that allow you automatically to be added as item inside SourceRepos project entity" + "title": "Reference between project and repository that allows it to be automatically added as an item inside SourceRepos project entity" }, "proxy": { "type": "string", diff --git a/cmd/argocd-applicationset-controller/commands/applicationset_controller.go b/cmd/argocd-applicationset-controller/commands/applicationset_controller.go index 4720123860ed2..d4b25d706e986 100644 --- a/cmd/argocd-applicationset-controller/commands/applicationset_controller.go +++ b/cmd/argocd-applicationset-controller/commands/applicationset_controller.go @@ -105,7 +105,7 @@ func NewCommand() *cobra.Command { os.Exit(1) } - // By default watch all namespace + // By default, watch all namespaces var watchedNamespace string = "" // If the applicationset-namespaces contains only one namespace it corresponds to the current namespace @@ -172,7 +172,7 @@ func NewCommand() *cobra.Command { } repoClientset := apiclient.NewRepoServerClientset(argocdRepoServer, repoServerTimeoutSeconds, tlsConfig) - argoCDService, err := services.NewArgoCDService(argoCDDB, gitSubmoduleEnabled, repoClientset, enableNewGitFileGlobbing) + argoCDService, err := services.NewArgoCDService(argoCDDB.GetRepository, gitSubmoduleEnabled, repoClientset, enableNewGitFileGlobbing) errors.CheckError(err) terminalGenerators := map[string]generators.Generator{ diff --git a/cmd/argocd/commands/admin/repo.go b/cmd/argocd/commands/admin/repo.go index 208a6ef8550f8..46795b45a9ba5 100644 --- a/cmd/argocd/commands/admin/repo.go +++ b/cmd/argocd/commands/admin/repo.go @@ -157,7 +157,7 @@ func NewGenRepoSpecCommand() *cobra.Command { _, err := argoDB.CreateRepository(ctx, &repoOpts.Repo) errors.CheckError(err) - secret, err := kubeClientset.CoreV1().Secrets(ArgoCDNamespace).Get(ctx, db.RepoURLToSecretName(repoSecretPrefix, repoOpts.Repo.Repo), v1.GetOptions{}) + secret, err := kubeClientset.CoreV1().Secrets(ArgoCDNamespace).Get(ctx, db.RepoURLToSecretName(repoSecretPrefix, repoOpts.Repo.Repo, repoOpts.Repo.Project), v1.GetOptions{}) errors.CheckError(err) errors.CheckError(PrintResources(outputFormat, os.Stdout, secret)) diff --git a/cmd/argocd/commands/repo.go b/cmd/argocd/commands/repo.go index 1a5b4388fbeba..1afddb170c5e9 100644 --- a/cmd/argocd/commands/repo.go +++ b/cmd/argocd/commands/repo.go @@ -242,6 +242,9 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { // NewRepoRemoveCommand returns a new instance of an `argocd repo remove` command func NewRepoRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { + var ( + project string + ) var command = &cobra.Command{ Use: "rm REPO", Short: "Remove repository credentials", @@ -255,12 +258,13 @@ func NewRepoRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command conn, repoIf := headless.NewClientOrDie(clientOpts, c).NewRepoClientOrDie() defer io.Close(conn) for _, repoURL := range args { - _, err := repoIf.DeleteRepository(ctx, &repositorypkg.RepoQuery{Repo: repoURL}) + _, err := repoIf.DeleteRepository(ctx, &repositorypkg.RepoQuery{Repo: repoURL, AppProject: project}) errors.CheckError(err) fmt.Printf("Repository '%s' removed\n", repoURL) } }, } + command.Flags().StringVar(&project, "project", "", "project of the repository") return command } @@ -337,6 +341,7 @@ func NewRepoGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { var ( output string refresh string + project string ) var command = &cobra.Command{ Use: "get", @@ -362,7 +367,7 @@ func NewRepoGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { err := fmt.Errorf("--refresh must be one of: 'hard'") errors.CheckError(err) } - repo, err := repoIf.Get(ctx, &repositorypkg.RepoQuery{Repo: repoURL, ForceRefresh: forceRefresh}) + repo, err := repoIf.Get(ctx, &repositorypkg.RepoQuery{Repo: repoURL, ForceRefresh: forceRefresh, AppProject: project}) errors.CheckError(err) switch output { case "yaml", "json": @@ -378,6 +383,8 @@ func NewRepoGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { } }, } + + command.Flags().StringVar(&project, "project", "", "project of the repository") command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|url") command.Flags().StringVar(&refresh, "refresh", "", "Force a cache refresh on connection status , must be one of: 'hard'") return command diff --git a/controller/state.go b/controller/state.go index 80678b74790e7..f94446a0f588a 100644 --- a/controller/state.go +++ b/controller/state.go @@ -178,7 +178,7 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp targetObjs := make([]*unstructured.Unstructured, 0) // Store the map of all sources having ref field into a map for applications with sources field - refSources, err := argo.GetRefSources(context.Background(), app.Spec, m.db) + refSources, err := argo.GetRefSources(context.Background(), app.Spec, m.db.GetRepository) if err != nil { return nil, nil, fmt.Errorf("failed to get ref sources: %v", err) } @@ -188,7 +188,7 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp revisions[i] = source.TargetRevision } ts.AddCheckpoint("helm_ms") - repo, err := m.db.GetRepository(context.Background(), source.RepoURL) + repo, err := m.db.GetRepository(context.Background(), source.RepoURL, proj.Name) if err != nil { return nil, nil, fmt.Errorf("failed to get repo %q: %w", source.RepoURL, err) } diff --git a/docs/user-guide/commands/argocd_repo_get.md b/docs/user-guide/commands/argocd_repo_get.md index 5a900adb09487..e1d445d1068f6 100644 --- a/docs/user-guide/commands/argocd_repo_get.md +++ b/docs/user-guide/commands/argocd_repo_get.md @@ -13,6 +13,7 @@ argocd repo get [flags] ``` -h, --help help for get -o, --output string Output format. One of: json|yaml|wide|url (default "wide") + --project string project of the repository --refresh string Force a cache refresh on connection status , must be one of: 'hard' ``` diff --git a/docs/user-guide/commands/argocd_repo_rm.md b/docs/user-guide/commands/argocd_repo_rm.md index 01e89d48e76a1..4e44bf0acf90b 100644 --- a/docs/user-guide/commands/argocd_repo_rm.md +++ b/docs/user-guide/commands/argocd_repo_rm.md @@ -11,7 +11,8 @@ argocd repo rm REPO [flags] ### Options ``` - -h, --help help for rm + -h, --help help for rm + --project string project of the repository ``` ### Options inherited from parent commands diff --git a/pkg/apiclient/repository/repository.pb.go b/pkg/apiclient/repository/repository.pb.go index 5540580c21f45..3e7938829a26b 100644 --- a/pkg/apiclient/repository/repository.pb.go +++ b/pkg/apiclient/repository/repository.pb.go @@ -278,7 +278,9 @@ type RepoQuery struct { // Repo URL for query Repo string `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"` // Whether to force a cache refresh on repo's connection state - ForceRefresh bool `protobuf:"varint,2,opt,name=forceRefresh,proto3" json:"forceRefresh,omitempty"` + ForceRefresh bool `protobuf:"varint,2,opt,name=forceRefresh,proto3" json:"forceRefresh,omitempty"` + // App project for query + AppProject string `protobuf:"bytes,3,opt,name=appProject,proto3" json:"appProject,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -331,6 +333,13 @@ func (m *RepoQuery) GetForceRefresh() bool { return false } +func (m *RepoQuery) GetAppProject() string { + if m != nil { + return m.AppProject + } + return "" +} + // RepoAccessQuery is a query for checking access to a repo type RepoAccessQuery struct { // The URL to the repo @@ -703,79 +712,79 @@ func init() { } var fileDescriptor_8d38260443475705 = []byte{ - // 1146 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x5f, 0x6f, 0x1b, 0x45, - 0x10, 0xd7, 0x25, 0x8d, 0x9b, 0x4c, 0x9a, 0xd4, 0xd9, 0x84, 0x72, 0xb8, 0x69, 0x1a, 0x5d, 0x4b, - 0x15, 0xa2, 0x72, 0xd7, 0x18, 0x21, 0x50, 0x11, 0x48, 0xce, 0x1f, 0x35, 0x11, 0x11, 0x29, 0x57, - 0x85, 0x07, 0x04, 0x42, 0x9b, 0xf3, 0xc4, 0xbe, 0xf6, 0x7c, 0xb7, 0xdd, 0x5d, 0x1b, 0xac, 0xaa, - 0x2f, 0x3c, 0x21, 0xc1, 0x0b, 0x42, 0x48, 0xbc, 0x21, 0x24, 0x24, 0x1e, 0xf8, 0x02, 0x7c, 0x04, - 0x1e, 0x91, 0xf8, 0x02, 0x28, 0xe2, 0x73, 0x20, 0xb4, 0xbb, 0xe7, 0xbb, 0x73, 0x62, 0x3b, 0xa9, - 0x08, 0x79, 0xdb, 0xf9, 0xcd, 0xdc, 0xcc, 0x6f, 0x7f, 0x3b, 0x3b, 0x6b, 0x83, 0x23, 0x90, 0x77, + // 1147 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x5f, 0x6f, 0xdc, 0x44, + 0x10, 0x97, 0x93, 0xe6, 0x9a, 0x4c, 0x9a, 0xf4, 0xb2, 0x09, 0xc5, 0x5c, 0xd3, 0x34, 0x72, 0x4b, + 0x15, 0xa2, 0x62, 0x37, 0x87, 0x10, 0xa8, 0x08, 0xa4, 0xcb, 0x1f, 0x35, 0x11, 0x11, 0x29, 0xae, + 0xc2, 0x03, 0x02, 0xa1, 0x8d, 0x6f, 0x72, 0xe7, 0xd6, 0x67, 0x6f, 0x77, 0xf7, 0x0e, 0x4e, 0x55, + 0x5f, 0x78, 0x42, 0x82, 0x17, 0x84, 0x90, 0x78, 0x43, 0x48, 0x48, 0x3c, 0xf0, 0x05, 0xf8, 0x08, + 0x3c, 0x22, 0xf1, 0x05, 0x50, 0xc4, 0xe7, 0x40, 0x68, 0x77, 0x7d, 0xb6, 0x2f, 0xb9, 0xbb, 0xa4, + 0x22, 0xe4, 0x6d, 0xf7, 0x37, 0xe3, 0x99, 0xdf, 0xfe, 0x3c, 0x33, 0x6b, 0x83, 0x23, 0x90, 0x77, 0x90, 0x7b, 0x1c, 0x59, 0x22, 0x42, 0x99, 0xf0, 0x6e, 0x61, 0xe9, 0x32, 0x9e, 0xc8, 0x84, 0x40, 0x8e, 0x54, 0x16, 0x1b, 0x49, 0xd2, 0x88, 0xd0, 0xa3, 0x2c, 0xf4, 0x68, 0x1c, 0x27, 0x92, 0xca, - 0x30, 0x89, 0x85, 0x89, 0xac, 0xec, 0x36, 0x42, 0xd9, 0x6c, 0x1f, 0xb8, 0x41, 0xd2, 0xf2, 0x28, + 0x30, 0x89, 0x85, 0xf1, 0xac, 0xec, 0x36, 0x42, 0xd9, 0x6c, 0x1f, 0xb8, 0x41, 0xd2, 0xf2, 0x28, 0x6f, 0x24, 0x8c, 0x27, 0x8f, 0xf5, 0xe2, 0xf5, 0xa0, 0xee, 0x75, 0xaa, 0x1e, 0x7b, 0xd2, 0x50, - 0x5f, 0x0a, 0x8f, 0x32, 0x16, 0x85, 0x81, 0xfe, 0xd6, 0xeb, 0xac, 0xd1, 0x88, 0x35, 0xe9, 0x9a, - 0xd7, 0xc0, 0x18, 0x39, 0x95, 0x58, 0x4f, 0xb3, 0x6d, 0x9d, 0x92, 0x4d, 0xd3, 0x3a, 0x95, 0xbe, - 0xd3, 0x85, 0x19, 0x1f, 0x59, 0x52, 0x63, 0x4c, 0x7c, 0xd8, 0x46, 0xde, 0x25, 0x04, 0x2e, 0xa9, - 0x20, 0xdb, 0x5a, 0xb6, 0x56, 0xa6, 0x7c, 0xbd, 0x26, 0x15, 0x98, 0xe4, 0xd8, 0x09, 0x45, 0x98, - 0xc4, 0xf6, 0x98, 0xc6, 0x33, 0x9b, 0xd8, 0x70, 0x99, 0x32, 0xf6, 0x01, 0x6d, 0xa1, 0x3d, 0xae, - 0x5d, 0x3d, 0x93, 0x2c, 0x01, 0x50, 0xc6, 0x1e, 0xf2, 0xe4, 0x31, 0x06, 0xd2, 0xbe, 0xa4, 0x9d, - 0x05, 0xc4, 0x59, 0x83, 0xcb, 0x35, 0xc6, 0x76, 0xe2, 0xc3, 0x44, 0x15, 0x95, 0x5d, 0x86, 0xbd, - 0xa2, 0x6a, 0xad, 0x30, 0x46, 0x65, 0x33, 0x2d, 0xa8, 0xd7, 0xce, 0x6f, 0x16, 0xcc, 0xa7, 0x74, - 0x37, 0x51, 0xd2, 0x30, 0x4a, 0x49, 0x37, 0xa0, 0x24, 0x92, 0x36, 0x0f, 0x4c, 0x86, 0xe9, 0xea, - 0x9e, 0x9b, 0xab, 0xe3, 0xf6, 0xd4, 0xd1, 0x8b, 0xcf, 0x82, 0xba, 0xdb, 0xa9, 0xba, 0xec, 0x49, - 0xc3, 0x55, 0x5a, 0xbb, 0x05, 0xad, 0xdd, 0x9e, 0xd6, 0x6e, 0x2d, 0x07, 0x1f, 0xe9, 0xb4, 0x7e, - 0x9a, 0xbe, 0xb8, 0xdb, 0xb1, 0x51, 0xbb, 0x1d, 0x3f, 0xb1, 0xdb, 0x77, 0xa1, 0xdc, 0x13, 0xda, - 0x47, 0xc1, 0x92, 0x58, 0x20, 0x79, 0x0d, 0x26, 0x42, 0x89, 0x2d, 0x61, 0x5b, 0xcb, 0xe3, 0x2b, - 0xd3, 0xd5, 0x79, 0xb7, 0x70, 0x3c, 0xa9, 0x34, 0xbe, 0x89, 0x70, 0x36, 0x60, 0x4a, 0x7d, 0x3e, - 0xfc, 0x8c, 0x1c, 0xb8, 0x72, 0x98, 0x28, 0xaa, 0x78, 0xc8, 0x51, 0x18, 0xd9, 0x26, 0xfd, 0x3e, - 0xcc, 0xf9, 0x69, 0x02, 0xae, 0x6a, 0x12, 0x41, 0x80, 0x62, 0xf4, 0x79, 0xb7, 0x05, 0xf2, 0x38, - 0xdf, 0x66, 0x66, 0x2b, 0x1f, 0xa3, 0x42, 0x7c, 0x9e, 0xf0, 0x7a, 0xba, 0xcb, 0xcc, 0x26, 0xb7, - 0x61, 0x46, 0x88, 0xe6, 0x43, 0x1e, 0x76, 0xa8, 0xc4, 0xf7, 0xb1, 0x9b, 0x1e, 0x7a, 0x3f, 0xa8, - 0x32, 0x84, 0xb1, 0xc0, 0xa0, 0xcd, 0xd1, 0x9e, 0xd0, 0x2c, 0x33, 0x9b, 0xdc, 0x85, 0x39, 0x19, - 0x89, 0x8d, 0x28, 0xc4, 0x58, 0x6e, 0x20, 0x97, 0x9b, 0x54, 0x52, 0xbb, 0xa4, 0xb3, 0x9c, 0x74, - 0x90, 0x55, 0x28, 0xf7, 0x81, 0xaa, 0xe4, 0x65, 0x1d, 0x7c, 0x02, 0xcf, 0x5a, 0x6c, 0xaa, 0xbf, - 0xc5, 0xf4, 0x1e, 0xc1, 0x60, 0x7a, 0x7f, 0x8b, 0x30, 0x85, 0x31, 0x3d, 0x88, 0x70, 0x2f, 0x08, - 0xed, 0x69, 0x4d, 0x2f, 0x07, 0xc8, 0x3d, 0x98, 0x37, 0x9d, 0x55, 0x53, 0x27, 0x9b, 0xed, 0xf3, - 0x8a, 0x4e, 0x30, 0xc8, 0x45, 0x96, 0x61, 0x3a, 0x83, 0x77, 0x36, 0xed, 0x99, 0x65, 0x6b, 0x65, - 0xdc, 0x2f, 0x42, 0xe4, 0x6d, 0x78, 0x39, 0x37, 0x63, 0x21, 0x69, 0x14, 0xe9, 0xd6, 0xdb, 0xd9, - 0xb4, 0x67, 0x75, 0xf4, 0x30, 0x37, 0x79, 0x0f, 0x2a, 0x99, 0x6b, 0x2b, 0x96, 0xc8, 0x19, 0x0f, - 0x05, 0xae, 0x53, 0x81, 0xfb, 0x3c, 0xb2, 0xaf, 0x6a, 0x52, 0x23, 0x22, 0xc8, 0x02, 0x4c, 0x30, - 0x9e, 0x7c, 0xd1, 0xb5, 0xcb, 0x3a, 0xd4, 0x18, 0xaa, 0xc7, 0x59, 0xda, 0xc6, 0x73, 0xa6, 0xc7, - 0x53, 0x93, 0x54, 0x61, 0xa1, 0x11, 0xb0, 0x47, 0xc8, 0x3b, 0x61, 0x80, 0xb5, 0x20, 0x48, 0xda, - 0xb1, 0xd6, 0x9c, 0xe8, 0xb0, 0x81, 0x3e, 0xe2, 0x02, 0xd1, 0x3d, 0xb8, 0x2d, 0x25, 0x5b, 0xa7, - 0x22, 0x0c, 0x6a, 0x6d, 0xd9, 0xb4, 0xe7, 0xb5, 0xb0, 0x03, 0x3c, 0xce, 0x2c, 0x5c, 0x51, 0x2d, - 0xda, 0xbb, 0x23, 0xce, 0x2f, 0x16, 0xcc, 0x29, 0x60, 0x83, 0x23, 0x95, 0xe8, 0xe3, 0xd3, 0x36, - 0x0a, 0x49, 0x3e, 0x29, 0x74, 0xed, 0x74, 0x75, 0xfb, 0xbf, 0x5d, 0x77, 0x3f, 0xbb, 0x75, 0x69, - 0xff, 0x5f, 0x83, 0x52, 0x9b, 0x09, 0xe4, 0x32, 0xbd, 0x45, 0xa9, 0xa5, 0x7a, 0x23, 0xe0, 0x58, - 0x17, 0x7b, 0x71, 0xd4, 0xd5, 0xcd, 0x3f, 0xe9, 0xe7, 0x80, 0xf3, 0xd4, 0x10, 0xdd, 0x67, 0xf5, - 0x8b, 0x22, 0x5a, 0xfd, 0x67, 0xd6, 0xd4, 0x34, 0x60, 0x2a, 0x3e, 0xf9, 0xc6, 0x82, 0x4b, 0xbb, - 0xa1, 0x90, 0xe4, 0xa5, 0xe2, 0x40, 0xc9, 0xc6, 0x47, 0x65, 0xf7, 0xbc, 0x58, 0xa8, 0x22, 0xce, - 0xcd, 0x2f, 0xff, 0xfc, 0xfb, 0xbb, 0xb1, 0x6b, 0x64, 0x41, 0x3f, 0x7b, 0x9d, 0xb5, 0xfc, 0x8d, - 0x09, 0x51, 0x7c, 0x35, 0x66, 0x91, 0xaf, 0x2d, 0x18, 0x7f, 0x80, 0x43, 0xd9, 0x9c, 0x9b, 0x26, - 0xce, 0x2d, 0xcd, 0xe4, 0x06, 0xb9, 0x3e, 0x88, 0x89, 0xf7, 0x4c, 0x59, 0xcf, 0xc9, 0xf7, 0x16, - 0x94, 0x15, 0x6f, 0xbf, 0xe0, 0xbb, 0x18, 0xa1, 0x16, 0x47, 0x09, 0x45, 0x3e, 0x85, 0x49, 0x43, - 0xeb, 0x70, 0x28, 0x9d, 0x72, 0x3f, 0x7c, 0x28, 0x9c, 0x15, 0x9d, 0xd2, 0x21, 0xcb, 0x23, 0x76, - 0xec, 0x71, 0x95, 0xb2, 0x65, 0xd2, 0xab, 0xe7, 0x87, 0xbc, 0x72, 0x3c, 0x7d, 0xf6, 0xfa, 0x57, - 0x16, 0x07, 0xb9, 0xb2, 0xbb, 0x78, 0xa6, 0x72, 0x54, 0x95, 0xf8, 0xd6, 0x82, 0x99, 0x07, 0x28, - 0xf3, 0x77, 0x9a, 0xdc, 0x1c, 0x90, 0xb9, 0xf8, 0x86, 0x57, 0x9c, 0xe1, 0x01, 0x19, 0x81, 0x77, - 0x34, 0x81, 0x37, 0x9d, 0x7b, 0x83, 0x09, 0x98, 0x47, 0x5a, 0xe7, 0xd9, 0xf7, 0x77, 0x35, 0x95, - 0xba, 0xc9, 0x70, 0xdf, 0x5a, 0x25, 0x1d, 0x4d, 0x69, 0x1b, 0xa3, 0xd6, 0x46, 0x93, 0x72, 0x39, - 0x54, 0xe6, 0xa5, 0x22, 0x9c, 0x87, 0x67, 0x24, 0x5c, 0x4d, 0x62, 0x85, 0xdc, 0x19, 0xa5, 0x42, - 0x13, 0xa3, 0x56, 0x60, 0xca, 0xfc, 0x60, 0x41, 0xc9, 0x4c, 0x2f, 0x72, 0xe3, 0x78, 0xc5, 0xbe, - 0xa9, 0x76, 0x8e, 0x57, 0xe1, 0x55, 0xcd, 0x71, 0xd1, 0x19, 0xd8, 0x6b, 0xf7, 0xf5, 0xf0, 0x50, - 0x57, 0xf3, 0x47, 0x0b, 0xca, 0x3d, 0x0a, 0xbd, 0x6f, 0x2f, 0x8e, 0xa4, 0x73, 0x3a, 0x49, 0xf2, - 0xb3, 0x05, 0x25, 0x33, 0x51, 0x4f, 0xf2, 0xea, 0x9b, 0xb4, 0xe7, 0xc8, 0x6b, 0xcd, 0x1c, 0x70, - 0x65, 0x44, 0x9b, 0x6b, 0x2a, 0xcf, 0x73, 0x21, 0x7f, 0xb5, 0xa0, 0xdc, 0xa3, 0x33, 0x5c, 0xc8, - 0xff, 0x8b, 0xb0, 0xfb, 0x62, 0x84, 0x09, 0x85, 0xd2, 0x26, 0x46, 0x28, 0x71, 0xd8, 0x15, 0xb0, - 0x8f, 0xc3, 0x59, 0xf3, 0xdf, 0x31, 0x33, 0x76, 0x75, 0xd4, 0x8c, 0x55, 0x82, 0x34, 0xa1, 0x6c, - 0x4a, 0x14, 0xf4, 0x78, 0xe1, 0x62, 0xb7, 0xce, 0x50, 0x8c, 0x3c, 0x83, 0xd9, 0x8f, 0x68, 0x14, - 0x2a, 0x65, 0xcd, 0xef, 0x5a, 0x72, 0xfd, 0xc4, 0x24, 0xc9, 0x7f, 0xef, 0x8e, 0xa8, 0x56, 0xd5, - 0xd5, 0xee, 0x3a, 0xb7, 0x47, 0xdd, 0xeb, 0x4e, 0x5a, 0xca, 0x28, 0xb9, 0xbe, 0xf5, 0xfb, 0xd1, - 0x92, 0xf5, 0xc7, 0xd1, 0x92, 0xf5, 0xd7, 0xd1, 0x92, 0xf5, 0xf1, 0x5b, 0x67, 0xfb, 0x87, 0x17, - 0xe8, 0x1f, 0xa6, 0x85, 0xff, 0x62, 0x07, 0x25, 0xfd, 0x67, 0xec, 0x8d, 0x7f, 0x03, 0x00, 0x00, - 0xff, 0xff, 0x52, 0xa9, 0xe9, 0x17, 0x71, 0x0e, 0x00, 0x00, + 0x4f, 0x0a, 0x8f, 0x32, 0x16, 0x85, 0x81, 0x7e, 0xd6, 0xeb, 0xac, 0xd1, 0x88, 0x35, 0xe9, 0x9a, + 0xd7, 0xc0, 0x18, 0x39, 0x95, 0x58, 0x4f, 0xa3, 0x6d, 0x9d, 0x12, 0x4d, 0xd3, 0x3a, 0x95, 0xbe, + 0xd3, 0x85, 0x19, 0x1f, 0x59, 0x52, 0x63, 0x4c, 0x7c, 0xd8, 0x46, 0xde, 0x25, 0x04, 0x2e, 0x29, + 0x27, 0xdb, 0x5a, 0xb6, 0x56, 0xa6, 0x7c, 0xbd, 0x26, 0x15, 0x98, 0xe4, 0xd8, 0x09, 0x45, 0x98, + 0xc4, 0xf6, 0x98, 0xc6, 0xb3, 0x3d, 0xb1, 0xe1, 0x32, 0x65, 0xec, 0x03, 0xda, 0x42, 0x7b, 0x5c, + 0x9b, 0x7a, 0x5b, 0xb2, 0x04, 0x40, 0x19, 0x7b, 0xc8, 0x93, 0xc7, 0x18, 0x48, 0xfb, 0x92, 0x36, + 0x16, 0x10, 0x67, 0x0d, 0x2e, 0xd7, 0x18, 0xdb, 0x89, 0x0f, 0x13, 0x95, 0x54, 0x76, 0x19, 0xf6, + 0x92, 0xaa, 0xb5, 0xc2, 0x18, 0x95, 0xcd, 0x34, 0xa1, 0x5e, 0x3b, 0xbf, 0x59, 0x30, 0x9f, 0xd2, + 0xdd, 0x44, 0x49, 0xc3, 0x28, 0x25, 0xdd, 0x80, 0x92, 0x48, 0xda, 0x3c, 0x30, 0x11, 0xa6, 0xab, + 0x7b, 0x6e, 0xae, 0x8e, 0xdb, 0x53, 0x47, 0x2f, 0x3e, 0x0b, 0xea, 0x6e, 0xa7, 0xea, 0xb2, 0x27, + 0x0d, 0x57, 0x69, 0xed, 0x16, 0xb4, 0x76, 0x7b, 0x5a, 0xbb, 0xb5, 0x1c, 0x7c, 0xa4, 0xc3, 0xfa, + 0x69, 0xf8, 0xe2, 0x69, 0xc7, 0x46, 0x9d, 0x76, 0xfc, 0xc4, 0x69, 0xdf, 0x85, 0x72, 0x4f, 0x68, + 0x1f, 0x05, 0x4b, 0x62, 0x81, 0xe4, 0x35, 0x98, 0x08, 0x25, 0xb6, 0x84, 0x6d, 0x2d, 0x8f, 0xaf, + 0x4c, 0x57, 0xe7, 0xdd, 0xc2, 0xeb, 0x49, 0xa5, 0xf1, 0x8d, 0x87, 0x13, 0xc0, 0x94, 0x7a, 0x7c, + 0xf8, 0x3b, 0x72, 0xe0, 0xca, 0x61, 0xa2, 0xa8, 0xe2, 0x21, 0x47, 0x61, 0x64, 0x9b, 0xf4, 0xfb, + 0xb0, 0x53, 0x39, 0xfe, 0x34, 0x01, 0x57, 0x35, 0xc9, 0x20, 0x40, 0x31, 0xba, 0x1e, 0xda, 0x02, + 0x79, 0x9c, 0xcb, 0x90, 0xed, 0x95, 0x8d, 0x51, 0x21, 0x3e, 0x4f, 0x78, 0x3d, 0xcd, 0x90, 0xed, + 0xc9, 0x6d, 0x98, 0x11, 0xa2, 0xf9, 0x90, 0x87, 0x1d, 0x2a, 0xf1, 0x7d, 0xec, 0xa6, 0x45, 0xd1, + 0x0f, 0xaa, 0x08, 0x61, 0x2c, 0x30, 0x68, 0x73, 0xb4, 0x27, 0xf4, 0x29, 0xb2, 0x3d, 0xb9, 0x0b, + 0x73, 0x32, 0x12, 0x1b, 0x51, 0x88, 0xb1, 0xdc, 0x40, 0x2e, 0x37, 0xa9, 0xa4, 0x76, 0x49, 0x47, + 0x39, 0x69, 0x20, 0xab, 0x50, 0xee, 0x03, 0x55, 0xca, 0xcb, 0xda, 0xf9, 0x04, 0x9e, 0x95, 0xe0, + 0x54, 0x7f, 0x09, 0xea, 0x33, 0x82, 0xc1, 0xf4, 0xf9, 0x16, 0x61, 0x0a, 0x63, 0x7a, 0x10, 0xe1, + 0x5e, 0x10, 0xda, 0xd3, 0x9a, 0x5e, 0x0e, 0x90, 0x7b, 0x30, 0x6f, 0x2a, 0xaf, 0xa6, 0x54, 0xcd, + 0xce, 0x79, 0x45, 0x07, 0x18, 0x64, 0x22, 0xcb, 0x30, 0x9d, 0xc1, 0x3b, 0x9b, 0xf6, 0xcc, 0xb2, + 0xb5, 0x32, 0xee, 0x17, 0x21, 0xf2, 0x36, 0xbc, 0x9c, 0x6f, 0x63, 0x21, 0x69, 0x14, 0xe9, 0xd2, + 0xdc, 0xd9, 0xb4, 0x67, 0xb5, 0xf7, 0x30, 0x33, 0x79, 0x0f, 0x2a, 0x99, 0x69, 0x2b, 0x96, 0xc8, + 0x19, 0x0f, 0x05, 0xae, 0x53, 0x81, 0xfb, 0x3c, 0xb2, 0xaf, 0x6a, 0x52, 0x23, 0x3c, 0xc8, 0x02, + 0x4c, 0x30, 0x9e, 0x7c, 0xd1, 0xb5, 0xcb, 0xda, 0xd5, 0x6c, 0x54, 0x0f, 0xb0, 0xb4, 0x84, 0xe6, + 0x4c, 0x0f, 0xa4, 0x5b, 0x52, 0x85, 0x85, 0x46, 0xc0, 0x1e, 0x21, 0xef, 0x84, 0x01, 0xd6, 0x82, + 0x20, 0x69, 0xc7, 0x5a, 0x73, 0xa2, 0xdd, 0x06, 0xda, 0x88, 0x0b, 0x44, 0xd7, 0xe8, 0xb6, 0x94, + 0x6c, 0x9d, 0x8a, 0x30, 0xa8, 0xb5, 0x65, 0xd3, 0x9e, 0xd7, 0xc2, 0x0e, 0xb0, 0x38, 0xb3, 0x70, + 0x45, 0x95, 0x68, 0xaf, 0x87, 0x9c, 0x5f, 0x2c, 0x98, 0x53, 0xc0, 0x06, 0x47, 0x2a, 0xd1, 0xc7, + 0xa7, 0x6d, 0x14, 0x92, 0x7c, 0x52, 0xa8, 0xda, 0xe9, 0xea, 0xf6, 0x7f, 0x1b, 0x07, 0x7e, 0xd6, + 0x95, 0x69, 0xfd, 0x5f, 0x83, 0x52, 0x9b, 0x09, 0xe4, 0x32, 0xed, 0xb2, 0x74, 0xa7, 0x6a, 0x23, + 0xe0, 0x58, 0x17, 0x7b, 0x71, 0xd4, 0xd5, 0xc5, 0x3f, 0xe9, 0xe7, 0x80, 0xf3, 0xd4, 0x10, 0xdd, + 0x67, 0xf5, 0x8b, 0x22, 0x5a, 0xfd, 0x67, 0xd6, 0xe4, 0x34, 0x60, 0x2a, 0x3e, 0xf9, 0xc6, 0x82, + 0x4b, 0xbb, 0xa1, 0x90, 0xe4, 0xa5, 0xe2, 0xc0, 0xc9, 0xc6, 0x4b, 0x65, 0xf7, 0xbc, 0x58, 0xa8, + 0x24, 0xce, 0xcd, 0x2f, 0xff, 0xfc, 0xfb, 0xbb, 0xb1, 0x6b, 0x64, 0x41, 0x5f, 0x8b, 0x9d, 0xb5, + 0xfc, 0x0e, 0x0a, 0x51, 0x7c, 0x35, 0x66, 0x91, 0xaf, 0x2d, 0x18, 0x7f, 0x80, 0x43, 0xd9, 0x9c, + 0x9b, 0x26, 0xce, 0x2d, 0xcd, 0xe4, 0x06, 0xb9, 0x3e, 0x88, 0x89, 0xf7, 0x4c, 0xed, 0x9e, 0x93, + 0xef, 0x2d, 0x28, 0x2b, 0xde, 0x7e, 0xc1, 0x76, 0x31, 0x42, 0x2d, 0x8e, 0x12, 0x8a, 0x7c, 0x0a, + 0x93, 0x86, 0xd6, 0xe1, 0x50, 0x3a, 0xe5, 0x7e, 0xf8, 0x50, 0x38, 0x2b, 0x3a, 0xa4, 0x43, 0x96, + 0x47, 0x9c, 0xd8, 0xe3, 0x2a, 0x64, 0xcb, 0x84, 0x57, 0xd7, 0x13, 0x79, 0xe5, 0x78, 0xf8, 0xec, + 0xeb, 0xa0, 0xb2, 0x38, 0xc8, 0x94, 0xf5, 0xe2, 0x99, 0xd2, 0x51, 0x95, 0xe2, 0x5b, 0x0b, 0x66, + 0x1e, 0xa0, 0xcc, 0xef, 0x71, 0x72, 0x73, 0x40, 0xe4, 0xe2, 0x1d, 0x5f, 0x71, 0x86, 0x3b, 0x64, + 0x04, 0xde, 0xd1, 0x04, 0xde, 0x74, 0xee, 0x0d, 0x26, 0x60, 0x2e, 0x71, 0x1d, 0x67, 0xdf, 0xdf, + 0xd5, 0x54, 0xea, 0x26, 0xc2, 0x7d, 0x6b, 0x95, 0x74, 0x34, 0xa5, 0x6d, 0x8c, 0x5a, 0x1b, 0x4d, + 0xca, 0xe5, 0x50, 0x99, 0x97, 0x8a, 0x70, 0xee, 0x9e, 0x91, 0x70, 0x35, 0x89, 0x15, 0x72, 0x67, + 0x94, 0x0a, 0x4d, 0x8c, 0x5a, 0x81, 0x49, 0xf3, 0x83, 0x05, 0x25, 0x33, 0xbd, 0xc8, 0x8d, 0xe3, + 0x19, 0xfb, 0xa6, 0xda, 0x39, 0xb6, 0xc2, 0xab, 0x9a, 0xe3, 0xa2, 0x33, 0xb0, 0xd6, 0xee, 0xeb, + 0xe1, 0xa1, 0x5a, 0xf3, 0x47, 0x0b, 0xca, 0x3d, 0x0a, 0xbd, 0x67, 0x2f, 0x8e, 0xa4, 0x73, 0x3a, + 0x49, 0xf2, 0xb3, 0x05, 0x25, 0x33, 0x51, 0x4f, 0xf2, 0xea, 0x9b, 0xb4, 0xe7, 0xc8, 0x6b, 0xcd, + 0xbc, 0xe0, 0xca, 0x88, 0x32, 0xd7, 0x54, 0x9e, 0xe7, 0x42, 0xfe, 0x6a, 0x41, 0xb9, 0x47, 0x67, + 0xb8, 0x90, 0xff, 0x17, 0x61, 0xf7, 0xc5, 0x08, 0x13, 0x0a, 0xa5, 0x4d, 0x8c, 0x50, 0xe2, 0xb0, + 0x16, 0xb0, 0x8f, 0xc3, 0x59, 0xf1, 0xdf, 0x31, 0x33, 0x76, 0x75, 0xd4, 0x8c, 0x55, 0x82, 0x34, + 0xa1, 0x6c, 0x52, 0x14, 0xf4, 0x78, 0xe1, 0x64, 0xb7, 0xce, 0x90, 0x8c, 0x3c, 0x83, 0xd9, 0x8f, + 0x68, 0x14, 0x2a, 0x65, 0xcd, 0x77, 0x2d, 0xb9, 0x7e, 0x62, 0x92, 0xe4, 0xdf, 0xbb, 0x23, 0xb2, + 0x55, 0x75, 0xb6, 0xbb, 0xce, 0xed, 0x51, 0x7d, 0xdd, 0x49, 0x53, 0x19, 0x25, 0xd7, 0xb7, 0x7e, + 0x3f, 0x5a, 0xb2, 0xfe, 0x38, 0x5a, 0xb2, 0xfe, 0x3a, 0x5a, 0xb2, 0x3e, 0x7e, 0xeb, 0x6c, 0x7f, + 0x80, 0x81, 0xfe, 0x30, 0x2d, 0xfc, 0xab, 0x1d, 0x94, 0xf4, 0xcf, 0xda, 0x1b, 0xff, 0x06, 0x00, + 0x00, 0xff, 0xff, 0xb4, 0x10, 0xe0, 0x77, 0x91, 0x0e, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1570,6 +1579,13 @@ func (m *RepoQuery) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if len(m.AppProject) > 0 { + i -= len(m.AppProject) + copy(dAtA[i:], m.AppProject) + i = encodeVarintRepository(dAtA, i, uint64(len(m.AppProject))) + i-- + dAtA[i] = 0x1a + } if m.ForceRefresh { i-- if m.ForceRefresh { @@ -1995,6 +2011,10 @@ func (m *RepoQuery) Size() (n int) { if m.ForceRefresh { n += 2 } + l = len(m.AppProject) + if l > 0 { + n += 1 + l + sovRepository(uint64(l)) + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -2747,6 +2767,38 @@ func (m *RepoQuery) Unmarshal(dAtA []byte) error { } } m.ForceRefresh = bool(v != 0) + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppProject", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRepository + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRepository + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthRepository + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppProject = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipRepository(dAtA[iNdEx:]) diff --git a/pkg/apis/application/v1alpha1/generated.proto b/pkg/apis/application/v1alpha1/generated.proto index a1f5694ea3371..f1d958b6db1d4 100644 --- a/pkg/apis/application/v1alpha1/generated.proto +++ b/pkg/apis/application/v1alpha1/generated.proto @@ -1513,7 +1513,7 @@ message RefTarget { // RepoCreds holds the definition for repository credentials message RepoCreds { - // URL is the URL that this credentials matches to + // URL is the URL to which these credentials match optional string url = 1; // Username for authenticating at the repo server @@ -1626,7 +1626,7 @@ message Repository { // Proxy specifies the HTTP/HTTPS proxy used to access the repo optional string proxy = 19; - // Reference between project and repository that allow you automatically to be added as item inside SourceRepos project entity + // Reference between project and repository that allows it to be automatically added as an item inside SourceRepos project entity optional string project = 20; // GCPServiceAccountKey specifies the service account key in JSON format to be used for getting credentials to Google Cloud Source repos diff --git a/pkg/apis/application/v1alpha1/openapi_generated.go b/pkg/apis/application/v1alpha1/openapi_generated.go index 6c8e901048886..28755d785d364 100644 --- a/pkg/apis/application/v1alpha1/openapi_generated.go +++ b/pkg/apis/application/v1alpha1/openapi_generated.go @@ -5436,7 +5436,7 @@ func schema_pkg_apis_application_v1alpha1_RepoCreds(ref common.ReferenceCallback Properties: map[string]spec.Schema{ "url": { SchemaProps: spec.SchemaProps{ - Description: "URL is the URL that this credentials matches to", + Description: "URL is the URL to which these credentials match", Default: "", Type: []string{"string"}, Format: "", @@ -5725,7 +5725,7 @@ func schema_pkg_apis_application_v1alpha1_Repository(ref common.ReferenceCallbac }, "project": { SchemaProps: spec.SchemaProps{ - Description: "Reference between project and repository that allow you automatically to be added as item inside SourceRepos project entity", + Description: "Reference between project and repository that allows it to be automatically added as an item inside SourceRepos project entity", Type: []string{"string"}, Format: "", }, diff --git a/pkg/apis/application/v1alpha1/repository_types.go b/pkg/apis/application/v1alpha1/repository_types.go index 3a557813d87c6..665c1f3c2afc9 100644 --- a/pkg/apis/application/v1alpha1/repository_types.go +++ b/pkg/apis/application/v1alpha1/repository_types.go @@ -14,7 +14,7 @@ import ( // RepoCreds holds the definition for repository credentials type RepoCreds struct { - // URL is the URL that this credentials matches to + // URL is the URL to which these credentials match URL string `json:"url" protobuf:"bytes,1,opt,name=url"` // Username for authenticating at the repo server Username string `json:"username,omitempty" protobuf:"bytes,2,opt,name=username"` @@ -87,7 +87,7 @@ type Repository struct { GitHubAppEnterpriseBaseURL string `json:"githubAppEnterpriseBaseUrl,omitempty" protobuf:"bytes,18,opt,name=githubAppEnterpriseBaseUrl"` // Proxy specifies the HTTP/HTTPS proxy used to access the repo Proxy string `json:"proxy,omitempty" protobuf:"bytes,19,opt,name=proxy"` - // Reference between project and repository that allow you automatically to be added as item inside SourceRepos project entity + // Reference between project and repository that allows it to be automatically added as an item inside SourceRepos project entity Project string `json:"project,omitempty" protobuf:"bytes,20,opt,name=project"` // GCPServiceAccountKey specifies the service account key in JSON format to be used for getting credentials to Google Cloud Source repos GCPServiceAccountKey string `json:"gcpServiceAccountKey,omitempty" protobuf:"bytes,21,opt,name=gcpServiceAccountKey"` diff --git a/reposerver/repository/repository.go b/reposerver/repository/repository.go index ac738cd513d19..ac6c6cd6d6c7e 100644 --- a/reposerver/repository/repository.go +++ b/reposerver/repository/repository.go @@ -344,7 +344,7 @@ func (s *Service) runRepoOperation( if source.IsHelm() { if settings.noCache { - err = helmClient.CleanChartCache(source.Chart, revision) + err = helmClient.CleanChartCache(source.Chart, revision, repo.Project) if err != nil { return err } @@ -353,7 +353,7 @@ func (s *Service) runRepoOperation( if source.Helm != nil { helmPassCredentials = source.Helm.PassCredentials } - chartPath, closer, err := helmClient.ExtractChart(source.Chart, revision, helmPassCredentials, s.initConstants.HelmManifestMaxExtractedSize, s.initConstants.DisableHelmManifestMaxExtractedSize) + chartPath, closer, err := helmClient.ExtractChart(source.Chart, revision, repo.Project, helmPassCredentials, s.initConstants.HelmManifestMaxExtractedSize, s.initConstants.DisableHelmManifestMaxExtractedSize) if err != nil { return err } @@ -1272,12 +1272,12 @@ func getResolvedValueFiles( referencedSource := getReferencedSource(rawValueFile, refSources) if referencedSource != nil { // If the $-prefixed path appears to reference another source, do env substitution _after_ resolving that source. - resolvedPath, err = getResolvedRefValueFile(rawValueFile, env, allowedValueFilesSchemas, referencedSource.Repo.Repo, gitRepoPaths) + resolvedPath, err = getResolvedRefValueFile(rawValueFile, env, allowedValueFilesSchemas, referencedSource.Repo.Repo, gitRepoPaths, referencedSource.Repo.Project) if err != nil { return nil, fmt.Errorf("error resolving value file path: %w", err) } } else { - // This will resolve val to an absolute path (or an URL) + // This will resolve val to an absolute path (or a URL) resolvedPath, isRemote, err = pathutil.ResolveValueFilePathOrUrl(appPath, repoRoot, env.Envsubst(rawValueFile), allowedValueFilesSchemas) if err != nil { return nil, fmt.Errorf("error resolving value file path: %w", err) @@ -1305,9 +1305,15 @@ func getResolvedRefValueFile( allowedValueFilesSchemas []string, refSourceRepo string, gitRepoPaths io.TempPaths, + project string, ) (pathutil.ResolvedFilePath, error) { pathStrings := strings.Split(rawValueFile, "/") - repoPath := gitRepoPaths.GetPathIfExists(git.NormalizeGitURL(refSourceRepo)) + + keyData, err := json.Marshal(map[string]string{"url": git.NormalizeGitURL(refSourceRepo), "project": project}) + if err != nil { + return "", err + } + repoPath := gitRepoPaths.GetPathIfExists(string(keyData)) if repoPath == "" { return "", fmt.Errorf("failed to find repo %q", refSourceRepo) } @@ -2301,7 +2307,7 @@ func (s *Service) GetRevisionChartDetails(ctx context.Context, q *apiclient.Repo if err != nil { return nil, fmt.Errorf("helm client error: %v", err) } - chartPath, closer, err := helmClient.ExtractChart(q.Name, revision, false, s.initConstants.HelmManifestMaxExtractedSize, s.initConstants.DisableHelmManifestMaxExtractedSize) + chartPath, closer, err := helmClient.ExtractChart(q.Name, revision, q.Repo.Project, false, s.initConstants.HelmManifestMaxExtractedSize, s.initConstants.DisableHelmManifestMaxExtractedSize) if err != nil { return nil, fmt.Errorf("error extracting chart: %v", err) } @@ -2331,7 +2337,11 @@ func fileParameters(q *apiclient.RepoServerAppDetailsQuery) []v1alpha1.HelmFileP } func (s *Service) newClient(repo *v1alpha1.Repository, opts ...git.ClientOpts) (git.Client, error) { - repoPath, err := s.gitRepoPaths.GetPath(git.NormalizeGitURL(repo.Repo)) + keyData, err := json.Marshal(map[string]string{"url": git.NormalizeGitURL(repo.Repo), "project": repo.Project}) + if err != nil { + return nil, err + } + repoPath, err := s.gitRepoPaths.GetPath(string(keyData)) if err != nil { return nil, err } diff --git a/reposerver/repository/repository_test.go b/reposerver/repository/repository_test.go index 7f66a1203203e..8396577e3ba33 100644 --- a/reposerver/repository/repository_test.go +++ b/reposerver/repository/repository_test.go @@ -40,7 +40,6 @@ import ( "github.com/argoproj/argo-cd/v2/reposerver/metrics" fileutil "github.com/argoproj/argo-cd/v2/test/fixture/path" "github.com/argoproj/argo-cd/v2/util/argo" - dbmocks "github.com/argoproj/argo-cd/v2/util/db/mocks" "github.com/argoproj/argo-cd/v2/util/git" gitmocks "github.com/argoproj/argo-cd/v2/util/git/mocks" "github.com/argoproj/argo-cd/v2/util/helm" @@ -123,10 +122,10 @@ func newServiceWithMocks(t *testing.T, root string, signed bool) (*Service, *git chart: {{Version: "1.0.0"}, {Version: version}}, oobChart: {{Version: "1.0.0"}, {Version: version}}, }}, nil) - helmClient.On("ExtractChart", chart, version, false, int64(0), false).Return("./testdata/my-chart", io.NopCloser, nil) - helmClient.On("ExtractChart", oobChart, version, false, int64(0), false).Return("./testdata2/out-of-bounds-chart", io.NopCloser, nil) - helmClient.On("CleanChartCache", chart, version).Return(nil) - helmClient.On("CleanChartCache", oobChart, version).Return(nil) + helmClient.On("ExtractChart", chart, version, "", false, int64(0), false).Return("./testdata/my-chart", io.NopCloser, nil) + helmClient.On("ExtractChart", oobChart, version, "", false, int64(0), false).Return("./testdata2/out-of-bounds-chart", io.NopCloser, nil) + helmClient.On("CleanChartCache", chart, version, "").Return(nil) + helmClient.On("CleanChartCache", oobChart, version, "").Return(nil) helmClient.On("DependencyBuild").Return(nil) paths.On("Add", mock.Anything, mock.Anything).Return(root, nil) @@ -499,11 +498,11 @@ func TestHelmChartReferencingExternalValues(t *testing.T) { {Ref: "ref", RepoURL: "https://git.example.com/test/repo"}, }, } - repoDB := &dbmocks.ArgoDB{} - repoDB.On("GetRepository", context.Background(), "https://git.example.com/test/repo").Return(&argoappv1.Repository{ - Repo: "https://git.example.com/test/repo", - }, nil) - refSources, err := argo.GetRefSources(context.Background(), spec, repoDB) + refSources, err := argo.GetRefSources(context.Background(), spec, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { + return &argoappv1.Repository{ + Repo: "https://git.example.com/test/repo", + }, nil + }) require.NoError(t, err) request := &apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &spec.Sources[0], NoCache: true, RefSources: refSources, HasMultipleSources: true, ProjectName: "something", ProjectSourceRepos: []string{"*"}} @@ -529,15 +528,16 @@ func TestHelmChartReferencingExternalValues_InvalidRefs(t *testing.T) { }, } - repoDB := &dbmocks.ArgoDB{} - repoDB.On("GetRepository", context.Background(), "https://git.example.com/test/repo").Return(&argoappv1.Repository{ - Repo: "https://git.example.com/test/repo", - }, nil) - // Empty refsource service := newService(t, ".") - refSources, err := argo.GetRefSources(context.Background(), spec, repoDB) + getRepository := func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { + return &argoappv1.Repository{ + Repo: "https://git.example.com/test/repo", + }, nil + } + + refSources, err := argo.GetRefSources(context.Background(), spec, getRepository) require.NoError(t, err) request := &apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &spec.Sources[0], NoCache: true, RefSources: refSources, HasMultipleSources: true, ProjectName: "something", @@ -550,7 +550,7 @@ func TestHelmChartReferencingExternalValues_InvalidRefs(t *testing.T) { service = newService(t, ".") spec.Sources[1].Ref = "Invalid" - refSources, err = argo.GetRefSources(context.Background(), spec, repoDB) + refSources, err = argo.GetRefSources(context.Background(), spec, getRepository) require.NoError(t, err) request = &apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &spec.Sources[0], NoCache: true, RefSources: refSources, HasMultipleSources: true, ProjectName: "something", @@ -564,7 +564,7 @@ func TestHelmChartReferencingExternalValues_InvalidRefs(t *testing.T) { spec.Sources[1].Ref = "ref" spec.Sources[1].Chart = "helm-chart" - refSources, err = argo.GetRefSources(context.Background(), spec, repoDB) + refSources, err = argo.GetRefSources(context.Background(), spec, getRepository) require.NoError(t, err) request = &apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &spec.Sources[0], NoCache: true, RefSources: refSources, HasMultipleSources: true, ProjectName: "something", @@ -582,7 +582,7 @@ func TestHelmChartReferencingExternalValues_OutOfBounds_Symlink(t *testing.T) { err = os.RemoveAll("testdata/oob-symlink") require.NoError(t, err) }) - // Create a symlink to a file outside of the repo + // Create a symlink to a file outside the repo err = os.Symlink("../../../values.yaml", "./testdata/oob-symlink/oob-symlink.yaml") // Create a regular file to reference from another source err = os.WriteFile("./testdata/oob-symlink/values.yaml", []byte("foo: bar"), 0644) @@ -597,11 +597,11 @@ func TestHelmChartReferencingExternalValues_OutOfBounds_Symlink(t *testing.T) { {Ref: "ref", RepoURL: "https://git.example.com/test/repo"}, }, } - repoDB := &dbmocks.ArgoDB{} - repoDB.On("GetRepository", context.Background(), "https://git.example.com/test/repo").Return(&argoappv1.Repository{ - Repo: "https://git.example.com/test/repo", - }, nil) - refSources, err := argo.GetRefSources(context.Background(), spec, repoDB) + refSources, err := argo.GetRefSources(context.Background(), spec, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { + return &argoappv1.Repository{ + Repo: "https://git.example.com/test/repo", + }, nil + }) require.NoError(t, err) request := &apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &spec.Sources[0], NoCache: true, RefSources: refSources, HasMultipleSources: true} _, err = service.GenerateManifest(context.Background(), request) @@ -3099,7 +3099,9 @@ func TestGetHelmRepo_NamedReposAlias(t *testing.T) { func Test_getResolvedValueFiles(t *testing.T) { tempDir := t.TempDir() paths := io.NewRandomizedTempPaths(tempDir) - paths.Add(git.NormalizeGitURL("https://github.com/org/repo1"), path.Join(tempDir, "repo1")) + + key, _ := json.Marshal(map[string]string{"url": git.NormalizeGitURL("https://github.com/org/repo1"), "project": ""}) + paths.Add(string(key), path.Join(tempDir, "repo1")) testCases := []struct { name string diff --git a/server/application/application.go b/server/application/application.go index c1510a0debd12..591e4d4e1a9f4 100644 --- a/server/application/application.go +++ b/server/application/application.go @@ -488,13 +488,13 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan } // Store the map of all sources having ref field into a map for applications with sources field - refSources, err := argo.GetRefSources(context.Background(), *appSpec, s.db) + refSources, err := argo.GetRefSources(context.Background(), *appSpec, s.db.GetRepository) if err != nil { return fmt.Errorf("failed to get ref sources: %v", err) } for _, source := range sources { - repo, err := s.db.GetRepository(ctx, source.RepoURL) + repo, err := s.db.GetRepository(ctx, source.RepoURL, proj.Name) if err != nil { return fmt.Errorf("error getting repository: %w", err) } @@ -615,7 +615,7 @@ func (s *Server) GetManifestsWithFiles(stream application.ApplicationService_Get return fmt.Errorf("error getting app project: %w", err) } - repo, err := s.db.GetRepository(ctx, a.Spec.GetSource().RepoURL) + repo, err := s.db.GetRepository(ctx, a.Spec.GetSource().RepoURL, proj.Name) if err != nil { return fmt.Errorf("error getting repository: %w", err) } @@ -749,7 +749,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app enabledSourceTypes map[string]bool, ) error { source := app.Spec.GetSource() - repo, err := s.db.GetRepository(ctx, a.Spec.GetSource().RepoURL) + repo, err := s.db.GetRepository(ctx, a.Spec.GetSource().RepoURL, proj.Name) if err != nil { return fmt.Errorf("error getting repository: %w", err) } @@ -1499,7 +1499,7 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe } source := a.Spec.GetSource() - repo, err := s.db.GetRepository(ctx, source.RepoURL) + repo, err := s.db.GetRepository(ctx, source.RepoURL, proj.Name) if err != nil { return nil, fmt.Errorf("error getting repository by URL: %w", err) } @@ -1524,7 +1524,7 @@ func (s *Server) RevisionChartDetails(ctx context.Context, q *application.Revisi if a.Spec.Source.Chart == "" { return nil, fmt.Errorf("no chart found for application: %v", a.QualifiedName()) } - repo, err := s.db.GetRepository(ctx, a.Spec.Source.RepoURL) + repo, err := s.db.GetRepository(ctx, a.Spec.Source.RepoURL, a.Spec.Project) if err != nil { return nil, fmt.Errorf("error getting repository by URL: %w", err) } @@ -2151,7 +2151,7 @@ func (s *Server) resolveRevision(ctx context.Context, app *appv1.Application, sy repoUrl = app.Spec.Sources[sourceIndex].RepoURL } - repo, err := s.db.GetRepository(ctx, repoUrl) + repo, err := s.db.GetRepository(ctx, repoUrl, app.Spec.Project) if err != nil { return "", "", fmt.Errorf("error getting repository by URL: %w", err) } diff --git a/server/cache/cache.go b/server/cache/cache.go index c2042c3f0e8d1..1fd326c596c56 100644 --- a/server/cache/cache.go +++ b/server/cache/cache.go @@ -65,17 +65,17 @@ func (c *Cache) GetAppManagedResources(appName string, res *[]*appv1.ResourceDif return c.cache.GetAppManagedResources(appName, res) } -func (c *Cache) SetRepoConnectionState(repo string, state *appv1.ConnectionState) error { - return c.cache.SetItem(repoConnectionStateKey(repo), &state, c.connectionStatusCacheExpiration, state == nil) +func (c *Cache) SetRepoConnectionState(repo string, project string, state *appv1.ConnectionState) error { + return c.cache.SetItem(repoConnectionStateKey(repo, project), &state, c.connectionStatusCacheExpiration, state == nil) } -func repoConnectionStateKey(repo string) string { - return fmt.Sprintf("repo|%s|connection-state", repo) +func repoConnectionStateKey(repo string, project string) string { + return fmt.Sprintf("repo|%s|%s|connection-state", repo, project) } -func (c *Cache) GetRepoConnectionState(repo string) (appv1.ConnectionState, error) { +func (c *Cache) GetRepoConnectionState(repo string, project string) (appv1.ConnectionState, error) { res := appv1.ConnectionState{} - err := c.cache.GetItem(repoConnectionStateKey(repo), &res) + err := c.cache.GetItem(repoConnectionStateKey(repo, project), &res) return res, err } diff --git a/server/cache/cache_test.go b/server/cache/cache_test.go index 6e173035aa33a..9fe90a285ff6c 100644 --- a/server/cache/cache_test.go +++ b/server/cache/cache_test.go @@ -31,18 +31,28 @@ func newFixtures() *fixtures { func TestCache_GetRepoConnectionState(t *testing.T) { cache := newFixtures().Cache // cache miss - _, err := cache.GetRepoConnectionState("my-repo") + _, err := cache.GetRepoConnectionState("my-repo", "") assert.Equal(t, ErrCacheMiss, err) // populate cache - err = cache.SetRepoConnectionState("my-repo", &ConnectionState{Status: "my-state"}) + err = cache.SetRepoConnectionState("my-repo", "", &ConnectionState{Status: "my-state"}) assert.NoError(t, err) // cache miss - _, err = cache.GetRepoConnectionState("other-repo") + _, err = cache.GetRepoConnectionState("my-repo", "some-project") + assert.Equal(t, ErrCacheMiss, err) + // populate cache + err = cache.SetRepoConnectionState("my-repo", "some-project", &ConnectionState{Status: "my-project-state"}) + assert.NoError(t, err) + // cache miss + _, err = cache.GetRepoConnectionState("other-repo", "") assert.Equal(t, ErrCacheMiss, err) // cache hit - value, err := cache.GetRepoConnectionState("my-repo") + value, err := cache.GetRepoConnectionState("my-repo", "") assert.NoError(t, err) assert.Equal(t, ConnectionState{Status: "my-state"}, value) + // cache hit + value, err = cache.GetRepoConnectionState("my-repo", "some-project") + assert.NoError(t, err) + assert.Equal(t, ConnectionState{Status: "my-project-state"}, value) } func TestAddCacheFlagsToCmd(t *testing.T) { diff --git a/server/repository/repository.go b/server/repository/repository.go index 417a41ee306ef..f9297fca2ff3f 100644 --- a/server/repository/repository.go +++ b/server/repository/repository.go @@ -3,6 +3,7 @@ package repository import ( "context" "fmt" + "github.com/argoproj/argo-cd/v2/util/git" "reflect" "github.com/argoproj/gitops-engine/pkg/utils/kube" @@ -69,8 +70,8 @@ var ( errPermissionDenied = status.Error(codes.PermissionDenied, "permission denied") ) -func (s *Server) getRepo(ctx context.Context, url string) (*appsv1.Repository, error) { - repo, err := s.db.GetRepository(ctx, url) +func (s *Server) getRepo(ctx context.Context, url, project string) (*appsv1.Repository, error) { + repo, err := s.db.GetRepository(ctx, url, project) if err != nil { return nil, errPermissionDenied } @@ -87,9 +88,9 @@ func createRBACObject(project string, repo string) string { // Get the connection state for a given repository URL by connecting to the // repo and evaluate the results. Unless forceRefresh is set to true, the // result may be retrieved out of the cache. -func (s *Server) getConnectionState(ctx context.Context, url string, forceRefresh bool) appsv1.ConnectionState { +func (s *Server) getConnectionState(ctx context.Context, url string, project string, forceRefresh bool) appsv1.ConnectionState { if !forceRefresh { - if connectionState, err := s.cache.GetRepoConnectionState(url); err == nil { + if connectionState, err := s.cache.GetRepoConnectionState(url, project); err == nil { return connectionState } } @@ -99,7 +100,7 @@ func (s *Server) getConnectionState(ctx context.Context, url string, forceRefres ModifiedAt: &now, } var err error - repo, err := s.db.GetRepository(ctx, url) + repo, err := s.db.GetRepository(ctx, url, project) if err == nil { err = s.testRepo(ctx, repo) } @@ -112,7 +113,7 @@ func (s *Server) getConnectionState(ctx context.Context, url string, forceRefres connectionState.Message = fmt.Sprintf("Unable to connect to repository: %v", err) } } - err = s.cache.SetRepoConnectionState(url, &connectionState) + err = s.cache.SetRepoConnectionState(url, project, &connectionState) if err != nil { log.Warnf("getConnectionState cache set error %s: %v", url, err) } @@ -127,7 +128,7 @@ func (s *Server) List(ctx context.Context, q *repositorypkg.RepoQuery) (*appsv1. // Get return the requested configured repository by URL and the state of its connections. func (s *Server) Get(ctx context.Context, q *repositorypkg.RepoQuery) (*appsv1.Repository, error) { - repo, err := s.getRepo(ctx, q.Repo) + repo, err := getRepository(ctx, s.ListRepositories, q) if err != nil { return nil, err } @@ -137,7 +138,7 @@ func (s *Server) Get(ctx context.Context, q *repositorypkg.RepoQuery) (*appsv1.R } // getRepo does not return an error for unconfigured repositories, so we are checking here - exists, err := s.db.RepositoryExists(ctx, q.Repo) + exists, err := s.db.RepositoryExists(ctx, q.Repo, repo.Project) if err != nil { return nil, err } @@ -166,7 +167,7 @@ func (s *Server) Get(ctx context.Context, q *repositorypkg.RepoQuery) (*appsv1.R InheritedCreds: repo.InheritedCreds, } - item.ConnectionState = s.getConnectionState(ctx, item.Repo, q.ForceRefresh) + item.ConnectionState = s.getConnectionState(ctx, item.Repo, item.Project, q.ForceRefresh) return &item, nil } @@ -202,7 +203,7 @@ func (s *Server) ListRepositories(ctx context.Context, q *repositorypkg.RepoQuer } } err = kube.RunAllAsync(len(items), func(i int) error { - items[i].ConnectionState = s.getConnectionState(ctx, items[i].Repo, q.ForceRefresh) + items[i].ConnectionState = s.getConnectionState(ctx, items[i].Repo, items[i].Project, q.ForceRefresh) return nil }) if err != nil { @@ -212,7 +213,7 @@ func (s *Server) ListRepositories(ctx context.Context, q *repositorypkg.RepoQuer } func (s *Server) ListRefs(ctx context.Context, q *repositorypkg.RepoQuery) (*apiclient.Refs, error) { - repo, err := s.getRepo(ctx, q.Repo) + repo, err := s.getRepo(ctx, q.Repo, q.GetAppProject()) if err != nil { return nil, err } @@ -235,7 +236,7 @@ func (s *Server) ListRefs(ctx context.Context, q *repositorypkg.RepoQuery) (*api // ListApps performs discovery of a git repository for potential sources of applications. Used // as a convenience to the UI for auto-complete. func (s *Server) ListApps(ctx context.Context, q *repositorypkg.RepoAppsQuery) (*repositorypkg.RepoAppsResponse, error) { - repo, err := s.getRepo(ctx, q.Repo) + repo, err := s.getRepo(ctx, q.Repo, q.GetAppProject()) if err != nil { return nil, err } @@ -286,7 +287,7 @@ func (s *Server) GetAppDetails(ctx context.Context, q *repositorypkg.RepoAppDeta if q.Source == nil { return nil, status.Errorf(codes.InvalidArgument, "missing payload in request") } - repo, err := s.getRepo(ctx, q.Source.RepoURL) + repo, err := s.getRepo(ctx, q.Source.RepoURL, q.GetAppProject()) if err != nil { return nil, err } @@ -355,7 +356,7 @@ func (s *Server) GetAppDetails(ctx context.Context, q *repositorypkg.RepoAppDeta // GetHelmCharts returns list of helm charts in the specified repository func (s *Server) GetHelmCharts(ctx context.Context, q *repositorypkg.RepoQuery) (*apiclient.HelmChartsResponse, error) { - repo, err := s.getRepo(ctx, q.Repo) + repo, err := s.getRepo(ctx, q.Repo, q.GetAppProject()) if err != nil { return nil, err } @@ -411,7 +412,7 @@ func (s *Server) CreateRepository(ctx context.Context, q *repositorypkg.RepoCrea repo, err = s.db.CreateRepository(ctx, r) if status.Convert(err).Code() == codes.AlreadyExists { // act idempotent if existing spec matches new spec - existing, getErr := s.db.GetRepository(ctx, r.Repo) + existing, getErr := s.db.GetRepository(ctx, r.Repo, q.Repo.Project) if getErr != nil { return nil, status.Errorf(codes.Internal, "unable to check existing repository details: %v", getErr) } @@ -446,7 +447,7 @@ func (s *Server) UpdateRepository(ctx context.Context, q *repositorypkg.RepoUpda return nil, status.Errorf(codes.InvalidArgument, "missing payload in request") } - repo, err := s.getRepo(ctx, q.Repo.Repo) + repo, err := s.getRepo(ctx, q.Repo.Repo, q.Repo.Project) if err != nil { return nil, err } @@ -471,7 +472,7 @@ func (s *Server) Delete(ctx context.Context, q *repositorypkg.RepoQuery) (*repos // DeleteRepository removes a repository from the configuration func (s *Server) DeleteRepository(ctx context.Context, q *repositorypkg.RepoQuery) (*repositorypkg.RepoResponse, error) { - repo, err := s.getRepo(ctx, q.Repo) + repo, err := getRepository(ctx, s.ListRepositories, q) if err != nil { return nil, err } @@ -481,14 +482,53 @@ func (s *Server) DeleteRepository(ctx context.Context, q *repositorypkg.RepoQuer } // invalidate cache - if err := s.cache.SetRepoConnectionState(q.Repo, nil); err == nil { + if err := s.cache.SetRepoConnectionState(repo.Repo, repo.Project, nil); err == nil { log.Errorf("error invalidating cache: %v", err) } - err = s.db.DeleteRepository(ctx, q.Repo) + err = s.db.DeleteRepository(ctx, repo.Repo, repo.Project) return &repositorypkg.RepoResponse{}, err } +// getRepository 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 getRepository(ctx context.Context, listRepositories func(context.Context, *repositorypkg.RepoQuery) (*v1alpha1.RepositoryList, error), q *repositorypkg.RepoQuery) (*appsv1.Repository, error) { + repositories, err := listRepositories(ctx, q) + if err != nil { + return nil, err + } + + 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 // given URL and credentials. func (s *Server) ValidateAccess(ctx context.Context, q *repositorypkg.RepoAccessQuery) (*repositorypkg.RepoResponse, error) { diff --git a/server/repository/repository.proto b/server/repository/repository.proto index 6466967702e85..eebf09ae75ce0 100644 --- a/server/repository/repository.proto +++ b/server/repository/repository.proto @@ -43,6 +43,8 @@ message RepoQuery { string repo = 1; // Whether to force a cache refresh on repo's connection state bool forceRefresh = 2; + // App project for query + string appProject = 3; } // RepoAccessQuery is a query for checking access to a repo diff --git a/server/repository/repository_test.go b/server/repository/repository_test.go index 0107506ef681e..4fe4b62f1a4db 100644 --- a/server/repository/repository_test.go +++ b/server/repository/repository_test.go @@ -264,7 +264,7 @@ func TestRepositoryServer(t *testing.T) { s := NewServer(&repoServerClientset, argoDB, enforcer, nil, appLister, projInformer, testNamespace, settingsMgr) url := "https://test" - repo, _ := s.getRepo(context.TODO(), url) + repo, _ := s.getRepo(context.TODO(), url, "") assert.Equal(t, repo.Repo, url) }) @@ -288,8 +288,9 @@ func TestRepositoryServer(t *testing.T) { url := "https://test" db := &dbmocks.ArgoDB{} - db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil) - db.On("RepositoryExists", context.TODO(), url).Return(true, nil) + db.On("ListRepositories", context.TODO()).Return([]*appsv1.Repository{{Repo: url}}, nil) + db.On("GetRepository", context.TODO(), url, "").Return(&appsv1.Repository{Repo: url}, nil) + db.On("RepositoryExists", context.TODO(), url, "").Return(true, nil) s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projInformer, testNamespace, settingsMgr) repo, err := s.Get(context.TODO(), &repository.RepoQuery{ @@ -312,8 +313,9 @@ func TestRepositoryServer(t *testing.T) { Username: "foo", InheritedCreds: true, } - db.On("GetRepository", context.TODO(), url).Return(testRepo, nil) - db.On("RepositoryExists", context.TODO(), url).Return(true, nil) + db.On("ListRepositories", context.TODO()).Return([]*appsv1.Repository{testRepo}, nil) + db.On("GetRepository", context.TODO(), url, "").Return(testRepo, nil) + db.On("RepositoryExists", context.TODO(), url, "").Return(true, nil) s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projInformer, testNamespace, settingsMgr) repo, err := s.Get(context.TODO(), &repository.RepoQuery{ @@ -332,8 +334,9 @@ func TestRepositoryServer(t *testing.T) { url := "https://test" db := &dbmocks.ArgoDB{} - db.On("GetRepository", context.TODO(), url).Return(nil, errors.New("some error")) - db.On("RepositoryExists", context.TODO(), url).Return(true, nil) + db.On("ListRepositories", context.TODO()).Return(nil, nil) + db.On("GetRepository", context.TODO(), url, "").Return(nil, errors.New("some error")) + db.On("RepositoryExists", context.TODO(), url, "").Return(true, nil) s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projInformer, testNamespace, settingsMgr) repo, err := s.Get(context.TODO(), &repository.RepoQuery{ @@ -346,11 +349,13 @@ func TestRepositoryServer(t *testing.T) { t.Run("Test_GetWithNotExistRepoShouldReturn404", func(t *testing.T) { repoServerClient := mocks.RepoServerServiceClient{} repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient} + repoServerClient.On("TestRepository", mock.Anything, mock.Anything).Return(&apiclient.TestRepositoryResponse{}, nil) url := "https://test" db := &dbmocks.ArgoDB{} - db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil) - db.On("RepositoryExists", context.TODO(), url).Return(false, nil) + db.On("ListRepositories", context.TODO()).Return([]*appsv1.Repository{{Repo: url}}, nil) + db.On("GetRepository", context.TODO(), url, "").Return(&appsv1.Repository{Repo: url}, nil) + db.On("RepositoryExists", context.TODO(), url, "").Return(false, nil) s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projInformer, testNamespace, settingsMgr) repo, err := s.Get(context.TODO(), &repository.RepoQuery{ @@ -389,7 +394,7 @@ func TestRepositoryServer(t *testing.T) { repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient} db := &dbmocks.ArgoDB{} - db.On("GetRepository", context.TODO(), "test").Return(&appsv1.Repository{ + db.On("GetRepository", context.TODO(), "test", "").Return(&appsv1.Repository{ Repo: "test", Username: "test", }, nil) @@ -417,7 +422,7 @@ func TestRepositoryServer(t *testing.T) { url := "https://test" db := &dbmocks.ArgoDB{} - db.On("GetRepository", context.TODO(), url).Return(nil, nil) + db.On("GetRepository", context.TODO(), url, "argocd").Return(nil, nil) db.On("ListHelmRepositories", context.TODO(), mock.Anything).Return(nil, nil) db.On("ListRepositories", context.TODO()).Return([]*appsv1.Repository{&fakeRepo, &fakeRepo}, nil) @@ -440,7 +445,7 @@ func TestRepositoryServerListApps(t *testing.T) { url := "https://test" db := &dbmocks.ArgoDB{} - db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil) + db.On("GetRepository", context.TODO(), url, "default").Return(&appsv1.Repository{Repo: url}, nil) appLister, projLister := newAppAndProjLister(defaultProj) s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, testNamespace, settingsMgr) @@ -463,7 +468,7 @@ func TestRepositoryServerListApps(t *testing.T) { url := "https://test" db := &dbmocks.ArgoDB{} - db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil) + db.On("GetRepository", context.TODO(), url, "default").Return(&appsv1.Repository{Repo: url}, nil) db.On("GetProjectRepositories", context.TODO(), "default").Return(nil, nil) db.On("GetProjectClusters", context.TODO(), "default").Return(nil, nil) repoServerClient.On("ListApps", context.TODO(), mock.Anything).Return(&apiclient.AppList{ @@ -494,7 +499,7 @@ func TestRepositoryServerListApps(t *testing.T) { url := "https://test" db := &dbmocks.ArgoDB{} - db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil) + db.On("GetRepository", context.TODO(), url, "default").Return(&appsv1.Repository{Repo: url}, nil) db.On("GetProjectRepositories", context.TODO(), "default").Return(nil, nil) db.On("GetProjectClusters", context.TODO(), "default").Return(nil, nil) repoServerClient.On("ListApps", context.TODO(), mock.Anything).Return(&apiclient.AppList{ @@ -527,7 +532,7 @@ func TestRepositoryServerGetAppDetails(t *testing.T) { url := "https://test" db := &dbmocks.ArgoDB{} - db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil) + db.On("GetRepository", context.TODO(), url, "default").Return(&appsv1.Repository{Repo: url}, nil) appLister, projLister := newAppAndProjLister(defaultProj) s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, testNamespace, settingsMgr) @@ -550,7 +555,7 @@ func TestRepositoryServerGetAppDetails(t *testing.T) { url := "https://test" db := &dbmocks.ArgoDB{} - db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil) + db.On("GetRepository", context.TODO(), url, "default").Return(&appsv1.Repository{Repo: url}, nil) appLister, projLister := newAppAndProjLister(defaultProj) s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, testNamespace, settingsMgr) @@ -572,7 +577,7 @@ func TestRepositoryServerGetAppDetails(t *testing.T) { url := "https://test" db := &dbmocks.ArgoDB{} - db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil) + db.On("GetRepository", context.TODO(), url, "default").Return(&appsv1.Repository{Repo: url}, nil) appLister, projLister := newAppAndProjLister(defaultProj) s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, testNamespace, settingsMgr) @@ -594,7 +599,7 @@ func TestRepositoryServerGetAppDetails(t *testing.T) { url := "https://test" db := &dbmocks.ArgoDB{} db.On("ListHelmRepositories", context.TODO(), mock.Anything).Return(nil, nil) - db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil) + db.On("GetRepository", context.TODO(), url, "default").Return(&appsv1.Repository{Repo: url}, nil) db.On("GetProjectRepositories", context.TODO(), "default").Return(nil, nil) db.On("GetProjectClusters", context.TODO(), "default").Return(nil, nil) expectedResp := apiclient.RepoAppDetailsResponse{Type: "Directory"} @@ -619,7 +624,7 @@ func TestRepositoryServerGetAppDetails(t *testing.T) { url := "https://test" db := &dbmocks.ArgoDB{} - db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil) + db.On("GetRepository", context.TODO(), url, "default").Return(&appsv1.Repository{Repo: url}, nil) db.On("GetProjectRepositories", context.TODO(), "default").Return(nil, nil) db.On("GetProjectClusters", context.TODO(), "default").Return(nil, nil) expectedResp := apiclient.RepoAppDetailsResponse{Type: "Directory"} @@ -645,7 +650,7 @@ func TestRepositoryServerGetAppDetails(t *testing.T) { url := "https://test" db := &dbmocks.ArgoDB{} db.On("ListHelmRepositories", context.TODO(), mock.Anything).Return(nil, nil) - db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil) + db.On("GetRepository", context.TODO(), url, "default").Return(&appsv1.Repository{Repo: url}, nil) db.On("GetProjectRepositories", context.TODO(), "default").Return(nil, nil) db.On("GetProjectClusters", context.TODO(), "default").Return(nil, nil) expectedResp := apiclient.RepoAppDetailsResponse{Type: "Directory"} @@ -670,7 +675,7 @@ func TestRepositoryServerGetAppDetails(t *testing.T) { helmRepos := []*appsv1.Repository{{Repo: url}, {Repo: url}} db := &dbmocks.ArgoDB{} db.On("ListHelmRepositories", context.TODO(), mock.Anything).Return(helmRepos, nil) - db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil) + db.On("GetRepository", context.TODO(), url, "default").Return(&appsv1.Repository{Repo: url}, nil) db.On("GetProjectRepositories", context.TODO(), "default").Return(nil, nil) db.On("GetProjectClusters", context.TODO(), "default").Return(nil, nil) expectedResp := apiclient.RepoAppDetailsResponse{Type: "Helm"} @@ -708,8 +713,8 @@ func TestRepositoryServerGetAppDetails(t *testing.T) { helmRepos := []*appsv1.Repository{{Repo: url0}, {Repo: url1}} db := &dbmocks.ArgoDB{} db.On("ListHelmRepositories", context.TODO(), mock.Anything).Return(helmRepos, nil) - db.On("GetRepository", context.TODO(), url0).Return(&appsv1.Repository{Repo: url0}, nil) - db.On("GetRepository", context.TODO(), url1).Return(&appsv1.Repository{Repo: url1}, nil) + db.On("GetRepository", context.TODO(), url0, "default").Return(&appsv1.Repository{Repo: url0}, nil) + db.On("GetRepository", context.TODO(), url1, "default").Return(&appsv1.Repository{Repo: url1}, nil) db.On("GetProjectRepositories", context.TODO(), "default").Return(nil, nil) db.On("GetProjectClusters", context.TODO(), "default").Return(nil, nil) expectedResp0 := apiclient.RepoAppDetailsResponse{Type: "Plugin"} @@ -747,7 +752,7 @@ func TestRepositoryServerGetAppDetails(t *testing.T) { url := "https://test" db := &dbmocks.ArgoDB{} - db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil) + db.On("GetRepository", context.TODO(), url, "mismatch").Return(&appsv1.Repository{Repo: url}, nil) appLister, projLister := newAppAndProjLister(defaultProj, guestbookApp) s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, testNamespace, settingsMgr) @@ -766,7 +771,7 @@ func TestRepositoryServerGetAppDetails(t *testing.T) { url := "https://test" db := &dbmocks.ArgoDB{} - db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil) + db.On("GetRepository", context.TODO(), url, "default").Return(&appsv1.Repository{Repo: url}, nil) appLister, projLister := newAppAndProjLister(defaultProj, guestbookApp) differentSource := guestbookApp.Spec.Source.DeepCopy() differentSource.Helm.ValueFiles = []string{"/etc/passwd"} @@ -787,7 +792,7 @@ func TestRepositoryServerGetAppDetails(t *testing.T) { url := "https://test" db := &dbmocks.ArgoDB{} - db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil) + db.On("GetRepository", context.TODO(), url, "default").Return(&appsv1.Repository{Repo: url}, nil) db.On("ListHelmRepositories", context.TODO(), mock.Anything).Return(nil, nil) db.On("GetProjectRepositories", context.TODO(), "default").Return(nil, nil) db.On("GetProjectClusters", context.TODO(), "default").Return(nil, nil) @@ -833,3 +838,159 @@ func newEnforcer(kubeclientset *fake.Clientset) *rbac.Enforcer { }) return enforcer } + +func TestGetRepository(t *testing.T) { + type args struct { + ctx context.Context + listRepositories func(context.Context, *repository.RepoQuery) (*appsv1.RepositoryList, error) + q *repository.RepoQuery + } + tests := []struct { + name string + args args + want *appsv1.Repository + error error + }{ + { + name: "empty project and no repos", + args: args{ + ctx: context.TODO(), + listRepositories: func(ctx context.Context, query *repository.RepoQuery) (*appsv1.RepositoryList, error) { + return &appsv1.RepositoryList{ + Items: []*appsv1.Repository{ + {Repo: "something-else"}, + }, + }, nil + }, + q: &repository.RepoQuery{}, + }, + want: nil, + error: status.Error(codes.PermissionDenied, "permission denied"), + }, + { + name: "empty project and no matching repos", + args: args{ + ctx: context.TODO(), + listRepositories: func(ctx context.Context, query *repository.RepoQuery) (*appsv1.RepositoryList, error) { + return &appsv1.RepositoryList{}, nil + }, + q: &repository.RepoQuery{ + Repo: "foobar", + }, + }, + want: nil, + error: status.Error(codes.PermissionDenied, "permission denied"), + }, + { + name: "empty project + matching repo with an empty project", + args: args{ + ctx: context.TODO(), + listRepositories: func(ctx context.Context, query *repository.RepoQuery) (*appsv1.RepositoryList, error) { + return &appsv1.RepositoryList{ + Items: []*appsv1.Repository{ + {Repo: "foobar", Project: ""}, + }, + }, nil + }, + q: &repository.RepoQuery{ + Repo: "foobar", + AppProject: "", + }, + }, + want: &appsv1.Repository{ + Repo: "foobar", + Project: "", + }, + error: nil, + }, + { + name: "empty project + matching repo with a non-empty project", + args: args{ + ctx: context.TODO(), + listRepositories: func(ctx context.Context, query *repository.RepoQuery) (*appsv1.RepositoryList, error) { + return &appsv1.RepositoryList{ + Items: []*appsv1.Repository{ + {Repo: "foobar", Project: "foobar"}, + }, + }, nil + }, + q: &repository.RepoQuery{ + Repo: "foobar", + AppProject: "", + }, + }, + want: &appsv1.Repository{ + Repo: "foobar", + Project: "foobar", + }, + error: nil, + }, + { + name: "non-empty project + matching repo with an empty project", + args: args{ + ctx: context.TODO(), + listRepositories: func(ctx context.Context, query *repository.RepoQuery) (*appsv1.RepositoryList, error) { + return &appsv1.RepositoryList{ + Items: []*appsv1.Repository{ + {Repo: "foobar", Project: ""}, + }, + }, nil + }, + q: &repository.RepoQuery{ + Repo: "foobar", + AppProject: "foobar", + }, + }, + want: nil, + error: errors.New(`repository not found for url "foobar" and project "foobar"`), + }, + { + name: "non-empty project + matching repo with a matching project", + args: args{ + ctx: context.TODO(), + listRepositories: func(ctx context.Context, query *repository.RepoQuery) (*appsv1.RepositoryList, error) { + return &appsv1.RepositoryList{ + Items: []*appsv1.Repository{ + {Repo: "foobar", Project: "foobar"}, + }, + }, nil + }, + q: &repository.RepoQuery{ + Repo: "foobar", + AppProject: "foobar", + }, + }, + want: &appsv1.Repository{ + Repo: "foobar", + Project: "foobar", + }, + error: nil, + }, + { + name: "non-empty project + matching repo with a non-matching project", + args: args{ + ctx: context.TODO(), + listRepositories: func(ctx context.Context, query *repository.RepoQuery) (*appsv1.RepositoryList, error) { + return &appsv1.RepositoryList{ + Items: []*appsv1.Repository{ + {Repo: "foobar", Project: "something-else"}, + }, + }, nil + }, + q: &repository.RepoQuery{ + Repo: "foobar", + AppProject: "foobar", + }, + }, + want: nil, + error: errors.New(`repository not found for url "foobar" and project "foobar"`), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getRepository(tt.args.ctx, tt.args.listRepositories, tt.args.q) + assert.Equal(t, tt.error, err) + assert.Equalf(t, tt.want, got, "getRepository(%v, %v) = %v", tt.args.ctx, tt.args.q, got) + }) + } +} diff --git a/ui/src/app/settings/components/repos-list/repos-list.tsx b/ui/src/app/settings/components/repos-list/repos-list.tsx index 5322644073142..c2aedd630ee4f 100644 --- a/ui/src/app/settings/components/repos-list/repos-list.tsx +++ b/ui/src/app/settings/components/repos-list/repos-list.tsx @@ -334,7 +334,7 @@ export class ReposList extends React.Component< }, { title: 'Disconnect', - action: () => this.disconnectRepo(repo.repo) + action: () => this.disconnectRepo(repo.repo, repo.project) } ]} /> @@ -847,11 +847,11 @@ export class ReposList extends React.Component< } // Remove a repository from the configuration - private async disconnectRepo(repo: string) { + private async disconnectRepo(repo: string, project: string) { const confirmed = await this.appContext.apis.popup.confirm('Disconnect repository', `Are you sure you want to disconnect '${repo}'?`); if (confirmed) { try { - await services.repos.delete(repo); + await services.repos.delete(repo, project); this.repoLoader.reload(); } catch (e) { this.appContext.apis.notifications.show({ diff --git a/ui/src/app/shared/services/repo-service.ts b/ui/src/app/shared/services/repo-service.ts index 94378bee8352b..86f75a0b19162 100644 --- a/ui/src/app/shared/services/repo-service.ts +++ b/ui/src/app/shared/services/repo-service.ts @@ -188,9 +188,9 @@ export class RepositoriesService { .then(res => res.body as models.Repository); } - public delete(url: string): Promise { + public delete(url: string, project: string): Promise { return requests - .delete(`/repositories/${encodeURIComponent(url)}`) + .delete(`/repositories/${encodeURIComponent(url)}?appProject=${project}`) .send() .then(res => res.body as models.Repository); } diff --git a/util/argo/argo.go b/util/argo/argo.go index 031f1dac6408c..424bcfd0b564f 100644 --- a/util/argo/argo.go +++ b/util/argo/argo.go @@ -388,7 +388,7 @@ func validateRepo(ctx context.Context, errMessage := "" for _, source := range sources { - repo, err := db.GetRepository(ctx, source.RepoURL) + repo, err := db.GetRepository(ctx, source.RepoURL, proj.Name) if err != nil { return nil, err } @@ -418,7 +418,7 @@ func validateRepo(ctx context.Context, } } - refSources, err := GetRefSources(ctx, app.Spec, db) + refSources, err := GetRefSources(ctx, app.Spec, db.GetRepository) if err != nil { return nil, fmt.Errorf("error getting ref sources: %w", err) } @@ -446,7 +446,7 @@ func validateRepo(ctx context.Context, // GetRefSources creates a map of ref keys (from the sources' 'ref' fields) to information about the referenced source. // This function also validates the references use allowed characters and does not define the same ref key more than // once (which would lead to ambiguous references). -func GetRefSources(ctx context.Context, spec argoappv1.ApplicationSpec, db db.ArgoDB) (argoappv1.RefTargetRevisionMapping, error) { +func GetRefSources(ctx context.Context, spec argoappv1.ApplicationSpec, getRepository func(ctx context.Context, url string, project string) (*argoappv1.Repository, error)) (argoappv1.RefTargetRevisionMapping, error) { refSources := make(argoappv1.RefTargetRevisionMapping) if spec.HasMultipleSources() { // Validate first to avoid unnecessary DB calls. @@ -467,7 +467,7 @@ func GetRefSources(ctx context.Context, spec argoappv1.ApplicationSpec, db db.Ar // Get Repositories for all sources before generating Manifests for _, source := range spec.Sources { if source.Ref != "" { - repo, err := db.GetRepository(ctx, source.RepoURL) + repo, err := getRepository(ctx, source.RepoURL, spec.Project) if err != nil { return nil, fmt.Errorf("failed to get repository %s: %v", source.RepoURL, err) } @@ -742,7 +742,7 @@ func verifyGenerateManifests( } for _, source := range sources { - repoRes, err := db.GetRepository(ctx, source.RepoURL) + repoRes, err := db.GetRepository(ctx, source.RepoURL, proj.Name) if err != nil { conditions = append(conditions, argoappv1.ApplicationCondition{ Type: argoappv1.ApplicationConditionInvalidSpecError, diff --git a/util/argo/argo_test.go b/util/argo/argo_test.go index 71721b7a9440b..3ffc35eb6f5c4 100644 --- a/util/argo/argo_test.go +++ b/util/argo/argo_test.go @@ -375,7 +375,7 @@ func TestValidateRepo(t *testing.T) { db := &dbmocks.ArgoDB{} - db.On("GetRepository", context.Background(), app.Spec.Source.RepoURL).Return(repo, nil) + db.On("GetRepository", context.Background(), app.Spec.Source.RepoURL, "").Return(repo, nil) db.On("ListHelmRepositories", context.Background()).Return(helmRepos, nil) db.On("GetCluster", context.Background(), app.Spec.Destination.Server).Return(cluster, nil) db.On("GetAllHelmRepositoryCredentials", context.Background()).Return(nil, nil) @@ -1270,15 +1270,14 @@ func Test_GetRefSources(t *testing.T) { repo := &argoappv1.Repository{Repo: fmt.Sprintf("file://%s", repoPath)} t.Run("target ref exists", func(t *testing.T) { - repoDB := &dbmocks.ArgoDB{} - repoDB.On("GetRepository", context.Background(), repo.Repo).Return(repo, nil) - argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{ {RepoURL: fmt.Sprintf("file://%s", repoPath), Ref: "source-1_2"}, {RepoURL: fmt.Sprintf("file://%s", repoPath)}, }) - refSources, err := GetRefSources(context.Background(), *argoSpec, repoDB) + refSources, err := GetRefSources(context.Background(), *argoSpec, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { + return repo, nil + }) expectedRefSource := argoappv1.RefTargetRevisionMapping{ "$source-1_2": &argoappv1.RefTarget{ @@ -1291,14 +1290,13 @@ func Test_GetRefSources(t *testing.T) { }) t.Run("target ref does not exist", func(t *testing.T) { - repoDB := &dbmocks.ArgoDB{} - repoDB.On("GetRepository", context.Background(), "file://does-not-exist").Return(nil, errors.New("repo does not exist")) - argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{ {RepoURL: "file://does-not-exist", Ref: "source1"}, }) - refSources, err := GetRefSources(context.Background(), *argoSpec, repoDB) + refSources, err := GetRefSources(context.Background(), *argoSpec, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { + return nil, errors.New("repo does not exist") + }) assert.Error(t, err) assert.Empty(t, refSources) @@ -1309,7 +1307,9 @@ func Test_GetRefSources(t *testing.T) { {RepoURL: "file://does-not-exist", Ref: "%invalid-name%"}, }) - refSources, err := GetRefSources(context.TODO(), *argoSpec, &dbmocks.ArgoDB{}) + refSources, err := GetRefSources(context.TODO(), *argoSpec, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { + return nil, err + }) assert.Error(t, err) assert.Empty(t, refSources) @@ -1321,7 +1321,9 @@ func Test_GetRefSources(t *testing.T) { {RepoURL: "file://does-not-exist", Ref: "source1"}, }) - refSources, err := GetRefSources(context.TODO(), *argoSpec, &dbmocks.ArgoDB{}) + refSources, err := GetRefSources(context.TODO(), *argoSpec, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { + return nil, err + }) assert.Error(t, err) assert.Empty(t, refSources) diff --git a/util/db/db.go b/util/db/db.go index 190928c6a14c6..01f819b8013dd 100644 --- a/util/db/db.go +++ b/util/db/db.go @@ -52,32 +52,32 @@ type ArgoDB interface { // CreateRepository creates a repository CreateRepository(ctx context.Context, r *appv1.Repository) (*appv1.Repository, error) // GetRepository returns a repository by URL - GetRepository(ctx context.Context, url string) (*appv1.Repository, error) + GetRepository(ctx context.Context, url, project string) (*appv1.Repository, error) // GetProjectRepositories returns project scoped repositories by given project name GetProjectRepositories(ctx context.Context, project string) ([]*appv1.Repository, error) // RepositoryExists returns whether a repository is configured for the given URL - RepositoryExists(ctx context.Context, repoURL string) (bool, error) + RepositoryExists(ctx context.Context, repoURL, project string) (bool, error) // UpdateRepository updates a repository UpdateRepository(ctx context.Context, r *appv1.Repository) (*appv1.Repository, error) // DeleteRepository deletes a repository from config - DeleteRepository(ctx context.Context, name string) error + DeleteRepository(ctx context.Context, name, project string) error - // ListRepoCredentials list all repo credential sets URL patterns + // ListRepositoryCredentials list all repo credential sets URL patterns ListRepositoryCredentials(ctx context.Context) ([]string, error) - // GetRepoCredentials gets repo credentials for given URL + // GetRepositoryCredentials gets repo credentials for given URL GetRepositoryCredentials(ctx context.Context, name string) (*appv1.RepoCreds, error) - // CreateRepoCredentials creates a repository credential set + // CreateRepositoryCredentials creates a repository credential set CreateRepositoryCredentials(ctx context.Context, r *appv1.RepoCreds) (*appv1.RepoCreds, error) - // UpdateRepoCredentials updates a repository credential set + // UpdateRepositoryCredentials updates a repository credential set UpdateRepositoryCredentials(ctx context.Context, r *appv1.RepoCreds) (*appv1.RepoCreds, error) - // DeleteRepoCredentials deletes a repository credential set from config + // DeleteRepositoryCredentials deletes a repository credential set from config DeleteRepositoryCredentials(ctx context.Context, name string) error // ListRepoCertificates lists all configured certificates ListRepoCertificates(ctx context.Context, selector *CertificateListSelector) (*appv1.RepositoryCertificateList, error) // CreateRepoCertificate creates a new certificate entry CreateRepoCertificate(ctx context.Context, certificate *appv1.RepositoryCertificateList, upsert bool) (*appv1.RepositoryCertificateList, error) - // CreateRepoCertificate creates a new certificate entry + // RemoveRepoCertificates removes certificates based upon a selector RemoveRepoCertificates(ctx context.Context, selector *CertificateListSelector) (*appv1.RepositoryCertificateList, error) // GetAllHelmRepositoryCredentials gets all repo credentials GetAllHelmRepositoryCredentials(ctx context.Context) ([]*appv1.RepoCreds, error) @@ -87,7 +87,7 @@ type ArgoDB interface { // ListConfiguredGPGPublicKeys returns all GPG public key IDs that are configured ListConfiguredGPGPublicKeys(ctx context.Context) (map[string]*appv1.GnuPGPublicKey, error) - // AddGPGPublicKey adds one ore more GPG public keys to the configuration + // AddGPGPublicKey adds one or more GPG public keys to the configuration AddGPGPublicKey(ctx context.Context, keyData string) (map[string]*appv1.GnuPGPublicKey, []string, error) // DeleteGPGPublicKey removes a GPG public key from the configuration DeleteGPGPublicKey(ctx context.Context, keyID string) error diff --git a/util/db/db_test.go b/util/db/db_test.go index 4574d6cd5e029..c4f3009dc8bc8 100644 --- a/util/db/db_test.go +++ b/util/db/db_test.go @@ -61,7 +61,7 @@ func TestCreateRepository(t *testing.T) { assert.Nil(t, err) assert.Equal(t, "https://github.com/argoproj/argocd-example-apps", repo.Repo) - secret, err := clientset.CoreV1().Secrets(testNamespace).Get(context.Background(), RepoURLToSecretName(repoSecretPrefix, repo.Repo), metav1.GetOptions{}) + secret, err := clientset.CoreV1().Secrets(testNamespace).Get(context.Background(), RepoURLToSecretName(repoSecretPrefix, repo.Repo, ""), metav1.GetOptions{}) assert.Nil(t, err) assert.Equal(t, common.AnnotationValueManagedByArgoCD, secret.Annotations[common.AnnotationKeyManagedBy]) @@ -70,6 +70,53 @@ func TestCreateRepository(t *testing.T) { assert.Empty(t, secret.Data[sshPrivateKey]) } +func TestCreateProjectScopedRepository(t *testing.T) { + clientset := getClientset(nil) + db := NewDB(testNamespace, settings.NewSettingsManager(context.Background(), clientset, testNamespace), clientset) + + repo, err := db.CreateRepository(context.Background(), &v1alpha1.Repository{ + Repo: "https://github.com/argoproj/argocd-example-apps", + Username: "test-username", + Password: "test-password", + Project: "test-project", + }) + assert.Nil(t, err) + + otherRepo, err := db.CreateRepository(context.Background(), &v1alpha1.Repository{ + Repo: "https://github.com/argoproj/argocd-example-apps", + Username: "other-username", + Password: "other-password", + Project: "other-project", + }) + assert.Nil(t, err) + + _, err = db.CreateRepository(context.Background(), &v1alpha1.Repository{ + Repo: "https://github.com/argoproj/argocd-example-apps", + Username: "wrong-username", + Password: "wrong-password", + }) + assert.Nil(t, err) + + assert.Equal(t, "https://github.com/argoproj/argocd-example-apps", repo.Repo) + + secret, err := clientset.CoreV1().Secrets(testNamespace).Get(context.Background(), RepoURLToSecretName(repoSecretPrefix, repo.Repo, "test-project"), metav1.GetOptions{}) + assert.Nil(t, err) + + assert.Equal(t, common.AnnotationValueManagedByArgoCD, secret.Annotations[common.AnnotationKeyManagedBy]) + assert.Equal(t, string(secret.Data[username]), "test-username") + assert.Equal(t, string(secret.Data[password]), "test-password") + assert.Equal(t, string(secret.Data[project]), "test-project") + assert.Empty(t, secret.Data[sshPrivateKey]) + + secret, err = clientset.CoreV1().Secrets(testNamespace).Get(context.Background(), RepoURLToSecretName(repoSecretPrefix, otherRepo.Repo, "other-project"), metav1.GetOptions{}) + assert.Nil(t, err) + assert.Equal(t, common.AnnotationValueManagedByArgoCD, secret.Annotations[common.AnnotationKeyManagedBy]) + assert.Equal(t, string(secret.Data[username]), "other-username") + assert.Equal(t, string(secret.Data[password]), "other-password") + assert.Equal(t, string(secret.Data[project]), "other-project") + assert.Empty(t, secret.Data[sshPrivateKey]) +} + func TestCreateRepoCredentials(t *testing.T) { clientset := getClientset(nil) db := NewDB(testNamespace, settings.NewSettingsManager(context.Background(), clientset, testNamespace), clientset) @@ -82,7 +129,7 @@ func TestCreateRepoCredentials(t *testing.T) { assert.Nil(t, err) assert.Equal(t, "https://github.com/argoproj/", creds.URL) - secret, err := clientset.CoreV1().Secrets(testNamespace).Get(context.Background(), RepoURLToSecretName(credSecretPrefix, creds.URL), metav1.GetOptions{}) + secret, err := clientset.CoreV1().Secrets(testNamespace).Get(context.Background(), RepoURLToSecretName(credSecretPrefix, creds.URL, ""), metav1.GetOptions{}) assert.Nil(t, err) assert.Equal(t, common.AnnotationValueManagedByArgoCD, secret.Annotations[common.AnnotationKeyManagedBy]) @@ -100,7 +147,7 @@ func TestCreateRepoCredentials(t *testing.T) { // Just give it a little time to settle. time.Sleep(1 * time.Second) - repo, err := db.GetRepository(context.Background(), created.Repo) + repo, err := db.GetRepository(context.Background(), created.Repo, "") assert.NoError(t, err) assert.Equal(t, "test-username", repo.Username) assert.Equal(t, "test-password", repo.Password) @@ -235,7 +282,7 @@ func TestGetRepository(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := db.GetRepository(context.TODO(), tt.repoURL) + got, err := db.GetRepository(context.TODO(), tt.repoURL, "") assert.NoError(t, err) assert.Equal(t, tt.want, got) }) @@ -272,7 +319,7 @@ func TestDeleteRepositoryManagedSecrets(t *testing.T) { clientset := getClientset(config, newManagedSecret()) db := NewDB(testNamespace, settings.NewSettingsManager(context.Background(), clientset, testNamespace), clientset) - err := db.DeleteRepository(context.Background(), "https://github.com/argoproj/argocd-example-apps") + err := db.DeleteRepository(context.Background(), "https://github.com/argoproj/argocd-example-apps", "") assert.Nil(t, err) _, err = clientset.CoreV1().Secrets(testNamespace).Get(context.Background(), "managed-secret", metav1.GetOptions{}) @@ -307,7 +354,7 @@ func TestDeleteRepositoryUnmanagedSecrets(t *testing.T) { }) db := NewDB(testNamespace, settings.NewSettingsManager(context.Background(), clientset, testNamespace), clientset) - err := db.DeleteRepository(context.Background(), "https://github.com/argoproj/argocd-example-apps") + err := db.DeleteRepository(context.Background(), "https://github.com/argoproj/argocd-example-apps", "") assert.Nil(t, err) s, err := clientset.CoreV1().Secrets(testNamespace).Get(context.Background(), "unmanaged-secret", metav1.GetOptions{}) @@ -350,7 +397,7 @@ func TestUpdateRepositoryWithManagedSecrets(t *testing.T) { }) db := NewDB(testNamespace, settings.NewSettingsManager(context.Background(), clientset, testNamespace), clientset) - repo, err := db.GetRepository(context.Background(), "https://github.com/argoproj/argocd-example-apps") + repo, err := db.GetRepository(context.Background(), "https://github.com/argoproj/argocd-example-apps", "") assert.Nil(t, err) assert.Equal(t, "test-username", repo.Username) assert.Equal(t, "test-password", repo.Password) @@ -411,7 +458,7 @@ func TestRepositorySecretsTrim(t *testing.T) { }) db := NewDB(testNamespace, settings.NewSettingsManager(context.Background(), clientset, testNamespace), clientset) - repo, err := db.GetRepository(context.Background(), "https://github.com/argoproj/argocd-example-apps") + repo, err := db.GetRepository(context.Background(), "https://github.com/argoproj/argocd-example-apps", "") assert.Nil(t, err) teststruct := []struct { expectedSecret string @@ -582,7 +629,7 @@ func TestFuzzyEquivalence(t *testing.T) { assert.Contains(t, err.Error(), "already exists") assert.Nil(t, repo) - repo, err = db.GetRepository(ctx, "https://github.com/argoproj/argocd-example-APPS") + repo, err = db.GetRepository(ctx, "https://github.com/argoproj/argocd-example-APPS", "") assert.Nil(t, err) assert.Equal(t, "https://github.com/argoproj/argocd-example-apps", repo.Repo) } @@ -666,7 +713,7 @@ func TestHelmRepositorySecretsTrim(t *testing.T) { }, }) db := NewDB(testNamespace, settings.NewSettingsManager(context.Background(), clientset, testNamespace), clientset) - repo, err := db.GetRepository(context.Background(), "https://argoproj.github.io/argo-helm") + repo, err := db.GetRepository(context.Background(), "https://argoproj.github.io/argo-helm", "") assert.Nil(t, err) teststruct := []struct { diff --git a/util/db/mocks/ArgoDB.go b/util/db/mocks/ArgoDB.go index c12c7cdbf6ad3..cacc19f417f59 100644 --- a/util/db/mocks/ArgoDB.go +++ b/util/db/mocks/ArgoDB.go @@ -211,17 +211,17 @@ func (_m *ArgoDB) DeleteGPGPublicKey(ctx context.Context, keyID string) error { return r0 } -// DeleteRepository provides a mock function with given fields: ctx, name -func (_m *ArgoDB) DeleteRepository(ctx context.Context, name string) error { - ret := _m.Called(ctx, name) +// DeleteRepository provides a mock function with given fields: ctx, name, project +func (_m *ArgoDB) DeleteRepository(ctx context.Context, name string, project string) error { + ret := _m.Called(ctx, name, project) if len(ret) == 0 { panic("no return value specified for DeleteRepository") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, name) + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, name, project) } else { r0 = ret.Error(0) } @@ -415,9 +415,9 @@ func (_m *ArgoDB) GetProjectRepositories(ctx context.Context, project string) ([ return r0, r1 } -// GetRepository provides a mock function with given fields: ctx, url -func (_m *ArgoDB) GetRepository(ctx context.Context, url string) (*v1alpha1.Repository, error) { - ret := _m.Called(ctx, url) +// GetRepository provides a mock function with given fields: ctx, url, project +func (_m *ArgoDB) GetRepository(ctx context.Context, url string, project string) (*v1alpha1.Repository, error) { + ret := _m.Called(ctx, url, project) if len(ret) == 0 { panic("no return value specified for GetRepository") @@ -425,19 +425,19 @@ func (_m *ArgoDB) GetRepository(ctx context.Context, url string) (*v1alpha1.Repo var r0 *v1alpha1.Repository var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (*v1alpha1.Repository, error)); ok { - return rf(ctx, url) + if rf, ok := ret.Get(0).(func(context.Context, string, string) (*v1alpha1.Repository, error)); ok { + return rf(ctx, url, project) } - if rf, ok := ret.Get(0).(func(context.Context, string) *v1alpha1.Repository); ok { - r0 = rf(ctx, url) + if rf, ok := ret.Get(0).(func(context.Context, string, string) *v1alpha1.Repository); ok { + r0 = rf(ctx, url, project) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*v1alpha1.Repository) } } - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, url) + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, url, project) } else { r1 = ret.Error(1) } @@ -685,9 +685,9 @@ func (_m *ArgoDB) RemoveRepoCertificates(ctx context.Context, selector *db.Certi return r0, r1 } -// RepositoryExists provides a mock function with given fields: ctx, repoURL -func (_m *ArgoDB) RepositoryExists(ctx context.Context, repoURL string) (bool, error) { - ret := _m.Called(ctx, repoURL) +// RepositoryExists provides a mock function with given fields: ctx, repoURL, project +func (_m *ArgoDB) RepositoryExists(ctx context.Context, repoURL string, project string) (bool, error) { + ret := _m.Called(ctx, repoURL, project) if len(ret) == 0 { panic("no return value specified for RepositoryExists") @@ -695,17 +695,17 @@ func (_m *ArgoDB) RepositoryExists(ctx context.Context, repoURL string) (bool, e var r0 bool var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (bool, error)); ok { - return rf(ctx, repoURL) + if rf, ok := ret.Get(0).(func(context.Context, string, string) (bool, error)); ok { + return rf(ctx, repoURL, project) } - if rf, ok := ret.Get(0).(func(context.Context, string) bool); ok { - r0 = rf(ctx, repoURL) + if rf, ok := ret.Get(0).(func(context.Context, string, string) bool); ok { + r0 = rf(ctx, repoURL, project) } else { r0 = ret.Get(0).(bool) } - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, repoURL) + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, repoURL, project) } else { r1 = ret.Error(1) } diff --git a/util/db/repository.go b/util/db/repository.go index 552baa3a7a61e..499c1bd4f7fa6 100644 --- a/util/db/repository.go +++ b/util/db/repository.go @@ -24,6 +24,8 @@ const ( username = "username" // The name of the key storing the password in the secret password = "password" + // The name of the project storing the project in the secret + project = "project" // The name of the key storing the SSH private in the secret sshPrivateKey = "sshPrivateKey" // The name of the key storing the TLS client cert data in the secret @@ -39,11 +41,11 @@ const ( // repositoryBackend defines the API for types that wish to provide interaction with repository storage type repositoryBackend interface { CreateRepository(ctx context.Context, r *appsv1.Repository) (*appsv1.Repository, error) - GetRepository(ctx context.Context, repoURL string) (*appsv1.Repository, error) + GetRepository(ctx context.Context, repoURL, project string) (*appsv1.Repository, error) ListRepositories(ctx context.Context, repoType *string) ([]*appsv1.Repository, error) UpdateRepository(ctx context.Context, r *appsv1.Repository) (*appsv1.Repository, error) - DeleteRepository(ctx context.Context, repoURL string) error - RepositoryExists(ctx context.Context, repoURL string) (bool, error) + DeleteRepository(ctx context.Context, repoURL, project string) error + RepositoryExists(ctx context.Context, repoURL, project string, allowFallback bool) (bool, error) CreateRepoCreds(ctx context.Context, r *appsv1.RepoCreds) (*appsv1.RepoCreds, error) GetRepoCreds(ctx context.Context, repoURL string) (*appsv1.RepoCreds, error) @@ -59,11 +61,11 @@ func (db *db) CreateRepository(ctx context.Context, r *appsv1.Repository) (*apps secretBackend := db.repoBackend() legacyBackend := db.legacyRepoBackend() - secretExists, err := secretBackend.RepositoryExists(ctx, r.Repo) + secretExists, err := secretBackend.RepositoryExists(ctx, r.Repo, r.Project, false) if err != nil { return nil, err } - legacyExists, err := legacyBackend.RepositoryExists(ctx, r.Repo) + legacyExists, err := legacyBackend.RepositoryExists(ctx, r.Repo, r.Project, false) if err != nil { return nil, err } @@ -75,8 +77,8 @@ func (db *db) CreateRepository(ctx context.Context, r *appsv1.Repository) (*apps return secretBackend.CreateRepository(ctx, r) } -func (db *db) GetRepository(ctx context.Context, repoURL string) (*appsv1.Repository, error) { - repository, err := db.getRepository(ctx, repoURL) +func (db *db) GetRepository(ctx context.Context, repoURL, project string) (*appsv1.Repository, error) { + repository, err := db.getRepository(ctx, repoURL, project) if err != nil { return repository, fmt.Errorf("unable to get repository %q: %v", repoURL, err) } @@ -108,24 +110,24 @@ func (db *db) GetProjectRepositories(ctx context.Context, project string) ([]*ap return res, nil } -func (db *db) RepositoryExists(ctx context.Context, repoURL string) (bool, error) { +func (db *db) RepositoryExists(ctx context.Context, repoURL, project string) (bool, error) { secretsBackend := db.repoBackend() - exists, err := secretsBackend.RepositoryExists(ctx, repoURL) + exists, err := secretsBackend.RepositoryExists(ctx, repoURL, project, true) if exists || err != nil { return exists, err } legacyBackend := db.legacyRepoBackend() - return legacyBackend.RepositoryExists(ctx, repoURL) + return legacyBackend.RepositoryExists(ctx, repoURL, project, true) } -func (db *db) getRepository(ctx context.Context, repoURL string) (*appsv1.Repository, error) { +func (db *db) getRepository(ctx context.Context, repoURL, project string) (*appsv1.Repository, error) { secretsBackend := db.repoBackend() - exists, err := secretsBackend.RepositoryExists(ctx, repoURL) + exists, err := secretsBackend.RepositoryExists(ctx, repoURL, project, true) if err != nil { return nil, fmt.Errorf("unable to check if repository %q exists from secrets backend: %v", repoURL, err) } else if exists { - repository, err := secretsBackend.GetRepository(ctx, repoURL) + repository, err := secretsBackend.GetRepository(ctx, repoURL, project) if err != nil { return nil, fmt.Errorf("unable to get repository %q from secrets backend: %v", repoURL, err) } @@ -133,11 +135,11 @@ func (db *db) getRepository(ctx context.Context, repoURL string) (*appsv1.Reposi } legacyBackend := db.legacyRepoBackend() - exists, err = legacyBackend.RepositoryExists(ctx, repoURL) + exists, err = legacyBackend.RepositoryExists(ctx, repoURL, project, true) if err != nil { return nil, fmt.Errorf("unable to check if repository %q exists from legacy backend: %v", repoURL, err) } else if exists { - repository, err := legacyBackend.GetRepository(ctx, repoURL) + repository, err := legacyBackend.GetRepository(ctx, repoURL, project) if err != nil { return nil, fmt.Errorf("unable to get repository %q from legacy backend: %v", repoURL, err) } @@ -176,7 +178,7 @@ func (db *db) listRepositories(ctx context.Context, repoType *string) ([]*appsv1 // UpdateRepository updates a repository func (db *db) UpdateRepository(ctx context.Context, r *appsv1.Repository) (*appsv1.Repository, error) { secretsBackend := db.repoBackend() - exists, err := secretsBackend.RepositoryExists(ctx, r.Repo) + exists, err := secretsBackend.RepositoryExists(ctx, r.Repo, r.Project, false) if err != nil { return nil, err } else if exists { @@ -184,7 +186,7 @@ func (db *db) UpdateRepository(ctx context.Context, r *appsv1.Repository) (*apps } legacyBackend := db.legacyRepoBackend() - exists, err = legacyBackend.RepositoryExists(ctx, r.Repo) + exists, err = legacyBackend.RepositoryExists(ctx, r.Repo, r.Project, false) if err != nil { return nil, err } else if exists { @@ -194,21 +196,21 @@ func (db *db) UpdateRepository(ctx context.Context, r *appsv1.Repository) (*apps return nil, status.Errorf(codes.NotFound, "repo '%s' not found", r.Repo) } -func (db *db) DeleteRepository(ctx context.Context, repoURL string) error { +func (db *db) DeleteRepository(ctx context.Context, repoURL, project string) error { secretsBackend := db.repoBackend() - exists, err := secretsBackend.RepositoryExists(ctx, repoURL) + exists, err := secretsBackend.RepositoryExists(ctx, repoURL, project, false) if err != nil { return err } else if exists { - return secretsBackend.DeleteRepository(ctx, repoURL) + return secretsBackend.DeleteRepository(ctx, repoURL, project) } legacyBackend := db.legacyRepoBackend() - exists, err = legacyBackend.RepositoryExists(ctx, repoURL) + exists, err = legacyBackend.RepositoryExists(ctx, repoURL, project, false) if err != nil { return err } else if exists { - return legacyBackend.DeleteRepository(ctx, repoURL) + return legacyBackend.DeleteRepository(ctx, repoURL, project) } return status.Errorf(codes.NotFound, "repo '%s' not found", repoURL) @@ -284,11 +286,11 @@ func (db *db) CreateRepositoryCredentials(ctx context.Context, r *appsv1.RepoCre legacyBackend := db.legacyRepoBackend() secretBackend := db.repoBackend() - secretExists, err := secretBackend.RepositoryExists(ctx, r.URL) + secretExists, err := secretBackend.RepositoryExists(ctx, r.URL, "", false) if err != nil { return nil, err } - legacyExists, err := legacyBackend.RepositoryExists(ctx, r.URL) + legacyExists, err := legacyBackend.RepositoryExists(ctx, r.URL, "", false) if err != nil { return nil, err } @@ -382,8 +384,9 @@ func (db *db) enrichCredsToRepo(ctx context.Context, repository *appsv1.Reposito // repositories are _imperatively_ created and need its credentials to be stored in a secret. // NOTE: this formula should not be considered stable and may change in future releases. // Do NOT rely on this formula as a means of secret lookup, only secret creation. -func RepoURLToSecretName(prefix string, repo string) string { +func RepoURLToSecretName(prefix string, repo string, project string) string { h := fnv.New32a() _, _ = h.Write([]byte(repo)) + _, _ = h.Write([]byte(project)) return fmt.Sprintf("%s-%v", prefix, h.Sum32()) } diff --git a/util/db/repository_legacy.go b/util/db/repository_legacy.go index e25fe873c7511..4f570c653e15e 100644 --- a/util/db/repository_legacy.go +++ b/util/db/repository_legacy.go @@ -29,11 +29,11 @@ type legacyRepositoryBackend struct { func (l *legacyRepositoryBackend) CreateRepository(ctx context.Context, r *appsv1.Repository) (*appsv1.Repository, error) { // This strategy only kept to preserve backward compatibility, but is deprecated. - // Therefore no new repositories can be added with this backend. + // Therefore, no new repositories can be added with this backend. panic("creating new repositories is not supported for the legacy repository backend") } -func (l *legacyRepositoryBackend) GetRepository(ctx context.Context, repoURL string) (*appsv1.Repository, error) { +func (l *legacyRepositoryBackend) GetRepository(ctx context.Context, repoURL, project string) (*appsv1.Repository, error) { repository, err := l.tryGetRepository(repoURL) if err != nil { return nil, fmt.Errorf("unable to get repository: %w", err) @@ -102,7 +102,7 @@ func (l *legacyRepositoryBackend) UpdateRepository(ctx context.Context, r *appsv return r, nil } -func (l *legacyRepositoryBackend) DeleteRepository(ctx context.Context, repoURL string) error { +func (l *legacyRepositoryBackend) DeleteRepository(ctx context.Context, repoURL, project string) error { repos, err := l.db.settingsMgr.GetRepositories() if err != nil { return err @@ -127,7 +127,7 @@ func (l *legacyRepositoryBackend) DeleteRepository(ctx context.Context, repoURL return l.db.settingsMgr.SaveRepositories(repos) } -func (l *legacyRepositoryBackend) RepositoryExists(ctx context.Context, repoURL string) (bool, error) { +func (l *legacyRepositoryBackend) RepositoryExists(ctx context.Context, repoURL, project string, allowFallback bool) (bool, error) { repos, err := l.db.settingsMgr.GetRepositories() if err != nil { return false, fmt.Errorf("unable to get repositories: %w", err) @@ -139,7 +139,7 @@ func (l *legacyRepositoryBackend) RepositoryExists(ctx context.Context, repoURL func (l *legacyRepositoryBackend) CreateRepoCreds(ctx context.Context, r *appsv1.RepoCreds) (*appsv1.RepoCreds, error) { // This strategy only kept to preserve backward compatibility, but is deprecated. - // Therefore no new repositories can be added with this backend. + // Therefore, no new repositories can be added with this backend. panic("creating new repository credentials is not supported for the legacy repository backend") } @@ -425,7 +425,7 @@ func (l *legacyRepositoryBackend) credentialsToRepositoryCredentials(repoInfo se func (l *legacyRepositoryBackend) setSecretData(prefix string, url string, secretsData map[string]map[string][]byte, secretKey *apiv1.SecretKeySelector, value string, defaultKeyName string) *apiv1.SecretKeySelector { if secretKey == nil && value != "" { secretKey = &apiv1.SecretKeySelector{ - LocalObjectReference: apiv1.LocalObjectReference{Name: RepoURLToSecretName(prefix, url)}, + LocalObjectReference: apiv1.LocalObjectReference{Name: RepoURLToSecretName(prefix, url, "")}, Key: defaultKeyName, } } diff --git a/util/db/repository_secrets.go b/util/db/repository_secrets.go index 2d96c1c3a99eb..e67ada694ca3a 100644 --- a/util/db/repository_secrets.go +++ b/util/db/repository_secrets.go @@ -24,7 +24,7 @@ type secretsRepositoryBackend struct { } func (s *secretsRepositoryBackend) CreateRepository(ctx context.Context, repository *appsv1.Repository) (*appsv1.Repository, error) { - secName := RepoURLToSecretName(repoSecretPrefix, repository.Repo) + secName := RepoURLToSecretName(repoSecretPrefix, repository.Repo, repository.Project) repositorySecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -81,8 +81,8 @@ func (s *secretsRepositoryBackend) GetRepoCredsBySecretName(_ context.Context, n return s.secretToRepoCred(secret) } -func (s *secretsRepositoryBackend) GetRepository(ctx context.Context, repoURL string) (*appsv1.Repository, error) { - secret, err := s.getRepositorySecret(repoURL) +func (s *secretsRepositoryBackend) GetRepository(ctx context.Context, repoURL, project string) (*appsv1.Repository, error) { + secret, err := s.getRepositorySecret(repoURL, project, true) if err != nil { if status.Code(err) == codes.NotFound { return &appsv1.Repository{Repo: repoURL}, nil @@ -133,7 +133,7 @@ func (s *secretsRepositoryBackend) ListRepositories(ctx context.Context, repoTyp } func (s *secretsRepositoryBackend) UpdateRepository(ctx context.Context, repository *appsv1.Repository) (*appsv1.Repository, error) { - repositorySecret, err := s.getRepositorySecret(repository.Repo) + repositorySecret, err := s.getRepositorySecret(repository.Repo, repository.Project, false) if err != nil { if status.Code(err) == codes.NotFound { return s.CreateRepository(ctx, repository) @@ -151,8 +151,8 @@ func (s *secretsRepositoryBackend) UpdateRepository(ctx context.Context, reposit return repository, s.db.settingsMgr.ResyncInformers() } -func (s *secretsRepositoryBackend) DeleteRepository(ctx context.Context, repoURL string) error { - secret, err := s.getRepositorySecret(repoURL) +func (s *secretsRepositoryBackend) DeleteRepository(ctx context.Context, repoURL, project string) error { + secret, err := s.getRepositorySecret(repoURL, project, false) if err != nil { return err } @@ -164,8 +164,8 @@ func (s *secretsRepositoryBackend) DeleteRepository(ctx context.Context, repoURL return s.db.settingsMgr.ResyncInformers() } -func (s *secretsRepositoryBackend) RepositoryExists(ctx context.Context, repoURL string) (bool, error) { - secret, err := s.getRepositorySecret(repoURL) +func (s *secretsRepositoryBackend) RepositoryExists(ctx context.Context, repoURL, project string, allowFallback bool) (bool, error) { + secret, err := s.getRepositorySecret(repoURL, project, allowFallback) if err != nil { if status.Code(err) == codes.NotFound { return false, nil @@ -178,7 +178,7 @@ func (s *secretsRepositoryBackend) RepositoryExists(ctx context.Context, repoURL } func (s *secretsRepositoryBackend) CreateRepoCreds(ctx context.Context, repoCreds *appsv1.RepoCreds) (*appsv1.RepoCreds, error) { - secName := RepoURLToSecretName(credSecretPrefix, repoCreds.URL) + secName := RepoURLToSecretName(credSecretPrefix, repoCreds.URL, "") repoCredsSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -454,18 +454,38 @@ func repoCredsToSecret(repoCreds *appsv1.RepoCreds, secret *corev1.Secret) { addSecretMetadata(secret, common.LabelValueSecretTypeRepoCreds) } -func (s *secretsRepositoryBackend) getRepositorySecret(repoURL string) (*corev1.Secret, error) { +func (s *secretsRepositoryBackend) getRepositorySecret(repoURL, project string, allowFallback bool) (*corev1.Secret, error) { secrets, err := s.db.listSecretsByType(common.LabelValueSecretTypeRepository) if err != nil { return nil, fmt.Errorf("failed to list repository secrets: %w", err) } + var foundSecret *corev1.Secret for _, secret := range secrets { if git.SameURL(string(secret.Data["url"]), repoURL) { - return secret, nil + projectSecret := string(secret.Data["project"]) + if project == projectSecret { + if foundSecret != nil { + log.Warnf("Found multiple credentials for repoURL: %s", repoURL) + } + + return secret, nil + } + + if projectSecret == "" && allowFallback { + if foundSecret != nil { + log.Warnf("Found multiple credentials for repoURL: %s", repoURL) + } + + foundSecret = secret + } } } + if foundSecret != nil { + return foundSecret, nil + } + return nil, status.Errorf(codes.NotFound, "repository %q not found", repoURL) } diff --git a/util/db/repository_secrets_test.go b/util/db/repository_secrets_test.go index f5beec2269d53..e225bea5d0f5c 100644 --- a/util/db/repository_secrets_test.go +++ b/util/db/repository_secrets_test.go @@ -64,7 +64,7 @@ func TestSecretsRepositoryBackend_CreateRepository(t *testing.T) { secret, err := f.clientSet.CoreV1().Secrets(testNamespace).Get( context.TODO(), - RepoURLToSecretName(repoSecretPrefix, repo.Repo), + RepoURLToSecretName(repoSecretPrefix, repo.Repo, ""), metav1.GetOptions{}, ) assert.NotNil(t, secret) @@ -109,7 +109,7 @@ func TestSecretsRepositoryBackend_CreateRepository(t *testing.T) { t.Run("will return proper error if secret already exists", func(t *testing.T) { // given t.Parallel() - secName := RepoURLToSecretName(repoSecretPrefix, repo.Repo) + secName := RepoURLToSecretName(repoSecretPrefix, repo.Repo, "") secret := &corev1.Secret{ TypeMeta: metav1.TypeMeta{ Kind: "Secret", @@ -154,7 +154,7 @@ func TestSecretsRepositoryBackend_GetRepository(t *testing.T) { &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: testNamespace, - Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git"), + Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git", ""), Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepository}, }, @@ -165,6 +165,21 @@ func TestSecretsRepositoryBackend_GetRepository(t *testing.T) { "password": []byte("somePassword"), }, }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git", "testProject"), + Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, + Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepository}, + }, + Data: map[string][]byte{ + "name": []byte("Scoped ArgoCD"), + "url": []byte("git@github.com:argoproj/argo-cd.git"), + "username": []byte("someScopedUsername"), + "password": []byte("someScopedPassword"), + "project": []byte("testProject"), + }, + }, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: testNamespace, @@ -178,6 +193,20 @@ func TestSecretsRepositoryBackend_GetRepository(t *testing.T) { "password": []byte("someOtherPassword"), }, }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "other-user-managed", + Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepository}, + }, + Data: map[string][]byte{ + "name": []byte("Scoped UserManagedRepo"), + "url": []byte("git@github.com:argoproj/argoproj.git"), + "username": []byte("someOtherUsername"), + "password": []byte("someOtherPassword"), + "project": []byte("testProject"), + }, + }, } clientset := getClientset(map[string]string{}, repoSecrets...) @@ -187,7 +216,7 @@ func TestSecretsRepositoryBackend_GetRepository(t *testing.T) { settingsMgr: settings.NewSettingsManager(context.TODO(), clientset, testNamespace), }} - repository, err := testee.GetRepository(context.TODO(), "git@github.com:argoproj/argo-cd.git") + repository, err := testee.GetRepository(context.TODO(), "git@github.com:argoproj/argo-cd.git", "") assert.NoError(t, err) assert.NotNil(t, repository) assert.Equal(t, "ArgoCD", repository.Name) @@ -195,13 +224,31 @@ func TestSecretsRepositoryBackend_GetRepository(t *testing.T) { assert.Equal(t, "someUsername", repository.Username) assert.Equal(t, "somePassword", repository.Password) - repository, err = testee.GetRepository(context.TODO(), "git@github.com:argoproj/argoproj.git") + repository, err = testee.GetRepository(context.TODO(), "git@github.com:argoproj/argoproj.git", "") assert.NoError(t, err) assert.NotNil(t, repository) assert.Equal(t, "UserManagedRepo", repository.Name) assert.Equal(t, "git@github.com:argoproj/argoproj.git", repository.Repo) assert.Equal(t, "someOtherUsername", repository.Username) assert.Equal(t, "someOtherPassword", repository.Password) + + repository, err = testee.GetRepository(context.TODO(), "git@github.com:argoproj/argo-cd.git", "testProject") + assert.NoError(t, err) + assert.NotNil(t, repository) + assert.Equal(t, "Scoped ArgoCD", repository.Name) + assert.Equal(t, "git@github.com:argoproj/argo-cd.git", repository.Repo) + assert.Equal(t, "someScopedUsername", repository.Username) + assert.Equal(t, "someScopedPassword", repository.Password) + assert.Equal(t, "testProject", repository.Project) + + repository, err = testee.GetRepository(context.TODO(), "git@github.com:argoproj/argoproj.git", "testProject") + assert.NoError(t, err) + assert.NotNil(t, repository) + assert.Equal(t, "Scoped UserManagedRepo", repository.Name) + assert.Equal(t, "git@github.com:argoproj/argoproj.git", repository.Repo) + assert.Equal(t, "someOtherUsername", repository.Username) + assert.Equal(t, "someOtherPassword", repository.Password) + assert.Equal(t, "testProject", repository.Project) } func TestSecretsRepositoryBackend_ListRepositories(t *testing.T) { @@ -209,7 +256,7 @@ func TestSecretsRepositoryBackend_ListRepositories(t *testing.T) { &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: testNamespace, - Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git"), + Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git", ""), Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepository}, }, @@ -268,12 +315,26 @@ func TestSecretsRepositoryBackend_UpdateRepository(t *testing.T) { Username: "someUsername", Password: "somePassword", } + managedProjectRepository := &appsv1.Repository{ + Name: "Managed", + Repo: "git@github.com:argoproj/argo-cd.git", + Username: "someUsername", + Password: "somePassword", + Project: "someProject", + } userProvidedRepository := &appsv1.Repository{ Name: "User Provided", Repo: "git@github.com:argoproj/argoproj.git", Username: "someOtherUsername", Password: "someOtherPassword", } + userProvidedProjectRepository := &appsv1.Repository{ + Name: "User Provided", + Repo: "git@github.com:argoproj/argoproj.git", + Username: "someOtherUsername", + Password: "someOtherPassword", + Project: "someProject", + } newRepository := &appsv1.Repository{ Name: "New", Repo: "git@github.com:argoproj/argo-events.git", @@ -281,8 +342,9 @@ func TestSecretsRepositoryBackend_UpdateRepository(t *testing.T) { Password: "bar", } - managedSecretName := RepoURLToSecretName(repoSecretPrefix, managedRepository.Repo) - newSecretName := RepoURLToSecretName(repoSecretPrefix, newRepository.Repo) + managedSecretName := RepoURLToSecretName(repoSecretPrefix, managedRepository.Repo, "") + managedProjectSecretName := RepoURLToSecretName(repoSecretPrefix, managedProjectRepository.Repo, "someProject") + newSecretName := RepoURLToSecretName(repoSecretPrefix, newRepository.Repo, "") repoSecrets := []runtime.Object{ &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -298,6 +360,21 @@ func TestSecretsRepositoryBackend_UpdateRepository(t *testing.T) { "password": []byte(managedRepository.Password), }, }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: managedProjectSecretName, + Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, + Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepository}, + }, + Data: map[string][]byte{ + "name": []byte(managedProjectRepository.Name), + "url": []byte(managedProjectRepository.Repo), + "username": []byte(managedProjectRepository.Username), + "password": []byte(managedProjectRepository.Password), + "project": []byte(managedProjectRepository.Project), + }, + }, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: testNamespace, @@ -311,6 +388,20 @@ func TestSecretsRepositoryBackend_UpdateRepository(t *testing.T) { "password": []byte(userProvidedRepository.Password), }, }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "user-managed-scoped", + Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepository}, + }, + Data: map[string][]byte{ + "name": []byte(userProvidedProjectRepository.Name), + "url": []byte(userProvidedProjectRepository.Repo), + "username": []byte(userProvidedProjectRepository.Username), + "password": []byte(userProvidedProjectRepository.Password), + "project": []byte(userProvidedProjectRepository.Project), + }, + }, } clientset := getClientset(map[string]string{}, repoSecrets...) @@ -350,10 +441,33 @@ func TestSecretsRepositoryBackend_UpdateRepository(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, secret) assert.Equal(t, "foo", string(secret.Data["username"])) + + managedProjectRepository.Username = "newUsername" + updateRepository, err = testee.UpdateRepository(context.TODO(), managedProjectRepository) + assert.NoError(t, err) + assert.Same(t, managedProjectRepository, updateRepository) + assert.Equal(t, managedProjectRepository.Username, updateRepository.Username) + + secret, err = clientset.CoreV1().Secrets(testNamespace).Get(context.TODO(), managedProjectSecretName, metav1.GetOptions{}) + assert.NoError(t, err) + assert.NotNil(t, secret) + assert.Equal(t, "newUsername", string(secret.Data["username"])) + + userProvidedProjectRepository.Username = "newUsernameScoped" + updateRepository, err = testee.UpdateRepository(context.TODO(), userProvidedProjectRepository) + assert.NoError(t, err) + assert.Same(t, userProvidedProjectRepository, updateRepository) + assert.Equal(t, userProvidedProjectRepository.Username, updateRepository.Username) + + secret, err = clientset.CoreV1().Secrets(testNamespace).Get(context.TODO(), "user-managed-scoped", metav1.GetOptions{}) + assert.NoError(t, err) + assert.NotNil(t, secret) + assert.Equal(t, "newUsernameScoped", string(secret.Data["username"])) } func TestSecretsRepositoryBackend_DeleteRepository(t *testing.T) { - managedSecretName := RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git") + managedSecretName := RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git", "") + managedScopedSecretName := RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git", "someProject") repoSecrets := []runtime.Object{ &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -369,6 +483,21 @@ func TestSecretsRepositoryBackend_DeleteRepository(t *testing.T) { "password": []byte("somePassword"), }, }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: managedScopedSecretName, + Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, + Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepository}, + }, + Data: map[string][]byte{ + "name": []byte("ArgoCD"), + "url": []byte("git@github.com:argoproj/argo-cd.git"), + "username": []byte("someUsername"), + "password": []byte("somePassword"), + "project": []byte("someProject"), + }, + }, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: testNamespace, @@ -391,13 +520,22 @@ func TestSecretsRepositoryBackend_DeleteRepository(t *testing.T) { settingsMgr: settings.NewSettingsManager(context.TODO(), clientset, testNamespace), }} - err := testee.DeleteRepository(context.TODO(), "git@github.com:argoproj/argo-cd.git") + err := testee.DeleteRepository(context.TODO(), "git@github.com:argoproj/argo-cd.git", "") assert.NoError(t, err) _, err = clientset.CoreV1().Secrets(testNamespace).Get(context.TODO(), managedSecretName, metav1.GetOptions{}) assert.Error(t, err) - err = testee.DeleteRepository(context.TODO(), "git@github.com:argoproj/argoproj.git") + _, err = clientset.CoreV1().Secrets(testNamespace).Get(context.TODO(), managedScopedSecretName, metav1.GetOptions{}) + assert.NoError(t, err) + + err = testee.DeleteRepository(context.TODO(), "git@github.com:argoproj/argo-cd.git", "someProject") + assert.NoError(t, err) + + _, err = clientset.CoreV1().Secrets(testNamespace).Get(context.TODO(), managedScopedSecretName, metav1.GetOptions{}) + assert.Error(t, err) + + err = testee.DeleteRepository(context.TODO(), "git@github.com:argoproj/argoproj.git", "") assert.NoError(t, err) secret, err := clientset.CoreV1().Secrets(testNamespace).Get(context.TODO(), "user-managed", metav1.GetOptions{}) @@ -450,7 +588,7 @@ func TestSecretsRepositoryBackend_CreateRepoCreds(t *testing.T) { secret, err := clientset.CoreV1().Secrets(testNamespace).Get( context.TODO(), - RepoURLToSecretName(credSecretPrefix, testCase.repoCreds.URL), + RepoURLToSecretName(credSecretPrefix, testCase.repoCreds.URL, ""), metav1.GetOptions{}, ) assert.NotNil(t, secret) @@ -490,7 +628,7 @@ func TestSecretsRepositoryBackend_GetRepoCreds(t *testing.T) { &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: testNamespace, - Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj"), + Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj", ""), Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepoCreds}, }, @@ -541,7 +679,7 @@ func TestSecretsRepositoryBackend_ListRepoCreds(t *testing.T) { &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: testNamespace, - Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj"), + Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj", ""), Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepoCreds}, }, @@ -596,8 +734,8 @@ func TestSecretsRepositoryBackend_UpdateRepoCreds(t *testing.T) { Password: "bar", } - managedCredsName := RepoURLToSecretName(credSecretPrefix, managedCreds.URL) - newCredsName := RepoURLToSecretName(credSecretPrefix, newCreds.URL) + managedCredsName := RepoURLToSecretName(credSecretPrefix, managedCreds.URL, "") + newCredsName := RepoURLToSecretName(credSecretPrefix, newCreds.URL, "") repoCredSecrets := []runtime.Object{ &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -666,7 +804,7 @@ func TestSecretsRepositoryBackend_UpdateRepoCreds(t *testing.T) { } func TestSecretsRepositoryBackend_DeleteRepoCreds(t *testing.T) { - managedSecretName := RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git") + managedSecretName := RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git", "") repoSecrets := []runtime.Object{ &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -722,7 +860,7 @@ func TestSecretsRepositoryBackend_GetAllHelmRepoCreds(t *testing.T) { &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: testNamespace, - Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj"), + Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj", ""), Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepoCreds}, }, @@ -736,7 +874,7 @@ func TestSecretsRepositoryBackend_GetAllHelmRepoCreds(t *testing.T) { &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: testNamespace, - Name: RepoURLToSecretName(repoSecretPrefix, "git@gitlab.com"), + Name: RepoURLToSecretName(repoSecretPrefix, "git@gitlab.com", ""), Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepoCreds}, }, diff --git a/util/db/repository_test.go b/util/db/repository_test.go index 6853fbfee785a..da95254a5eb2a 100644 --- a/util/db/repository_test.go +++ b/util/db/repository_test.go @@ -77,7 +77,7 @@ func TestDb_CreateRepository(t *testing.T) { // New repositories should be now stored as secrets secret, err := clientset.CoreV1().Secrets(testNamespace).Get( context.TODO(), - RepoURLToSecretName(repoSecretPrefix, input.Repo), + RepoURLToSecretName(repoSecretPrefix, input.Repo, ""), metav1.GetOptions{}, ) assert.NotNil(t, secret) @@ -93,17 +93,17 @@ func TestDb_GetRepository(t *testing.T) { settingsMgr: settingsManager, } - repository, err := testee.GetRepository(context.TODO(), "git@github.com:argoproj/argoproj.git") + repository, err := testee.GetRepository(context.TODO(), "git@github.com:argoproj/argoproj.git", "") assert.NoError(t, err) assert.NotNil(t, repository) assert.Equal(t, "OtherRepo", repository.Name) - repository, err = testee.GetRepository(context.TODO(), "git@github.com:argoproj/argo-cd.git") + repository, err = testee.GetRepository(context.TODO(), "git@github.com:argoproj/argo-cd.git", "") assert.NoError(t, err) assert.NotNil(t, repository) assert.Equal(t, "SomeRepo", repository.Name) - repository, err = testee.GetRepository(context.TODO(), "git@github.com:argoproj/not-existing.git") + repository, err = testee.GetRepository(context.TODO(), "git@github.com:argoproj/not-existing.git", "") assert.NoError(t, err) assert.NotNil(t, repository) assert.Equal(t, "git@github.com:argoproj/not-existing.git", repository.Repo) @@ -189,14 +189,14 @@ func TestDb_DeleteRepository(t *testing.T) { settingsMgr: settingsManager, } - err := testee.DeleteRepository(context.TODO(), "git@github.com:argoproj/argoproj.git") + err := testee.DeleteRepository(context.TODO(), "git@github.com:argoproj/argoproj.git", "") assert.NoError(t, err) repositories, err := settingsManager.GetRepositories() assert.NoError(t, err) assert.Empty(t, repositories) - err = testee.DeleteRepository(context.TODO(), "git@github.com:argoproj/argo-cd.git") + err = testee.DeleteRepository(context.TODO(), "git@github.com:argoproj/argo-cd.git", "") assert.NoError(t, err) _, err = clientset.CoreV1().Secrets(testNamespace).Get(context.TODO(), "some-repo-secret", metav1.GetOptions{}) @@ -249,18 +249,63 @@ func TestDb_GetRepositoryCredentials(t *testing.T) { } func TestRepoURLToSecretName(t *testing.T) { - tables := map[string]string{ - "git://git@github.com:argoproj/ARGO-cd.git": "repo-83273445", - "https://github.com/argoproj/ARGO-cd": "repo-1890113693", - "https://github.com/argoproj/argo-cd": "repo-42374749", - "https://github.com/argoproj/argo-cd.git": "repo-821842295", - "https://github.com/argoproj/argo_cd.git": "repo-1049844989", - "ssh://git@github.com/argoproj/argo-cd.git": "repo-3569564120", - } - - for k, v := range tables { - if sn := RepoURLToSecretName(repoSecretPrefix, k); sn != v { - t.Errorf("Expected secret name %q for repo %q; instead, got %q", v, k, sn) + tables := []struct { + repoUrl string + secretName string + project string + }{{ + repoUrl: "git://git@github.com:argoproj/ARGO-cd.git", + secretName: "repo-83273445", + project: "", + }, { + repoUrl: "git://git@github.com:argoproj/ARGO-cd.git", + secretName: "repo-2733415816", + project: "foobar", + }, { + repoUrl: "https://github.com/argoproj/ARGO-cd", + secretName: "repo-1890113693", + project: "", + }, { + repoUrl: "https://github.com/argoproj/ARGO-cd", + secretName: "repo-4161185408", + project: "foobar", + }, { + repoUrl: "https://github.com/argoproj/argo-cd", + secretName: "repo-42374749", + project: "", + }, { + repoUrl: "https://github.com/argoproj/argo-cd", + secretName: "repo-1894545728", + project: "foobar", + }, { + repoUrl: "https://github.com/argoproj/argo-cd.git", + secretName: "repo-821842295", + project: "", + }, { + repoUrl: "https://github.com/argoproj/argo-cd.git", + secretName: "repo-1474166686", + project: "foobar", + }, { + repoUrl: "https://github.com/argoproj/argo_cd.git", + secretName: "repo-1049844989", + project: "", + }, { + repoUrl: "https://github.com/argoproj/argo_cd.git", + secretName: "repo-3916272608", + project: "foobar", + }, { + repoUrl: "ssh://git@github.com/argoproj/argo-cd.git", + secretName: "repo-3569564120", + project: "", + }, { + repoUrl: "ssh://git@github.com/argoproj/argo-cd.git", + secretName: "repo-754834421", + project: "foobar", + }} + + for _, v := range tables { + if sn := RepoURLToSecretName(repoSecretPrefix, v.repoUrl, v.project); sn != v.secretName { + t.Errorf("Expected secret name %q for repo %q; instead, got %q", v.secretName, v.repoUrl, sn) } } } @@ -274,7 +319,7 @@ func Test_CredsURLToSecretName(t *testing.T) { } for k, v := range tables { - if sn := RepoURLToSecretName(credSecretPrefix, k); sn != v { + if sn := RepoURLToSecretName(credSecretPrefix, k, ""); sn != v { t.Errorf("Expected secret name %q for repo %q; instead, got %q", v, k, sn) } } diff --git a/util/helm/client.go b/util/helm/client.go index f04effa30ec58..976fc1ec85782 100644 --- a/util/helm/client.go +++ b/util/helm/client.go @@ -56,8 +56,8 @@ type indexCache interface { } type Client interface { - CleanChartCache(chart string, version string) error - ExtractChart(chart string, version string, passCredentials bool, manifestMaxExtractedSize int64, disableManifestMaxExtractedSize bool) (string, argoio.Closer, error) + CleanChartCache(chart string, version string, project string) error + ExtractChart(chart string, version string, project string, passCredentials bool, manifestMaxExtractedSize int64, disableManifestMaxExtractedSize bool) (string, argoio.Closer, error) GetIndex(noCache bool, maxIndexSize int64) (*Index, error) GetTags(chart string, noCache bool) (*TagsList, error) TestHelmOCI() (bool, error) @@ -119,8 +119,8 @@ func fileExist(filePath string) (bool, error) { return true, nil } -func (c *nativeHelmChart) CleanChartCache(chart string, version string) error { - cachePath, err := c.getCachedChartPath(chart, version) +func (c *nativeHelmChart) CleanChartCache(chart string, version string, project string) error { + cachePath, err := c.getCachedChartPath(chart, version, project) if err != nil { return err } @@ -141,7 +141,7 @@ func untarChart(tempDir string, cachedChartPath string, manifestMaxExtractedSize return files.Untgz(tempDir, reader, manifestMaxExtractedSize, false) } -func (c *nativeHelmChart) ExtractChart(chart string, version string, passCredentials bool, manifestMaxExtractedSize int64, disableManifestMaxExtractedSize bool) (string, argoio.Closer, error) { +func (c *nativeHelmChart) ExtractChart(chart string, version string, project string, passCredentials bool, manifestMaxExtractedSize int64, disableManifestMaxExtractedSize bool) (string, argoio.Closer, error) { // always use Helm V3 since we don't have chart content to determine correct Helm version helmCmd, err := NewCmdWithVersion("", HelmV3, c.enableOci, c.proxy) @@ -161,7 +161,7 @@ func (c *nativeHelmChart) ExtractChart(chart string, version string, passCredent return "", nil, err } - cachedChartPath, err := c.getCachedChartPath(chart, version) + cachedChartPath, err := c.getCachedChartPath(chart, version, project) if err != nil { return "", nil, err } @@ -376,8 +376,8 @@ func normalizeChartName(chart string) string { return nc } -func (c *nativeHelmChart) getCachedChartPath(chart string, version string) (string, error) { - keyData, err := json.Marshal(map[string]string{"url": c.repoURL, "chart": chart, "version": version}) +func (c *nativeHelmChart) getCachedChartPath(chart string, version string, project string) (string, error) { + keyData, err := json.Marshal(map[string]string{"url": c.repoURL, "chart": chart, "version": version, "project": project}) if err != nil { return "", err } diff --git a/util/helm/client_test.go b/util/helm/client_test.go index ad613ca3bd7eb..a5c58fc8b879b 100644 --- a/util/helm/client_test.go +++ b/util/helm/client_test.go @@ -79,7 +79,7 @@ func TestIndex(t *testing.T) { func Test_nativeHelmChart_ExtractChart(t *testing.T) { client := NewClient("https://argoproj.github.io/argo-helm", Creds{}, false, "") - path, closer, err := client.ExtractChart("argo-cd", "0.7.1", false, math.MaxInt64, true) + path, closer, err := client.ExtractChart("argo-cd", "0.7.1", "", false, math.MaxInt64, true) assert.NoError(t, err) defer io.Close(closer) info, err := os.Stat(path) @@ -89,13 +89,13 @@ func Test_nativeHelmChart_ExtractChart(t *testing.T) { func Test_nativeHelmChart_ExtractChartWithLimiter(t *testing.T) { client := NewClient("https://argoproj.github.io/argo-helm", Creds{}, false, "") - _, _, err := client.ExtractChart("argo-cd", "0.7.1", false, 100, false) + _, _, err := client.ExtractChart("argo-cd", "0.7.1", "", false, 100, false) assert.Error(t, err, "error while iterating on tar reader: unexpected EOF") } func Test_nativeHelmChart_ExtractChart_insecure(t *testing.T) { client := NewClient("https://argoproj.github.io/argo-helm", Creds{InsecureSkipVerify: true}, false, "") - path, closer, err := client.ExtractChart("argo-cd", "0.7.1", false, math.MaxInt64, true) + path, closer, err := client.ExtractChart("argo-cd", "0.7.1", "", false, math.MaxInt64, true) assert.NoError(t, err) defer io.Close(closer) info, err := os.Stat(path) diff --git a/util/helm/mocks/Client.go b/util/helm/mocks/Client.go index ad2c74bcb8e0f..0e6d13822866f 100644 --- a/util/helm/mocks/Client.go +++ b/util/helm/mocks/Client.go @@ -14,17 +14,17 @@ type Client struct { mock.Mock } -// CleanChartCache provides a mock function with given fields: chart, version -func (_m *Client) CleanChartCache(chart string, version string) error { - ret := _m.Called(chart, version) +// CleanChartCache provides a mock function with given fields: chart, version, project +func (_m *Client) CleanChartCache(chart string, version string, project string) error { + ret := _m.Called(chart, version, project) if len(ret) == 0 { panic("no return value specified for CleanChartCache") } var r0 error - if rf, ok := ret.Get(0).(func(string, string) error); ok { - r0 = rf(chart, version) + if rf, ok := ret.Get(0).(func(string, string, string) error); ok { + r0 = rf(chart, version, project) } else { r0 = ret.Error(0) } @@ -32,9 +32,9 @@ func (_m *Client) CleanChartCache(chart string, version string) error { return r0 } -// ExtractChart provides a mock function with given fields: chart, version, passCredentials, manifestMaxExtractedSize, disableManifestMaxExtractedSize -func (_m *Client) ExtractChart(chart string, version string, passCredentials bool, manifestMaxExtractedSize int64, disableManifestMaxExtractedSize bool) (string, io.Closer, error) { - ret := _m.Called(chart, version, passCredentials, manifestMaxExtractedSize, disableManifestMaxExtractedSize) +// ExtractChart provides a mock function with given fields: chart, version, project, passCredentials, manifestMaxExtractedSize, disableManifestMaxExtractedSize +func (_m *Client) ExtractChart(chart string, version string, project string, passCredentials bool, manifestMaxExtractedSize int64, disableManifestMaxExtractedSize bool) (string, io.Closer, error) { + ret := _m.Called(chart, version, project, passCredentials, manifestMaxExtractedSize, disableManifestMaxExtractedSize) if len(ret) == 0 { panic("no return value specified for ExtractChart") @@ -43,25 +43,25 @@ func (_m *Client) ExtractChart(chart string, version string, passCredentials boo var r0 string var r1 io.Closer var r2 error - if rf, ok := ret.Get(0).(func(string, string, bool, int64, bool) (string, io.Closer, error)); ok { - return rf(chart, version, passCredentials, manifestMaxExtractedSize, disableManifestMaxExtractedSize) + if rf, ok := ret.Get(0).(func(string, string, string, bool, int64, bool) (string, io.Closer, error)); ok { + return rf(chart, version, project, passCredentials, manifestMaxExtractedSize, disableManifestMaxExtractedSize) } - if rf, ok := ret.Get(0).(func(string, string, bool, int64, bool) string); ok { - r0 = rf(chart, version, passCredentials, manifestMaxExtractedSize, disableManifestMaxExtractedSize) + if rf, ok := ret.Get(0).(func(string, string, string, bool, int64, bool) string); ok { + r0 = rf(chart, version, project, passCredentials, manifestMaxExtractedSize, disableManifestMaxExtractedSize) } else { r0 = ret.Get(0).(string) } - if rf, ok := ret.Get(1).(func(string, string, bool, int64, bool) io.Closer); ok { - r1 = rf(chart, version, passCredentials, manifestMaxExtractedSize, disableManifestMaxExtractedSize) + if rf, ok := ret.Get(1).(func(string, string, string, bool, int64, bool) io.Closer); ok { + r1 = rf(chart, version, project, passCredentials, manifestMaxExtractedSize, disableManifestMaxExtractedSize) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(io.Closer) } } - if rf, ok := ret.Get(2).(func(string, string, bool, int64, bool) error); ok { - r2 = rf(chart, version, passCredentials, manifestMaxExtractedSize, disableManifestMaxExtractedSize) + if rf, ok := ret.Get(2).(func(string, string, string, bool, int64, bool) error); ok { + r2 = rf(chart, version, project, passCredentials, manifestMaxExtractedSize, disableManifestMaxExtractedSize) } else { r2 = ret.Error(2) } diff --git a/util/notification/argocd/mocks/Service.go b/util/notification/argocd/mocks/Service.go index 54515ad89434a..014e156930894 100644 --- a/util/notification/argocd/mocks/Service.go +++ b/util/notification/argocd/mocks/Service.go @@ -17,9 +17,9 @@ type Service struct { mock.Mock } -// GetAppDetails provides a mock function with given fields: ctx, appSource, appName -func (_m *Service) GetAppDetails(ctx context.Context, appSource *v1alpha1.ApplicationSource, appName string) (*shared.AppDetail, error) { - ret := _m.Called(ctx, appSource, appName) +// GetAppDetails provides a mock function with given fields: ctx, app +func (_m *Service) GetAppDetails(ctx context.Context, app *v1alpha1.Application) (*shared.AppDetail, error) { + ret := _m.Called(ctx, app) if len(ret) == 0 { panic("no return value specified for GetAppDetails") @@ -27,19 +27,19 @@ func (_m *Service) GetAppDetails(ctx context.Context, appSource *v1alpha1.Applic var r0 *shared.AppDetail var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *v1alpha1.ApplicationSource, string) (*shared.AppDetail, error)); ok { - return rf(ctx, appSource, appName) + if rf, ok := ret.Get(0).(func(context.Context, *v1alpha1.Application) (*shared.AppDetail, error)); ok { + return rf(ctx, app) } - if rf, ok := ret.Get(0).(func(context.Context, *v1alpha1.ApplicationSource, string) *shared.AppDetail); ok { - r0 = rf(ctx, appSource, appName) + if rf, ok := ret.Get(0).(func(context.Context, *v1alpha1.Application) *shared.AppDetail); ok { + r0 = rf(ctx, app) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*shared.AppDetail) } } - if rf, ok := ret.Get(1).(func(context.Context, *v1alpha1.ApplicationSource, string) error); ok { - r1 = rf(ctx, appSource, appName) + if rf, ok := ret.Get(1).(func(context.Context, *v1alpha1.Application) error); ok { + r1 = rf(ctx, app) } else { r1 = ret.Error(1) } @@ -47,9 +47,9 @@ func (_m *Service) GetAppDetails(ctx context.Context, appSource *v1alpha1.Applic return r0, r1 } -// GetCommitMetadata provides a mock function with given fields: ctx, repoURL, commitSHA -func (_m *Service) GetCommitMetadata(ctx context.Context, repoURL string, commitSHA string) (*shared.CommitMetadata, error) { - ret := _m.Called(ctx, repoURL, commitSHA) +// GetCommitMetadata provides a mock function with given fields: ctx, repoURL, commitSHA, project +func (_m *Service) GetCommitMetadata(ctx context.Context, repoURL string, commitSHA string, project string) (*shared.CommitMetadata, error) { + ret := _m.Called(ctx, repoURL, commitSHA, project) if len(ret) == 0 { panic("no return value specified for GetCommitMetadata") @@ -57,19 +57,19 @@ func (_m *Service) GetCommitMetadata(ctx context.Context, repoURL string, commit var r0 *shared.CommitMetadata var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) (*shared.CommitMetadata, error)); ok { - return rf(ctx, repoURL, commitSHA) + if rf, ok := ret.Get(0).(func(context.Context, string, string, string) (*shared.CommitMetadata, error)); ok { + return rf(ctx, repoURL, commitSHA, project) } - if rf, ok := ret.Get(0).(func(context.Context, string, string) *shared.CommitMetadata); ok { - r0 = rf(ctx, repoURL, commitSHA) + if rf, ok := ret.Get(0).(func(context.Context, string, string, string) *shared.CommitMetadata); ok { + r0 = rf(ctx, repoURL, commitSHA, project) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*shared.CommitMetadata) } } - if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { - r1 = rf(ctx, repoURL, commitSHA) + if rf, ok := ret.Get(1).(func(context.Context, string, string, string) error); ok { + r1 = rf(ctx, repoURL, commitSHA, project) } else { r1 = ret.Error(1) } diff --git a/util/notification/argocd/service.go b/util/notification/argocd/service.go index 6c70ae8d66446..dd4f7fa202129 100644 --- a/util/notification/argocd/service.go +++ b/util/notification/argocd/service.go @@ -16,8 +16,8 @@ import ( //go:generate go run github.com/vektra/mockery/v2@v2.40.2 --name=Service type Service interface { - GetCommitMetadata(ctx context.Context, repoURL string, commitSHA string) (*shared.CommitMetadata, error) - GetAppDetails(ctx context.Context, appSource *v1alpha1.ApplicationSource, appName string) (*shared.AppDetail, error) + GetCommitMetadata(ctx context.Context, repoURL string, commitSHA string, project string) (*shared.CommitMetadata, error) + GetAppDetails(ctx context.Context, app *v1alpha1.Application) (*shared.AppDetail, error) } func NewArgoCDService(clientset kubernetes.Interface, namespace string, repoClientset apiclient.Clientset) (*argoCDService, error) { @@ -46,9 +46,9 @@ type argoCDService struct { dispose func() } -func (svc *argoCDService) GetCommitMetadata(ctx context.Context, repoURL string, commitSHA string) (*shared.CommitMetadata, error) { +func (svc *argoCDService) GetCommitMetadata(ctx context.Context, repoURL string, commitSHA string, project string) (*shared.CommitMetadata, error) { argocdDB := db.NewDB(svc.namespace, svc.settingsMgr, svc.clientset) - repo, err := argocdDB.GetRepository(ctx, repoURL) + repo, err := argocdDB.GetRepository(ctx, repoURL, project) if err != nil { return nil, err } @@ -75,9 +75,11 @@ func (svc *argoCDService) getKustomizeOptions(source *v1alpha1.ApplicationSource return kustomizeSettings.GetOptions(*source) } -func (svc *argoCDService) GetAppDetails(ctx context.Context, appSource *v1alpha1.ApplicationSource, appName string) (*shared.AppDetail, error) { +func (svc *argoCDService) GetAppDetails(ctx context.Context, app *v1alpha1.Application) (*shared.AppDetail, error) { + appSource := app.Spec.GetSourcePtrByIndex(0) + argocdDB := db.NewDB(svc.namespace, svc.settingsMgr, svc.clientset) - repo, err := argocdDB.GetRepository(ctx, appSource.RepoURL) + repo, err := argocdDB.GetRepository(ctx, appSource.RepoURL, app.Spec.Project) if err != nil { return nil, err } @@ -94,7 +96,7 @@ func (svc *argoCDService) GetAppDetails(ctx context.Context, appSource *v1alpha1 return nil, err } appDetail, err := svc.repoServerClient.GetAppDetails(ctx, &apiclient.RepoServerAppDetailsQuery{ - AppName: appName, + AppName: app.Name, Repo: repo, Source: appSource, Repos: helmRepos, diff --git a/util/notification/expression/repo/repo.go b/util/notification/expression/repo/repo.go index ada7a353e3d32..d633562737f1e 100644 --- a/util/notification/expression/repo/repo.go +++ b/util/notification/expression/repo/repo.go @@ -23,25 +23,25 @@ var ( gitSuffix = regexp.MustCompile(`\.git$`) ) -func getApplicationSourceAndName(obj *unstructured.Unstructured) (*v1alpha1.ApplicationSource, string, error) { +func getApplication(obj *unstructured.Unstructured) (*v1alpha1.Application, error) { data, err := json.Marshal(obj) if err != nil { - return nil, "", err + return nil, err } application := &v1alpha1.Application{} err = json.Unmarshal(data, application) if err != nil { - return nil, "", err + return nil, err } - return application.Spec.GetSourcePtrByIndex(0), application.GetName(), nil + return application, nil } -func getAppDetails(app *unstructured.Unstructured, argocdService service.Service) (*shared.AppDetail, error) { - appSource, appName, err := getApplicationSourceAndName(app) +func getAppDetails(un *unstructured.Unstructured, argocdService service.Service) (*shared.AppDetail, error) { + app, err := getApplication(un) if err != nil { return nil, err } - appDetail, err := argocdService.GetAppDetails(context.Background(), appSource, appName) + appDetail, err := argocdService.GetAppDetails(context.Background(), app) if err != nil { return nil, err } @@ -56,7 +56,15 @@ func getCommitMetadata(commitSHA string, app *unstructured.Unstructured, argocdS if !ok { panic(errors.New("failed to get application source repo URL")) } - meta, err := argocdService.GetCommitMetadata(context.Background(), repoURL, commitSHA) + project, ok, err := unstructured.NestedString(app.Object, "spec", "project") + if err != nil { + return nil, err + } + if !ok { + panic(errors.New("failed to get application project")) + } + + meta, err := argocdService.GetCommitMetadata(context.Background(), repoURL, commitSHA, project) if err != nil { return nil, err } diff --git a/util/webhook/webhook.go b/util/webhook/webhook.go index dab69d7b131b7..b83e05d2dfb22 100644 --- a/util/webhook/webhook.go +++ b/util/webhook/webhook.go @@ -344,7 +344,7 @@ func (a *ArgoCDWebhookHandler) storePreviouslyCachedManifests(app *v1alpha1.Appl return fmt.Errorf("error getting cluster info: %w", err) } - refSources, err := argo.GetRefSources(context.Background(), app.Spec, a.db) + refSources, err := argo.GetRefSources(context.Background(), app.Spec, a.db.GetRepository) if err != nil { return fmt.Errorf("error getting ref sources: %w", err) }