diff --git a/internal/services/configstore/command/command.go b/internal/services/configstore/command/command.go index eba29a4..8cc6346 100644 --- a/internal/services/configstore/command/command.go +++ b/internal/services/configstore/command/command.go @@ -135,6 +135,9 @@ func (s *CommandHandler) CreateProject(ctx context.Context, project *types.Proje if !types.IsValidVisibility(project.Visibility) { return nil, util.NewErrBadRequest(errors.Errorf("invalid project visibility")) } + if !types.IsValidRemoteRepositoryConfigType(project.RemoteRepositoryConfigType) { + return nil, util.NewErrBadRequest(errors.Errorf("invalid project remote repository config type %q", project.RemoteRepositoryConfigType)) + } var cgt *datamanager.ChangeGroupsUpdateToken @@ -181,17 +184,22 @@ func (s *CommandHandler) CreateProject(ctx context.Context, project *types.Proje return util.NewErrBadRequest(errors.Errorf("project group with name %q, path %q already exists", pg.Name, pp)) } - // check that the linked account matches the remote source - user, err := s.readDB.GetUserByLinkedAccount(tx, project.LinkedAccountID) - if err != nil { - return errors.Wrapf(err, "failed to get user with linked account id %q", project.LinkedAccountID) - } - la, ok := user.LinkedAccounts[project.LinkedAccountID] - if !ok { - return util.NewErrBadRequest(errors.Errorf("linked account id %q for user %q doesn't exist", project.LinkedAccountID, user.Name)) - } - if la.RemoteSourceID != project.RemoteSourceID { - return util.NewErrBadRequest(errors.Errorf("linked account id %q remote source %q different than project remote source %q", project.LinkedAccountID, la.RemoteSourceID, project.RemoteSourceID)) + if project.RemoteRepositoryConfigType == types.RemoteRepositoryConfigTypeRemoteSource { + // check that the linked account matches the remote source + user, err := s.readDB.GetUserByLinkedAccount(tx, project.LinkedAccountID) + if err != nil { + return errors.Wrapf(err, "failed to get user with linked account id %q", project.LinkedAccountID) + } + if user == nil { + return util.NewErrBadRequest(errors.Errorf("user for linked account %q doesn't exist", project.LinkedAccountID)) + } + la, ok := user.LinkedAccounts[project.LinkedAccountID] + if !ok { + return util.NewErrBadRequest(errors.Errorf("linked account id %q for user %q doesn't exist", project.LinkedAccountID, user.Name)) + } + if la.RemoteSourceID != project.RemoteSourceID { + return util.NewErrBadRequest(errors.Errorf("linked account id %q remote source %q different than project remote source %q", project.LinkedAccountID, la.RemoteSourceID, project.RemoteSourceID)) + } } return nil diff --git a/internal/services/configstore/configstore_test.go b/internal/services/configstore/configstore_test.go index ae11c92..57e9d18 100644 --- a/internal/services/configstore/configstore_test.go +++ b/internal/services/configstore/configstore_test.go @@ -394,13 +394,13 @@ func TestProjectGroupsAndProjects(t *testing.T) { time.Sleep(2 * time.Second) t.Run("create a project in user root project group", func(t *testing.T) { - _, err := cs.ch.CreateProject(ctx, &types.Project{Name: "project01", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("user", user.Name)}, Visibility: types.VisibilityPublic}) + _, err := cs.ch.CreateProject(ctx, &types.Project{Name: "project01", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("user", user.Name)}, Visibility: types.VisibilityPublic, RemoteRepositoryConfigType: types.RemoteRepositoryConfigTypeManual}) if err != nil { t.Fatalf("unexpected err: %v", err) } }) t.Run("create a project in org root project group", func(t *testing.T) { - _, err := cs.ch.CreateProject(ctx, &types.Project{Name: "project01", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("org", org.Name)}, Visibility: types.VisibilityPublic}) + _, err := cs.ch.CreateProject(ctx, &types.Project{Name: "project01", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("org", org.Name)}, Visibility: types.VisibilityPublic, RemoteRepositoryConfigType: types.RemoteRepositoryConfigTypeManual}) if err != nil { t.Fatalf("unexpected err: %v", err) } @@ -418,13 +418,13 @@ func TestProjectGroupsAndProjects(t *testing.T) { } }) t.Run("create a project in user non root project group with same name as a root project", func(t *testing.T) { - _, err := cs.ch.CreateProject(ctx, &types.Project{Name: "project01", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("user", user.Name, "projectgroup01")}, Visibility: types.VisibilityPublic}) + _, err := cs.ch.CreateProject(ctx, &types.Project{Name: "project01", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("user", user.Name, "projectgroup01")}, Visibility: types.VisibilityPublic, RemoteRepositoryConfigType: types.RemoteRepositoryConfigTypeManual}) if err != nil { t.Fatalf("unexpected err: %+#v", err) } }) t.Run("create a project in org non root project group with same name as a root project", func(t *testing.T) { - _, err := cs.ch.CreateProject(ctx, &types.Project{Name: "project01", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("org", org.Name, "projectgroup01")}, Visibility: types.VisibilityPublic}) + _, err := cs.ch.CreateProject(ctx, &types.Project{Name: "project01", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("org", org.Name, "projectgroup01")}, Visibility: types.VisibilityPublic, RemoteRepositoryConfigType: types.RemoteRepositoryConfigTypeManual}) if err != nil { t.Fatalf("unexpected err: %v", err) } @@ -433,7 +433,7 @@ func TestProjectGroupsAndProjects(t *testing.T) { t.Run("create duplicated project in user root project group", func(t *testing.T) { projectName := "project01" expectedErr := fmt.Sprintf("project with name %q, path %q already exists", projectName, path.Join("user", user.Name, projectName)) - _, err := cs.ch.CreateProject(ctx, &types.Project{Name: projectName, Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("user", user.Name)}, Visibility: types.VisibilityPublic}) + _, err := cs.ch.CreateProject(ctx, &types.Project{Name: projectName, Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("user", user.Name)}, Visibility: types.VisibilityPublic, RemoteRepositoryConfigType: types.RemoteRepositoryConfigTypeManual}) if err.Error() != expectedErr { t.Fatalf("expected err %v, got err: %v", expectedErr, err) } @@ -441,7 +441,7 @@ func TestProjectGroupsAndProjects(t *testing.T) { t.Run("create duplicated project in org root project group", func(t *testing.T) { projectName := "project01" expectedErr := fmt.Sprintf("project with name %q, path %q already exists", projectName, path.Join("org", org.Name, projectName)) - _, err := cs.ch.CreateProject(ctx, &types.Project{Name: projectName, Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("org", org.Name)}, Visibility: types.VisibilityPublic}) + _, err := cs.ch.CreateProject(ctx, &types.Project{Name: projectName, Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("org", org.Name)}, Visibility: types.VisibilityPublic, RemoteRepositoryConfigType: types.RemoteRepositoryConfigTypeManual}) if err.Error() != expectedErr { t.Fatalf("expected err %v, got err: %v", expectedErr, err) } @@ -450,7 +450,7 @@ func TestProjectGroupsAndProjects(t *testing.T) { t.Run("create duplicated project in user non root project group", func(t *testing.T) { projectName := "project01" expectedErr := fmt.Sprintf("project with name %q, path %q already exists", projectName, path.Join("user", user.Name, "projectgroup01", projectName)) - _, err := cs.ch.CreateProject(ctx, &types.Project{Name: projectName, Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("user", user.Name, "projectgroup01")}, Visibility: types.VisibilityPublic}) + _, err := cs.ch.CreateProject(ctx, &types.Project{Name: projectName, Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("user", user.Name, "projectgroup01")}, Visibility: types.VisibilityPublic, RemoteRepositoryConfigType: types.RemoteRepositoryConfigTypeManual}) if err.Error() != expectedErr { t.Fatalf("expected err %v, got err: %v", expectedErr, err) } @@ -458,7 +458,7 @@ func TestProjectGroupsAndProjects(t *testing.T) { t.Run("create duplicated project in org non root project group", func(t *testing.T) { projectName := "project01" expectedErr := fmt.Sprintf("project with name %q, path %q already exists", projectName, path.Join("org", org.Name, "projectgroup01", projectName)) - _, err := cs.ch.CreateProject(ctx, &types.Project{Name: projectName, Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("org", org.Name, "projectgroup01")}, Visibility: types.VisibilityPublic}) + _, err := cs.ch.CreateProject(ctx, &types.Project{Name: projectName, Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("org", org.Name, "projectgroup01")}, Visibility: types.VisibilityPublic, RemoteRepositoryConfigType: types.RemoteRepositoryConfigTypeManual}) if err.Error() != expectedErr { t.Fatalf("expected err %v, got err: %v", expectedErr, err) } @@ -466,14 +466,14 @@ func TestProjectGroupsAndProjects(t *testing.T) { t.Run("create project in unexistent project group", func(t *testing.T) { expectedErr := `project group with id "unexistentid" doesn't exist` - _, err := cs.ch.CreateProject(ctx, &types.Project{Name: "project01", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: "unexistentid"}, Visibility: types.VisibilityPublic}) + _, err := cs.ch.CreateProject(ctx, &types.Project{Name: "project01", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: "unexistentid"}, Visibility: types.VisibilityPublic, RemoteRepositoryConfigType: types.RemoteRepositoryConfigTypeManual}) if err.Error() != expectedErr { t.Fatalf("expected err %v, got err: %v", expectedErr, err) } }) t.Run("create project without parent id specified", func(t *testing.T) { expectedErr := "project parent id required" - _, err := cs.ch.CreateProject(ctx, &types.Project{Name: "project01", Visibility: types.VisibilityPublic}) + _, err := cs.ch.CreateProject(ctx, &types.Project{Name: "project01", Visibility: types.VisibilityPublic, RemoteRepositoryConfigType: types.RemoteRepositoryConfigTypeManual}) if err.Error() != expectedErr { t.Fatalf("expected err %v, got err: %v", expectedErr, err) } @@ -488,7 +488,7 @@ func TestProjectGroupsAndProjects(t *testing.T) { wg := sync.WaitGroup{} for i := 0; i < 10; i++ { wg.Add(1) - go cs.ch.CreateProject(ctx, &types.Project{Name: "project02", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("user", user.Name)}, Visibility: types.VisibilityPublic}) + go cs.ch.CreateProject(ctx, &types.Project{Name: "project02", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("user", user.Name)}, Visibility: types.VisibilityPublic, RemoteRepositoryConfigType: types.RemoteRepositoryConfigTypeManual}) wg.Done() } wg.Wait() diff --git a/internal/services/gateway/command/project.go b/internal/services/gateway/command/project.go index 8c7e8ed..fa8cb23 100644 --- a/internal/services/gateway/command/project.go +++ b/internal/services/gateway/command/project.go @@ -98,13 +98,14 @@ func (c *CommandHandler) CreateProject(ctx context.Context, req *CreateProjectRe Type: types.ConfigTypeProjectGroup, ID: parentID, }, - Visibility: req.Visibility, - RemoteSourceID: rs.ID, - LinkedAccountID: la.ID, - RepositoryID: repo.ID, - RepositoryPath: req.RepoPath, - SkipSSHHostKeyCheck: req.SkipSSHHostKeyCheck, - SSHPrivateKey: string(privateKey), + Visibility: req.Visibility, + RemoteRepositoryConfigType: types.RemoteRepositoryConfigTypeRemoteSource, + RemoteSourceID: rs.ID, + LinkedAccountID: la.ID, + RepositoryID: repo.ID, + RepositoryPath: req.RepoPath, + SkipSSHHostKeyCheck: req.SkipSSHHostKeyCheck, + SSHPrivateKey: string(privateKey), } c.log.Infof("creating project") diff --git a/internal/services/types/types.go b/internal/services/types/types.go index 0e4962f..0547f23 100644 --- a/internal/services/types/types.go +++ b/internal/services/types/types.go @@ -148,6 +148,27 @@ type LinkedAccount struct { Oauth2AccessTokenExpiresAt time.Time `json:"oauth_2_access_token_expires_at,omitempty"` } +// RemoteRepositoryConfigType defines how a remote repository is configured and +// managed. Currently only "remotesource" is supported. +// In future other config types (like a fully manual config) could be supported. +type RemoteRepositoryConfigType string + +const ( + // RemoteRepositoryConfigTypeManual is currently only used for tests and not available for direct usage + RemoteRepositoryConfigTypeManual RemoteRepositoryConfigType = "manual" + RemoteRepositoryConfigTypeRemoteSource RemoteRepositoryConfigType = "remotesource" +) + +func IsValidRemoteRepositoryConfigType(t RemoteRepositoryConfigType) bool { + switch t { + case RemoteRepositoryConfigTypeManual: + case RemoteRepositoryConfigTypeRemoteSource: + default: + return false + } + return true +} + type Project struct { // The type version. Increase when a breaking change is done. Usually not // needed when adding fields. @@ -161,6 +182,8 @@ type Project struct { Visibility Visibility `json:"visibility,omitempty"` // Remote Repository fields + RemoteRepositoryConfigType RemoteRepositoryConfigType `json:"remote_repository_config_type,omitempty"` + RemoteSourceID string `json:"remote_source_id,omitempty"` LinkedAccountID string `json:"linked_account_id,omitempty"`