From d551dcb8203060b1c29ee526bab09153ad53852d Mon Sep 17 00:00:00 2001 From: Simone Gotti Date: Mon, 13 May 2019 00:23:57 +0200 Subject: [PATCH] configstore: implement projectgroup delete --- .../services/configstore/action/project.go | 2 +- .../configstore/action/projectgroup.go | 44 ++++++ internal/services/configstore/api/client.go | 4 + .../services/configstore/api/projectgroup.go | 28 ++++ internal/services/configstore/configstore.go | 2 + .../services/configstore/configstore_test.go | 141 ++++++++++++++++++ 6 files changed, 220 insertions(+), 1 deletion(-) diff --git a/internal/services/configstore/action/project.go b/internal/services/configstore/action/project.go index c231314..4eff454 100644 --- a/internal/services/configstore/action/project.go +++ b/internal/services/configstore/action/project.go @@ -291,7 +291,7 @@ func (h *ActionHandler) DeleteProject(ctx context.Context, projectRef string) er return err } - // TODO(sgotti) delete project secrets/variables + // TODO(sgotti) implement childs garbage collection actions := []*datamanager.Action{ { ActionType: datamanager.ActionTypeDelete, diff --git a/internal/services/configstore/action/projectgroup.go b/internal/services/configstore/action/projectgroup.go index 5ffabe8..bc1ed76 100644 --- a/internal/services/configstore/action/projectgroup.go +++ b/internal/services/configstore/action/projectgroup.go @@ -147,3 +147,47 @@ func (h *ActionHandler) CreateProjectGroup(ctx context.Context, projectGroup *ty _, err = h.dm.WriteWal(ctx, actions, cgt) return projectGroup, err } + +func (h *ActionHandler) DeleteProjectGroup(ctx context.Context, projectGroupRef string) error { + var projectGroup *types.ProjectGroup + + var cgt *datamanager.ChangeGroupsUpdateToken + + // must do all the checks in a single transaction to avoid concurrent changes + err := h.readDB.Do(func(tx *db.Tx) error { + var err error + + // check project group existance + projectGroup, err = h.readDB.GetProjectGroup(tx, projectGroupRef) + if err != nil { + return err + } + if projectGroup == nil { + return util.NewErrBadRequest(errors.Errorf("project group %q doesn't exist", projectGroupRef)) + } + + // changegroup is the project group id. + cgNames := []string{util.EncodeSha256Hex(projectGroup.ID)} + cgt, err = h.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return err + } + + // TODO(sgotti) implement childs garbage collection + actions := []*datamanager.Action{ + { + ActionType: datamanager.ActionTypeDelete, + DataType: string(types.ConfigTypeProjectGroup), + ID: projectGroup.ID, + }, + } + + _, err = h.dm.WriteWal(ctx, actions, cgt) + return err +} diff --git a/internal/services/configstore/api/client.go b/internal/services/configstore/api/client.go index 237b716..beb6758 100644 --- a/internal/services/configstore/api/client.go +++ b/internal/services/configstore/api/client.go @@ -135,6 +135,10 @@ func (c *Client) CreateProjectGroup(ctx context.Context, projectGroup *types.Pro return resProjectGroup, resp, err } +func (c *Client) DeleteProjectGroup(ctx context.Context, projectGroupRef string) (*http.Response, error) { + return c.getResponse(ctx, "DELETE", fmt.Sprintf("/projectgroups/%s", url.PathEscape(projectGroupRef)), nil, jsonContent, nil) +} + func (c *Client) GetProject(ctx context.Context, projectRef string) (*Project, *http.Response, error) { project := new(Project) resp, err := c.getParsedResponse(ctx, "GET", fmt.Sprintf("/projects/%s", url.PathEscape(projectRef)), nil, jsonContent, nil, project) diff --git a/internal/services/configstore/api/projectgroup.go b/internal/services/configstore/api/projectgroup.go index 242b2e7..a931d4c 100644 --- a/internal/services/configstore/api/projectgroup.go +++ b/internal/services/configstore/api/projectgroup.go @@ -243,3 +243,31 @@ func (h *CreateProjectGroupHandler) ServeHTTP(w http.ResponseWriter, r *http.Req h.log.Errorf("err: %+v", err) } } + +type DeleteProjectGroupHandler struct { + log *zap.SugaredLogger + ah *action.ActionHandler +} + +func NewDeleteProjectGroupHandler(logger *zap.Logger, ah *action.ActionHandler) *DeleteProjectGroupHandler { + return &DeleteProjectGroupHandler{log: logger.Sugar(), ah: ah} +} + +func (h *DeleteProjectGroupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + vars := mux.Vars(r) + projectGroupRef, err := url.PathUnescape(vars["projectgroupref"]) + if err != nil { + httpError(w, util.NewErrBadRequest(err)) + return + } + + err = h.ah.DeleteProjectGroup(ctx, projectGroupRef) + if httpError(w, err) { + h.log.Errorf("err: %+v", err) + } + if err := httpResponse(w, http.StatusNoContent, nil); err != nil { + h.log.Errorf("err: %+v", err) + } +} diff --git a/internal/services/configstore/configstore.go b/internal/services/configstore/configstore.go index ad808b6..e8ed492 100644 --- a/internal/services/configstore/configstore.go +++ b/internal/services/configstore/configstore.go @@ -129,6 +129,7 @@ func (s *Configstore) Run(ctx context.Context) error { projectGroupSubgroupsHandler := api.NewProjectGroupSubgroupsHandler(logger, s.ah, s.readDB) projectGroupProjectsHandler := api.NewProjectGroupProjectsHandler(logger, s.ah, s.readDB) createProjectGroupHandler := api.NewCreateProjectGroupHandler(logger, s.ah, s.readDB) + deleteProjectGroupHandler := api.NewDeleteProjectGroupHandler(logger, s.ah) projectHandler := api.NewProjectHandler(logger, s.readDB) createProjectHandler := api.NewCreateProjectHandler(logger, s.ah, s.readDB) @@ -178,6 +179,7 @@ func (s *Configstore) Run(ctx context.Context) error { apirouter.Handle("/projectgroups/{projectgroupref}/subgroups", projectGroupSubgroupsHandler).Methods("GET") apirouter.Handle("/projectgroups/{projectgroupref}/projects", projectGroupProjectsHandler).Methods("GET") apirouter.Handle("/projectgroups", createProjectGroupHandler).Methods("POST") + apirouter.Handle("/projectgroups/{projectgroupref}", deleteProjectGroupHandler).Methods("DELETE") apirouter.Handle("/projects/{projectref}", projectHandler).Methods("GET") apirouter.Handle("/projects", createProjectHandler).Methods("POST") diff --git a/internal/services/configstore/configstore_test.go b/internal/services/configstore/configstore_test.go index 3ec06b8..493ed5c 100644 --- a/internal/services/configstore/configstore_test.go +++ b/internal/services/configstore/configstore_test.go @@ -507,6 +507,147 @@ func TestProjectGroupsAndProjects(t *testing.T) { }) } +func TestProjectGroupDelete(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(t, ctx, dir) + defer shutdownEtcd(tetcd) + + t.Logf("starting cs") + go func() { + if err := cs.Run(ctx); err != nil { + t.Fatalf("err: %v", err) + } + }() + + // 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) + //} + org, err := cs.ah.CreateOrg(ctx, &types.Organization{Name: "org01"}) + 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) + + // create a projectgroup in org root project group + pg01, err := cs.ah.CreateProjectGroup(ctx, &types.ProjectGroup{Name: "projectgroup01", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("org", org.Name)}, Visibility: types.VisibilityPublic}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + + //create a child projectgroup in org root project group + spg01, err := cs.ah.CreateProjectGroup(ctx, &types.ProjectGroup{Name: "subprojectgroup01", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: pg01.ID}, Visibility: types.VisibilityPublic}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + + // create a project inside child projectgroup + project, err := cs.ah.CreateProject(ctx, &types.Project{Name: "project01", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: spg01.ID}, Visibility: types.VisibilityPublic, RemoteRepositoryConfigType: types.RemoteRepositoryConfigTypeManual}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + + // create project secret + _, err = cs.ah.CreateSecret(ctx, &types.Secret{Name: "secret01", Parent: types.Parent{Type: types.ConfigTypeProject, ID: project.ID}, Type: types.SecretTypeInternal, Data: map[string]string{"secret01": "secretvar01"}}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + // create project variable + _, err = cs.ah.CreateVariable(ctx, &types.Variable{Name: "variable01", Parent: types.Parent{Type: types.ConfigTypeProject, ID: project.ID}, Values: []types.VariableValue{{SecretName: "secret01", SecretVar: "secretvar01"}}}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + + // delete root projectgroup + if err = cs.ah.DeleteProjectGroup(ctx, pg01.ID); err != nil { + t.Fatalf("unexpected err: %v", err) + } + + // recreate the same hierarchj using the paths + pg01, err = cs.ah.CreateProjectGroup(ctx, &types.ProjectGroup{Name: "projectgroup01", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("org", org.Name)}, Visibility: types.VisibilityPublic}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + spg01, err = cs.ah.CreateProjectGroup(ctx, &types.ProjectGroup{Name: "subprojectgroup01", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("org", org.Name, pg01.Name)}, Visibility: types.VisibilityPublic}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + project, err = cs.ah.CreateProject(ctx, &types.Project{Name: "project01", Parent: types.Parent{Type: types.ConfigTypeProjectGroup, ID: path.Join("org", org.Name, pg01.Name, spg01.Name)}, Visibility: types.VisibilityPublic, RemoteRepositoryConfigType: types.RemoteRepositoryConfigTypeManual}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + secret, err := cs.ah.CreateSecret(ctx, &types.Secret{Name: "secret01", Parent: types.Parent{Type: types.ConfigTypeProject, ID: path.Join("org", org.Name, pg01.Name, spg01.Name, project.Name)}, Type: types.SecretTypeInternal, Data: map[string]string{"secret01": "secretvar01"}}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + variable, err := cs.ah.CreateVariable(ctx, &types.Variable{Name: "variable01", Parent: types.Parent{Type: types.ConfigTypeProject, ID: path.Join("org", org.Name, pg01.Name, spg01.Name, project.Name)}, Values: []types.VariableValue{{SecretName: "secret01", SecretVar: "secretvar01"}}}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + + // Get by projectgroup id + projects, err := cs.ah.GetProjectGroupProjects(ctx, spg01.ID) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + if diff := cmp.Diff(projects, []*types.Project{project}); diff != "" { + t.Error(diff) + } + + // Get by projectgroup path + projects, err = cs.ah.GetProjectGroupProjects(ctx, path.Join("org", org.Name, pg01.Name, spg01.Name)) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + if diff := cmp.Diff(projects, []*types.Project{project}); diff != "" { + t.Error(diff) + } + + secrets, err := cs.ah.GetSecrets(ctx, types.ConfigTypeProject, project.ID, false) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + if diff := cmp.Diff(secrets, []*types.Secret{secret}); diff != "" { + t.Error(diff) + } + + secrets, err = cs.ah.GetSecrets(ctx, types.ConfigTypeProject, path.Join("org", org.Name, pg01.Name, spg01.Name, project.Name), false) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + if diff := cmp.Diff(secrets, []*types.Secret{secret}); diff != "" { + t.Error(diff) + } + + variables, err := cs.ah.GetVariables(ctx, types.ConfigTypeProject, project.ID, false) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + if diff := cmp.Diff(variables, []*types.Variable{variable}); diff != "" { + t.Error(diff) + } + + variables, err = cs.ah.GetVariables(ctx, types.ConfigTypeProject, path.Join("org", org.Name, pg01.Name, spg01.Name, project.Name), false) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + if diff := cmp.Diff(variables, []*types.Variable{variable}); diff != "" { + t.Error(diff) + } +} + func TestOrgMembers(t *testing.T) { dir, err := ioutil.TempDir("", "agola") if err != nil {