diff --git a/internal/services/configstore/action/project.go b/internal/services/configstore/action/project.go index 116ee06..bafc690 100644 --- a/internal/services/configstore/action/project.go +++ b/internal/services/configstore/action/project.go @@ -64,6 +64,24 @@ func (h *ActionHandler) ValidateProject(ctx context.Context, project *types.Proj return nil } +func (h *ActionHandler) GetProject(ctx context.Context, projectRef string) (*types.Project, error) { + var project *types.Project + err := h.readDB.Do(ctx, func(tx *db.Tx) error { + var err error + project, err = h.readDB.GetProject(tx, projectRef) + return err + }) + if err != nil { + return nil, err + } + + if project == nil { + return nil, util.NewErrNotFound(errors.Errorf("project %q doesn't exist", projectRef)) + } + + return project, nil +} + func (h *ActionHandler) CreateProject(ctx context.Context, project *types.Project) (*types.Project, error) { if err := h.ValidateProject(ctx, project); err != nil { return nil, err @@ -190,12 +208,7 @@ func (h *ActionHandler) UpdateProject(ctx context.Context, req *UpdateProjectReq if group == nil { return util.NewErrBadRequest(errors.Errorf("project group with id %q doesn't exist", req.Project.Parent.ID)) } - - // currently we don't support changing parent - // TODO(sgotti) handle project move (changed parent project group) - if p.Parent.ID != req.Project.Parent.ID { - return util.NewErrBadRequest(errors.Errorf("changing project parent isn't supported")) - } + req.Project.Parent.ID = group.ID groupPath, err := h.readDB.GetProjectGroupPath(tx, group) if err != nil { @@ -203,7 +216,7 @@ func (h *ActionHandler) UpdateProject(ctx context.Context, req *UpdateProjectReq } pp := path.Join(groupPath, req.Project.Name) - if p.Name != req.Project.Name { + if p.Name != req.Project.Name || p.Parent.ID != req.Project.Parent.ID { // check duplicate project name ap, err := h.readDB.GetProjectByName(tx, req.Project.Parent.ID, req.Project.Name) if err != nil { @@ -217,6 +230,26 @@ func (h *ActionHandler) UpdateProject(ctx context.Context, req *UpdateProjectReq // changegroup is the project path. Use "projectpath" prefix as it must // cover both projects and projectgroups cgNames := []string{util.EncodeSha256Hex("projectpath-" + pp)} + + // add new projectpath + if p.Parent.ID != req.Project.Parent.ID { + // get old parent project group + curGroup, err := h.readDB.GetProjectGroup(tx, p.Parent.ID) + if err != nil { + return err + } + if curGroup == nil { + return util.NewErrBadRequest(errors.Errorf("project group with id %q doesn't exist", p.Parent.ID)) + } + curGroupPath, err := h.readDB.GetProjectGroupPath(tx, curGroup) + if err != nil { + return err + } + pp := path.Join(curGroupPath, req.Project.Name) + + cgNames = append(cgNames, util.EncodeSha256Hex("projectpath-"+pp)) + } + cgt, err = h.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) if err != nil { return err diff --git a/internal/services/configstore/api/project.go b/internal/services/configstore/api/project.go index f8386a3..91e0042 100644 --- a/internal/services/configstore/api/project.go +++ b/internal/services/configstore/api/project.go @@ -30,7 +30,6 @@ import ( "github.com/gorilla/mux" "go.uber.org/zap" - errors "golang.org/x/xerrors" ) func projectResponse(ctx context.Context, readDB *readdb.ReadDB, project *types.Project) (*csapitypes.Project, error) { @@ -114,11 +113,12 @@ func getGlobalVisibility(readDB *readdb.ReadDB, tx *db.Tx, curVisibility types.V type ProjectHandler struct { log *zap.SugaredLogger + ah *action.ActionHandler readDB *readdb.ReadDB } -func NewProjectHandler(logger *zap.Logger, readDB *readdb.ReadDB) *ProjectHandler { - return &ProjectHandler{log: logger.Sugar(), readDB: readDB} +func NewProjectHandler(logger *zap.Logger, ah *action.ActionHandler, readDB *readdb.ReadDB) *ProjectHandler { + return &ProjectHandler{log: logger.Sugar(), ah: ah, readDB: readDB} } func (h *ProjectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -130,20 +130,9 @@ func (h *ProjectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - var project *types.Project - err = h.readDB.Do(ctx, func(tx *db.Tx) error { - var err error - project, err = h.readDB.GetProject(tx, projectRef) - return err - }) - if err != nil { + project, err := h.ah.GetProject(ctx, projectRef) + if httpError(w, err) { h.log.Errorf("err: %+v", err) - httpError(w, err) - return - } - - if project == nil { - httpError(w, util.NewErrNotFound(errors.Errorf("project %q doesn't exist", projectRef))) return } diff --git a/internal/services/configstore/configstore.go b/internal/services/configstore/configstore.go index 1033ef8..f919bf5 100644 --- a/internal/services/configstore/configstore.go +++ b/internal/services/configstore/configstore.go @@ -184,7 +184,7 @@ func (s *Configstore) setupDefaultRouter() http.Handler { updateProjectGroupHandler := api.NewUpdateProjectGroupHandler(logger, s.ah, s.readDB) deleteProjectGroupHandler := api.NewDeleteProjectGroupHandler(logger, s.ah) - projectHandler := api.NewProjectHandler(logger, s.readDB) + projectHandler := api.NewProjectHandler(logger, s.ah, s.readDB) createProjectHandler := api.NewCreateProjectHandler(logger, s.ah, s.readDB) updateProjectHandler := api.NewUpdateProjectHandler(logger, s.ah, s.readDB) deleteProjectHandler := api.NewDeleteProjectHandler(logger, s.ah) diff --git a/internal/services/configstore/configstore_test.go b/internal/services/configstore/configstore_test.go index c597cd1..2d82e70 100644 --- a/internal/services/configstore/configstore_test.go +++ b/internal/services/configstore/configstore_test.go @@ -57,7 +57,7 @@ func shutdownEtcd(tetcd *testutil.TestEmbeddedEtcd) { } } -func setupConfigstore(t *testing.T, ctx context.Context, dir string) (*Configstore, *testutil.TestEmbeddedEtcd) { +func setupConfigstore(ctx context.Context, t *testing.T, dir string) (*Configstore, *testutil.TestEmbeddedEtcd) { etcdDir, err := ioutil.TempDir(dir, "etcd") if err != nil { t.Fatalf("unexpected err: %v", err) @@ -550,7 +550,7 @@ func TestUser(t *testing.T) { ctx := context.Background() - cs, tetcd := setupConfigstore(t, ctx, dir) + cs, tetcd := setupConfigstore(ctx, t, dir) defer shutdownEtcd(tetcd) t.Logf("starting cs") @@ -608,7 +608,7 @@ func TestUser(t *testing.T) { }) } -func TestProjectGroupsAndProjects(t *testing.T) { +func TestProjectGroupsAndProjectsCreate(t *testing.T) { dir, err := ioutil.TempDir("", "agola") if err != nil { t.Fatalf("unexpected err: %v", err) @@ -617,7 +617,7 @@ func TestProjectGroupsAndProjects(t *testing.T) { ctx := context.Background() - cs, tetcd := setupConfigstore(t, ctx, dir) + cs, tetcd := setupConfigstore(ctx,t, dir) defer shutdownEtcd(tetcd) t.Logf("starting cs") @@ -755,6 +755,82 @@ func TestProjectGroupsAndProjects(t *testing.T) { }) } +func TestProjectUpdate(t *testing.T) { + dir, err := ioutil.TempDir("", "agola") + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + defer os.RemoveAll(dir) + + ctx := context.Background() + + cs, tetcd := setupConfigstore(ctx,t, dir) + defer shutdownEtcd(tetcd) + + t.Logf("starting cs") + go func() { + _ = cs.Run(ctx) + }() + + // TODO(sgotti) change the sleep with a real check that all is ready + time.Sleep(2 * time.Second) + + user, err := cs.ah.CreateUser(ctx, &action.CreateUserRequest{UserName: "user01"}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + + // TODO(sgotti) change the sleep with a real check that user is in readdb + time.Sleep(2 * time.Second) + + _, err = cs.ah.CreateProjectGroup(ctx, &types.ProjectGroup{Name: "projectgroup01", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("user", user.Name)}, Visibility: types.VisibilityPublic}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + p01 := &types.Project{Name: "project01", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("user", user.Name)}, Visibility: types.VisibilityPublic, RemoteRepositoryConfigType: types.RemoteRepositoryConfigTypeManual} + _, err = cs.ah.CreateProject(ctx, p01) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + p02 := &types.Project{Name: "project01", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("user", user.Name, "projectgroup01")}, Visibility: types.VisibilityPublic, RemoteRepositoryConfigType: types.RemoteRepositoryConfigTypeManual} + _, err = cs.ah.CreateProject(ctx, p02) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + p03 := &types.Project{Name: "project02", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("user", user.Name, "projectgroup01")}, Visibility: types.VisibilityPublic, RemoteRepositoryConfigType: types.RemoteRepositoryConfigTypeManual} + _, err = cs.ah.CreateProject(ctx, p03) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + + t.Run("rename project keeping same parent", func(t *testing.T) { + projectName := "project02" + p03.Name = "newproject02" + _, err := cs.ah.UpdateProject(ctx, &action.UpdateProjectRequest{ProjectRef: path.Join("user", user.Name, "projectgroup01", projectName), Project: p03}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + }) + t.Run("move project to project group having project with same name", func(t *testing.T) { + projectName := "project01" + expectedErr := fmt.Sprintf("project with name %q, path %q already exists", projectName, path.Join("user", user.Name, projectName)) + p02.Parent.ID = path.Join("user", user.Name) + _, err := cs.ah.UpdateProject(ctx, &action.UpdateProjectRequest{ProjectRef: path.Join("user", user.Name, "projectgroup01", projectName), Project: p02}) + if err.Error() != expectedErr { + t.Fatalf("expected err %v, got err: %v", expectedErr, err) + } + }) + t.Run("move project to project group changing name", func(t *testing.T) { + projectName := "project01" + p02.Name = "newproject01" + p02.Parent.ID = path.Join("user", user.Name) + _, err := cs.ah.UpdateProject(ctx, &action.UpdateProjectRequest{ProjectRef: path.Join("user", user.Name, "projectgroup01", projectName), Project: p02}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + }) +} + func TestProjectGroupDelete(t *testing.T) { dir, err := ioutil.TempDir("", "agola") if err != nil { @@ -764,7 +840,7 @@ func TestProjectGroupDelete(t *testing.T) { ctx := context.Background() - cs, tetcd := setupConfigstore(t, ctx, dir) + cs, tetcd := setupConfigstore(ctx,t, dir) defer shutdownEtcd(tetcd) t.Logf("starting cs") @@ -903,7 +979,7 @@ func TestOrgMembers(t *testing.T) { ctx := context.Background() - cs, tetcd := setupConfigstore(t, ctx, dir) + cs, tetcd := setupConfigstore(ctx,t, dir) defer shutdownEtcd(tetcd) t.Logf("starting cs") @@ -1139,7 +1215,7 @@ func TestRemoteSource(t *testing.T) { } ctx := context.Background() - cs, tetcd := setupConfigstore(t, ctx, dir) + cs, tetcd := setupConfigstore(ctx,t, dir) defer shutdownEtcd(tetcd) t.Logf("starting cs")