configstore: implement project move

This commit is contained in:
Simone Gotti 2019-09-24 15:19:17 +02:00
parent 6daf830d85
commit fe9e3e8317
4 changed files with 129 additions and 31 deletions

View File

@ -64,6 +64,24 @@ func (h *ActionHandler) ValidateProject(ctx context.Context, project *types.Proj
return nil 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) { func (h *ActionHandler) CreateProject(ctx context.Context, project *types.Project) (*types.Project, error) {
if err := h.ValidateProject(ctx, project); err != nil { if err := h.ValidateProject(ctx, project); err != nil {
return nil, err return nil, err
@ -190,12 +208,7 @@ func (h *ActionHandler) UpdateProject(ctx context.Context, req *UpdateProjectReq
if group == nil { if group == nil {
return util.NewErrBadRequest(errors.Errorf("project group with id %q doesn't exist", req.Project.Parent.ID)) return util.NewErrBadRequest(errors.Errorf("project group with id %q doesn't exist", req.Project.Parent.ID))
} }
req.Project.Parent.ID = group.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"))
}
groupPath, err := h.readDB.GetProjectGroupPath(tx, group) groupPath, err := h.readDB.GetProjectGroupPath(tx, group)
if err != nil { if err != nil {
@ -203,7 +216,7 @@ func (h *ActionHandler) UpdateProject(ctx context.Context, req *UpdateProjectReq
} }
pp := path.Join(groupPath, req.Project.Name) 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 // check duplicate project name
ap, err := h.readDB.GetProjectByName(tx, req.Project.Parent.ID, req.Project.Name) ap, err := h.readDB.GetProjectByName(tx, req.Project.Parent.ID, req.Project.Name)
if err != nil { 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 // changegroup is the project path. Use "projectpath" prefix as it must
// cover both projects and projectgroups // cover both projects and projectgroups
cgNames := []string{util.EncodeSha256Hex("projectpath-" + pp)} 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) cgt, err = h.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
if err != nil { if err != nil {
return err return err

View File

@ -30,7 +30,6 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"go.uber.org/zap" "go.uber.org/zap"
errors "golang.org/x/xerrors"
) )
func projectResponse(ctx context.Context, readDB *readdb.ReadDB, project *types.Project) (*csapitypes.Project, error) { 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 { type ProjectHandler struct {
log *zap.SugaredLogger log *zap.SugaredLogger
ah *action.ActionHandler
readDB *readdb.ReadDB readDB *readdb.ReadDB
} }
func NewProjectHandler(logger *zap.Logger, readDB *readdb.ReadDB) *ProjectHandler { func NewProjectHandler(logger *zap.Logger, ah *action.ActionHandler, readDB *readdb.ReadDB) *ProjectHandler {
return &ProjectHandler{log: logger.Sugar(), readDB: readDB} return &ProjectHandler{log: logger.Sugar(), ah: ah, readDB: readDB}
} }
func (h *ProjectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 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 return
} }
var project *types.Project project, err := h.ah.GetProject(ctx, projectRef)
err = h.readDB.Do(ctx, func(tx *db.Tx) error { if httpError(w, err) {
var err error
project, err = h.readDB.GetProject(tx, projectRef)
return err
})
if err != nil {
h.log.Errorf("err: %+v", 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 return
} }

View File

@ -184,7 +184,7 @@ func (s *Configstore) setupDefaultRouter() http.Handler {
updateProjectGroupHandler := api.NewUpdateProjectGroupHandler(logger, s.ah, s.readDB) updateProjectGroupHandler := api.NewUpdateProjectGroupHandler(logger, s.ah, s.readDB)
deleteProjectGroupHandler := api.NewDeleteProjectGroupHandler(logger, s.ah) 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) createProjectHandler := api.NewCreateProjectHandler(logger, s.ah, s.readDB)
updateProjectHandler := api.NewUpdateProjectHandler(logger, s.ah, s.readDB) updateProjectHandler := api.NewUpdateProjectHandler(logger, s.ah, s.readDB)
deleteProjectHandler := api.NewDeleteProjectHandler(logger, s.ah) deleteProjectHandler := api.NewDeleteProjectHandler(logger, s.ah)

View File

@ -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") etcdDir, err := ioutil.TempDir(dir, "etcd")
if err != nil { if err != nil {
t.Fatalf("unexpected err: %v", err) t.Fatalf("unexpected err: %v", err)
@ -550,7 +550,7 @@ func TestUser(t *testing.T) {
ctx := context.Background() ctx := context.Background()
cs, tetcd := setupConfigstore(t, ctx, dir) cs, tetcd := setupConfigstore(ctx, t, dir)
defer shutdownEtcd(tetcd) defer shutdownEtcd(tetcd)
t.Logf("starting cs") 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") dir, err := ioutil.TempDir("", "agola")
if err != nil { if err != nil {
t.Fatalf("unexpected err: %v", err) t.Fatalf("unexpected err: %v", err)
@ -617,7 +617,7 @@ func TestProjectGroupsAndProjects(t *testing.T) {
ctx := context.Background() ctx := context.Background()
cs, tetcd := setupConfigstore(t, ctx, dir) cs, tetcd := setupConfigstore(ctx,t, dir)
defer shutdownEtcd(tetcd) defer shutdownEtcd(tetcd)
t.Logf("starting cs") 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) { func TestProjectGroupDelete(t *testing.T) {
dir, err := ioutil.TempDir("", "agola") dir, err := ioutil.TempDir("", "agola")
if err != nil { if err != nil {
@ -764,7 +840,7 @@ func TestProjectGroupDelete(t *testing.T) {
ctx := context.Background() ctx := context.Background()
cs, tetcd := setupConfigstore(t, ctx, dir) cs, tetcd := setupConfigstore(ctx,t, dir)
defer shutdownEtcd(tetcd) defer shutdownEtcd(tetcd)
t.Logf("starting cs") t.Logf("starting cs")
@ -903,7 +979,7 @@ func TestOrgMembers(t *testing.T) {
ctx := context.Background() ctx := context.Background()
cs, tetcd := setupConfigstore(t, ctx, dir) cs, tetcd := setupConfigstore(ctx,t, dir)
defer shutdownEtcd(tetcd) defer shutdownEtcd(tetcd)
t.Logf("starting cs") t.Logf("starting cs")
@ -1139,7 +1215,7 @@ func TestRemoteSource(t *testing.T) {
} }
ctx := context.Background() ctx := context.Background()
cs, tetcd := setupConfigstore(t, ctx, dir) cs, tetcd := setupConfigstore(ctx,t, dir)
defer shutdownEtcd(tetcd) defer shutdownEtcd(tetcd)
t.Logf("starting cs") t.Logf("starting cs")