From e1d0318c9b9a81f47f3b313ae0706be67e325529 Mon Sep 17 00:00:00 2001 From: Simone Gotti Date: Thu, 9 May 2019 15:33:57 +0200 Subject: [PATCH] configstore: add UpdateProject api --- .../services/configstore/action/project.go | 107 ++++++++++++++++++ internal/services/configstore/api/client.go | 11 ++ internal/services/configstore/api/project.go | 48 ++++++++ internal/services/configstore/configstore.go | 2 + 4 files changed, 168 insertions(+) diff --git a/internal/services/configstore/action/project.go b/internal/services/configstore/action/project.go index c864f13..c231314 100644 --- a/internal/services/configstore/action/project.go +++ b/internal/services/configstore/action/project.go @@ -153,6 +153,113 @@ func (h *ActionHandler) CreateProject(ctx context.Context, project *types.Projec return project, err } +type UpdateProjectRequest struct { + ProjectRef string + + Project *types.Project +} + +func (h *ActionHandler) UpdateProject(ctx context.Context, req *UpdateProjectRequest) (*types.Project, error) { + if err := h.ValidateProject(ctx, req.Project); err != nil { + return nil, err + } + + 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 exists + p, err := h.readDB.GetProject(tx, req.ProjectRef) + if err != nil { + return err + } + if p == nil { + return util.NewErrBadRequest(errors.Errorf("project with ref %q doesn't exist", req.ProjectRef)) + } + // check that the project.ID matches + if p.ID != req.Project.ID { + return util.NewErrBadRequest(errors.Errorf("project with ref %q has a different id", req.ProjectRef)) + } + + // check parent project group exists + group, err := h.readDB.GetProjectGroup(tx, req.Project.Parent.ID) + if err != nil { + return err + } + 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")) + } + + pp, err := h.readDB.GetProjectPath(tx, p) + if err != nil { + return err + } + + // check duplicate project name + ap, err := h.readDB.GetProjectByName(tx, req.Project.Parent.ID, req.Project.Name) + if err != nil { + return err + } + if ap != nil { + return util.NewErrBadRequest(errors.Errorf("project with name %q, path %q already exists", p.Name, pp)) + } + + // changegroup is the project path. Use "projectpath" prefix as it must + // cover both projects and projectgroups + cgNames := []string{util.EncodeSha256Hex("projectpath-" + pp)} + cgt, err = h.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) + if err != nil { + return err + } + + if req.Project.RemoteRepositoryConfigType == types.RemoteRepositoryConfigTypeRemoteSource { + // check that the linked account matches the remote source + user, err := h.readDB.GetUserByLinkedAccount(tx, req.Project.LinkedAccountID) + if err != nil { + return errors.Wrapf(err, "failed to get user with linked account id %q", req.Project.LinkedAccountID) + } + if user == nil { + return util.NewErrBadRequest(errors.Errorf("user for linked account %q doesn't exist", req.Project.LinkedAccountID)) + } + la, ok := user.LinkedAccounts[req.Project.LinkedAccountID] + if !ok { + return util.NewErrBadRequest(errors.Errorf("linked account id %q for user %q doesn't exist", req.Project.LinkedAccountID, user.Name)) + } + if la.RemoteSourceID != req.Project.RemoteSourceID { + return util.NewErrBadRequest(errors.Errorf("linked account id %q remote source %q different than project remote source %q", req.Project.LinkedAccountID, la.RemoteSourceID, req.Project.RemoteSourceID)) + } + } + + return nil + }) + if err != nil { + return nil, err + } + + pcj, err := json.Marshal(req.Project) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal project") + } + actions := []*datamanager.Action{ + { + ActionType: datamanager.ActionTypePut, + DataType: string(types.ConfigTypeProject), + ID: req.Project.ID, + Data: pcj, + }, + } + + _, err = h.dm.WriteWal(ctx, actions, cgt) + return req.Project, err +} + func (h *ActionHandler) DeleteProject(ctx context.Context, projectRef string) error { var project *types.Project diff --git a/internal/services/configstore/api/client.go b/internal/services/configstore/api/client.go index 9080cad..12a129e 100644 --- a/internal/services/configstore/api/client.go +++ b/internal/services/configstore/api/client.go @@ -152,6 +152,17 @@ func (c *Client) CreateProject(ctx context.Context, project *types.Project) (*Pr return resProject, resp, err } +func (c *Client) UpdateProject(ctx context.Context, projectRef string, project *types.Project) (*Project, *http.Response, error) { + pj, err := json.Marshal(project) + if err != nil { + return nil, nil, err + } + + resProject := new(Project) + resp, err := c.getParsedResponse(ctx, "PUT", fmt.Sprintf("/projects/%s", url.PathEscape(projectRef)), nil, jsonContent, bytes.NewReader(pj), resProject) + return resProject, resp, err +} + func (c *Client) DeleteProject(ctx context.Context, projectRef string) (*http.Response, error) { return c.getResponse(ctx, "DELETE", fmt.Sprintf("/projects/%s", url.PathEscape(projectRef)), nil, jsonContent, nil) } diff --git a/internal/services/configstore/api/project.go b/internal/services/configstore/api/project.go index 1089a7f..92cbfd9 100644 --- a/internal/services/configstore/api/project.go +++ b/internal/services/configstore/api/project.go @@ -194,6 +194,54 @@ func (h *CreateProjectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) } } +type UpdateProjectHandler struct { + log *zap.SugaredLogger + ah *action.ActionHandler + readDB *readdb.ReadDB +} + +func NewUpdateProjectHandler(logger *zap.Logger, ah *action.ActionHandler, readDB *readdb.ReadDB) *UpdateProjectHandler { + return &UpdateProjectHandler{log: logger.Sugar(), ah: ah, readDB: readDB} +} + +func (h *UpdateProjectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + vars := mux.Vars(r) + projectRef, err := url.PathUnescape(vars["projectref"]) + if err != nil { + httpError(w, util.NewErrBadRequest(err)) + return + } + + var project *types.Project + d := json.NewDecoder(r.Body) + if err := d.Decode(&project); err != nil { + httpError(w, util.NewErrBadRequest(err)) + return + } + + areq := &action.UpdateProjectRequest{ + ProjectRef: projectRef, + Project: project, + } + project, err = h.ah.UpdateProject(ctx, areq) + if httpError(w, err) { + h.log.Errorf("err: %+v", err) + return + } + + resProject, err := projectResponse(h.readDB, project) + if httpError(w, err) { + h.log.Errorf("err: %+v", err) + return + } + + if err := httpResponse(w, http.StatusCreated, resProject); err != nil { + h.log.Errorf("err: %+v", err) + } +} + type DeleteProjectHandler struct { log *zap.SugaredLogger ah *action.ActionHandler diff --git a/internal/services/configstore/configstore.go b/internal/services/configstore/configstore.go index 69e52a2..3e033f6 100644 --- a/internal/services/configstore/configstore.go +++ b/internal/services/configstore/configstore.go @@ -132,6 +132,7 @@ func (s *Configstore) Run(ctx context.Context) error { projectHandler := api.NewProjectHandler(logger, s.readDB) createProjectHandler := api.NewCreateProjectHandler(logger, s.ah, s.readDB) + updateProjectHandler := api.NewUpdateProjectHandler(logger, s.ah, s.readDB) deleteProjectHandler := api.NewDeleteProjectHandler(logger, s.ah) secretsHandler := api.NewSecretsHandler(logger, s.readDB) @@ -177,6 +178,7 @@ func (s *Configstore) Run(ctx context.Context) error { apirouter.Handle("/projects/{projectref}", projectHandler).Methods("GET") apirouter.Handle("/projects", createProjectHandler).Methods("POST") + apirouter.Handle("/projects/{projectref}", updateProjectHandler).Methods("PUT") apirouter.Handle("/projects/{projectref}", deleteProjectHandler).Methods("DELETE") apirouter.Handle("/projectgroups/{projectgroupref}/secrets", secretsHandler).Methods("GET")