diff --git a/internal/services/configstore/api/client.go b/internal/services/configstore/api/client.go index 955db1b..82d6536 100644 --- a/internal/services/configstore/api/client.go +++ b/internal/services/configstore/api/client.go @@ -106,50 +106,50 @@ func (c *Client) getParsedResponse(ctx context.Context, method, path string, que return resp, d.Decode(obj) } -func (c *Client) GetProjectGroup(ctx context.Context, projectGroupRef string) (*types.ProjectGroup, *http.Response, error) { - projectGroup := new(types.ProjectGroup) +func (c *Client) GetProjectGroup(ctx context.Context, projectGroupRef string) (*ProjectGroup, *http.Response, error) { + projectGroup := new(ProjectGroup) resp, err := c.getParsedResponse(ctx, "GET", fmt.Sprintf("/projectgroups/%s", url.PathEscape(projectGroupRef)), nil, jsonContent, nil, projectGroup) return projectGroup, resp, err } -func (c *Client) GetProjectGroupSubgroups(ctx context.Context, projectGroupRef string) ([]*types.ProjectGroup, *http.Response, error) { - projectGroups := []*types.ProjectGroup{} +func (c *Client) GetProjectGroupSubgroups(ctx context.Context, projectGroupRef string) ([]*ProjectGroup, *http.Response, error) { + projectGroups := []*ProjectGroup{} resp, err := c.getParsedResponse(ctx, "GET", fmt.Sprintf("/projectgroups/%s/subgroups", url.PathEscape(projectGroupRef)), nil, jsonContent, nil, &projectGroups) return projectGroups, resp, err } -func (c *Client) GetProjectGroupProjects(ctx context.Context, projectGroupRef string) ([]*types.Project, *http.Response, error) { - projects := []*types.Project{} +func (c *Client) GetProjectGroupProjects(ctx context.Context, projectGroupRef string) ([]*Project, *http.Response, error) { + projects := []*Project{} resp, err := c.getParsedResponse(ctx, "GET", fmt.Sprintf("/projectgroups/%s/projects", url.PathEscape(projectGroupRef)), nil, jsonContent, nil, &projects) return projects, resp, err } -func (c *Client) CreateProjectGroup(ctx context.Context, projectGroup *types.ProjectGroup) (*types.ProjectGroup, *http.Response, error) { +func (c *Client) CreateProjectGroup(ctx context.Context, projectGroup *types.ProjectGroup) (*ProjectGroup, *http.Response, error) { pj, err := json.Marshal(projectGroup) if err != nil { return nil, nil, err } - projectGroup = new(types.ProjectGroup) - resp, err := c.getParsedResponse(ctx, "POST", "/projectgroups", nil, jsonContent, bytes.NewReader(pj), projectGroup) - return projectGroup, resp, err + resProjectGroup := new(ProjectGroup) + resp, err := c.getParsedResponse(ctx, "POST", "/projectgroups", nil, jsonContent, bytes.NewReader(pj), resProjectGroup) + return resProjectGroup, resp, err } -func (c *Client) GetProject(ctx context.Context, projectRef string) (*types.Project, *http.Response, error) { - project := new(types.Project) +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) return project, resp, err } -func (c *Client) CreateProject(ctx context.Context, project *types.Project) (*types.Project, *http.Response, error) { +func (c *Client) CreateProject(ctx context.Context, project *types.Project) (*Project, *http.Response, error) { pj, err := json.Marshal(project) if err != nil { return nil, nil, err } - project = new(types.Project) - resp, err := c.getParsedResponse(ctx, "POST", "/projects", nil, jsonContent, bytes.NewReader(pj), project) - return project, resp, err + resProject := new(Project) + resp, err := c.getParsedResponse(ctx, "POST", "/projects", nil, jsonContent, bytes.NewReader(pj), resProject) + return resProject, resp, err } func (c *Client) DeleteProject(ctx context.Context, projectRef string) (*http.Response, error) { diff --git a/internal/services/configstore/api/project.go b/internal/services/configstore/api/project.go index 09b5837..3a4a42c 100644 --- a/internal/services/configstore/api/project.go +++ b/internal/services/configstore/api/project.go @@ -18,6 +18,7 @@ import ( "encoding/json" "net/http" "net/url" + "path" "github.com/pkg/errors" "github.com/sorintlab/agola/internal/db" @@ -31,6 +32,46 @@ import ( "go.uber.org/zap" ) +// Project augments types.Project with dynamic data +type Project struct { + *types.Project + + // dynamic data + Path string + ParentPath string +} + +func projectResponse(readDB *readdb.ReadDB, project *types.Project) (*Project, error) { + r, err := projectsResponse(readDB, []*types.Project{project}) + if err != nil { + return nil, err + } + return r[0], nil +} + +func projectsResponse(readDB *readdb.ReadDB, projects []*types.Project) ([]*Project, error) { + resProjects := make([]*Project, len(projects)) + + err := readDB.Do(func(tx *db.Tx) error { + for i, project := range projects { + pp, err := readDB.GetPath(tx, project.Parent.Type, project.Parent.ID) + if err != nil { + return err + } + // we calculate the path here from parent path since the db could not yet be + // updated on create + resProjects[i] = &Project{ + Project: project, + Path: path.Join(pp, project.Name), + ParentPath: pp, + } + } + return nil + }) + + return resProjects, err +} + type ProjectHandler struct { log *zap.SugaredLogger readDB *readdb.ReadDB @@ -76,7 +117,13 @@ func (h *ProjectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - if err := httpResponse(w, http.StatusOK, project); err != nil { + resProject, err := projectResponse(h.readDB, project) + if httpError(w, err) { + h.log.Errorf("err: %+v", err) + return + } + + if err := httpResponse(w, http.StatusOK, resProject); err != nil { h.log.Errorf("err: %+v", err) } } @@ -87,8 +134,8 @@ type CreateProjectHandler struct { readDB *readdb.ReadDB } -func NewCreateProjectHandler(logger *zap.Logger, ch *command.CommandHandler) *CreateProjectHandler { - return &CreateProjectHandler{log: logger.Sugar(), ch: ch} +func NewCreateProjectHandler(logger *zap.Logger, ch *command.CommandHandler, readDB *readdb.ReadDB) *CreateProjectHandler { + return &CreateProjectHandler{log: logger.Sugar(), ch: ch, readDB: readDB} } func (h *CreateProjectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -107,7 +154,13 @@ func (h *CreateProjectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) return } - if err := httpResponse(w, http.StatusCreated, project); err != nil { + 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) } } diff --git a/internal/services/configstore/api/projectgroup.go b/internal/services/configstore/api/projectgroup.go index 9ea067e..124e976 100644 --- a/internal/services/configstore/api/projectgroup.go +++ b/internal/services/configstore/api/projectgroup.go @@ -18,6 +18,7 @@ import ( "encoding/json" "net/http" "net/url" + "path" "github.com/pkg/errors" "github.com/sorintlab/agola/internal/db" @@ -30,6 +31,46 @@ import ( "go.uber.org/zap" ) +// ProjectGroup augments types.ProjectGroup with dynamic data +type ProjectGroup struct { + *types.ProjectGroup + + // dynamic data + Path string + ParentPath string +} + +func projectGroupResponse(readDB *readdb.ReadDB, projectGroup *types.ProjectGroup) (*ProjectGroup, error) { + r, err := projectGroupsResponse(readDB, []*types.ProjectGroup{projectGroup}) + if err != nil { + return nil, err + } + return r[0], nil +} + +func projectGroupsResponse(readDB *readdb.ReadDB, projectGroups []*types.ProjectGroup) ([]*ProjectGroup, error) { + resProjectGroups := make([]*ProjectGroup, len(projectGroups)) + + err := readDB.Do(func(tx *db.Tx) error { + for i, projectGroup := range projectGroups { + pp, err := readDB.GetPath(tx, projectGroup.Parent.Type, projectGroup.Parent.ID) + if err != nil { + return err + } + // we calculate the path here from parent path since the db could not yet be + // updated on create + resProjectGroups[i] = &ProjectGroup{ + ProjectGroup: projectGroup, + Path: path.Join(pp, projectGroup.Name), + ParentPath: pp, + } + } + return nil + }) + + return resProjectGroups, err +} + type ProjectGroupHandler struct { log *zap.SugaredLogger readDB *readdb.ReadDB @@ -64,7 +105,13 @@ func (h *ProjectGroupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) return } - if err := httpResponse(w, http.StatusOK, projectGroup); err != nil { + resProjectGroup, err := projectGroupResponse(h.readDB, projectGroup) + if httpError(w, err) { + h.log.Errorf("err: %+v", err) + return + } + + if err := httpResponse(w, http.StatusOK, resProjectGroup); err != nil { h.log.Errorf("err: %+v", err) } } @@ -114,7 +161,13 @@ func (h *ProjectGroupProjectsHandler) ServeHTTP(w http.ResponseWriter, r *http.R return } - if err := httpResponse(w, http.StatusOK, projects); err != nil { + resProjects, err := projectsResponse(h.readDB, projects) + if httpError(w, err) { + h.log.Errorf("err: %+v", err) + return + } + + if err := httpResponse(w, http.StatusOK, resProjects); err != nil { h.log.Errorf("err: %+v", err) } } @@ -164,7 +217,13 @@ func (h *ProjectGroupSubgroupsHandler) ServeHTTP(w http.ResponseWriter, r *http. return } - if err := httpResponse(w, http.StatusOK, projectGroups); err != nil { + resProjectGroups, err := projectGroupsResponse(h.readDB, projectGroups) + if httpError(w, err) { + h.log.Errorf("err: %+v", err) + return + } + + if err := httpResponse(w, http.StatusOK, resProjectGroups); err != nil { h.log.Errorf("err: %+v", err) } } @@ -175,8 +234,8 @@ type CreateProjectGroupHandler struct { readDB *readdb.ReadDB } -func NewCreateProjectGroupHandler(logger *zap.Logger, ch *command.CommandHandler) *CreateProjectGroupHandler { - return &CreateProjectGroupHandler{log: logger.Sugar(), ch: ch} +func NewCreateProjectGroupHandler(logger *zap.Logger, ch *command.CommandHandler, readDB *readdb.ReadDB) *CreateProjectGroupHandler { + return &CreateProjectGroupHandler{log: logger.Sugar(), ch: ch, readDB: readDB} } func (h *CreateProjectGroupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -195,7 +254,13 @@ func (h *CreateProjectGroupHandler) ServeHTTP(w http.ResponseWriter, r *http.Req return } - if err := httpResponse(w, http.StatusCreated, projectGroup); err != nil { + resProjectGroup, err := projectGroupResponse(h.readDB, projectGroup) + if httpError(w, err) { + h.log.Errorf("err: %+v", err) + return + } + + if err := httpResponse(w, http.StatusCreated, resProjectGroup); err != nil { h.log.Errorf("err: %+v", err) } } diff --git a/internal/services/configstore/configstore.go b/internal/services/configstore/configstore.go index 34de300..4222e46 100644 --- a/internal/services/configstore/configstore.go +++ b/internal/services/configstore/configstore.go @@ -127,10 +127,10 @@ func (s *ConfigStore) Run(ctx context.Context) error { projectGroupHandler := api.NewProjectGroupHandler(logger, s.readDB) projectGroupSubgroupsHandler := api.NewProjectGroupSubgroupsHandler(logger, s.readDB) projectGroupProjectsHandler := api.NewProjectGroupProjectsHandler(logger, s.readDB) - createProjectGroupHandler := api.NewCreateProjectGroupHandler(logger, s.ch) + createProjectGroupHandler := api.NewCreateProjectGroupHandler(logger, s.ch, s.readDB) projectHandler := api.NewProjectHandler(logger, s.readDB) - createProjectHandler := api.NewCreateProjectHandler(logger, s.ch) + createProjectHandler := api.NewCreateProjectHandler(logger, s.ch, s.readDB) deleteProjectHandler := api.NewDeleteProjectHandler(logger, s.ch) secretsHandler := api.NewSecretsHandler(logger, s.readDB) diff --git a/internal/services/gateway/api/project.go b/internal/services/gateway/api/project.go index 9c5fc87..d3a9fd9 100644 --- a/internal/services/gateway/api/project.go +++ b/internal/services/gateway/api/project.go @@ -22,7 +22,6 @@ import ( "github.com/pkg/errors" csapi "github.com/sorintlab/agola/internal/services/configstore/api" "github.com/sorintlab/agola/internal/services/gateway/command" - "github.com/sorintlab/agola/internal/services/types" "github.com/sorintlab/agola/internal/util" "github.com/gorilla/mux" @@ -182,14 +181,18 @@ func (h *ProjectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } type ProjectResponse struct { - ID string `json:"id"` - Name string `json:"name"` + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Path string `json:"path,omitempty"` + ParentPath string `json:"parent_path,omitempty"` } -func createProjectResponse(r *types.Project) *ProjectResponse { +func createProjectResponse(r *csapi.Project) *ProjectResponse { res := &ProjectResponse{ - ID: r.ID, - Name: r.Name, + ID: r.ID, + Name: r.Name, + Path: r.Path, + ParentPath: r.ParentPath, } return res diff --git a/internal/services/gateway/api/projectgroup.go b/internal/services/gateway/api/projectgroup.go index a21c953..63ea46c 100644 --- a/internal/services/gateway/api/projectgroup.go +++ b/internal/services/gateway/api/projectgroup.go @@ -22,7 +22,6 @@ import ( "github.com/pkg/errors" csapi "github.com/sorintlab/agola/internal/services/configstore/api" "github.com/sorintlab/agola/internal/services/gateway/command" - "github.com/sorintlab/agola/internal/services/types" "github.com/sorintlab/agola/internal/util" "github.com/gorilla/mux" @@ -180,14 +179,18 @@ func (h *ProjectGroupSubgroupsHandler) ServeHTTP(w http.ResponseWriter, r *http. } type ProjectGroupResponse struct { - ID string `json:"id"` - Name string `json:"name"` + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Path string `json:"path,omitempty"` + ParentPath string `json:"parent_path,omitempty"` } -func createProjectGroupResponse(r *types.ProjectGroup) *ProjectGroupResponse { +func createProjectGroupResponse(r *csapi.ProjectGroup) *ProjectGroupResponse { run := &ProjectGroupResponse{ - ID: r.ID, - Name: r.Name, + ID: r.ID, + Name: r.Name, + Path: r.Path, + ParentPath: r.ParentPath, } return run diff --git a/internal/services/gateway/command/project.go b/internal/services/gateway/command/project.go index ad55279..468a909 100644 --- a/internal/services/gateway/command/project.go +++ b/internal/services/gateway/command/project.go @@ -20,6 +20,7 @@ import ( "net/url" "path" + csapi "github.com/sorintlab/agola/internal/services/configstore/api" "github.com/sorintlab/agola/internal/services/types" "github.com/sorintlab/agola/internal/util" @@ -27,15 +28,15 @@ import ( ) type CreateProjectRequest struct { + CurrentUserID string Name string ParentID string RemoteSourceName string RepoPath string - CurrentUserID string SkipSSHHostKeyCheck bool } -func (c *CommandHandler) CreateProject(ctx context.Context, req *CreateProjectRequest) (*types.Project, error) { +func (c *CommandHandler) CreateProject(ctx context.Context, req *CreateProjectRequest) (*csapi.Project, error) { if !util.ValidateName(req.Name) { return nil, util.NewErrBadRequest(errors.Errorf("invalid project name %q", req.Name)) } @@ -99,16 +100,16 @@ func (c *CommandHandler) CreateProject(ctx context.Context, req *CreateProjectRe } c.log.Infof("creating project") - p, resp, err = c.configstoreClient.CreateProject(ctx, p) + rp, resp, err := c.configstoreClient.CreateProject(ctx, p) if err != nil { return nil, ErrFromRemote(resp, errors.Wrapf(err, "failed to create project")) } c.log.Infof("project %s created, ID: %s", p.Name, p.ID) - return p, c.SetupProject(ctx, rs, user, la, p) + return rp, c.SetupProject(ctx, rs, user, la, rp) } -func (c *CommandHandler) SetupProject(ctx context.Context, rs *types.RemoteSource, user *types.User, la *types.LinkedAccount, project *types.Project) error { +func (c *CommandHandler) SetupProject(ctx context.Context, rs *types.RemoteSource, user *types.User, la *types.LinkedAccount, project *csapi.Project) error { gitsource, err := c.GetGitSource(ctx, rs, user.Name, la) if err != nil { return errors.Wrapf(err, "failed to create gitsource client") diff --git a/internal/services/gateway/command/projectgroup.go b/internal/services/gateway/command/projectgroup.go index ea3d917..654c418 100644 --- a/internal/services/gateway/command/projectgroup.go +++ b/internal/services/gateway/command/projectgroup.go @@ -18,6 +18,7 @@ import ( "context" "path" + csapi "github.com/sorintlab/agola/internal/services/configstore/api" "github.com/sorintlab/agola/internal/services/types" "github.com/sorintlab/agola/internal/util" @@ -25,12 +26,12 @@ import ( ) type CreateProjectGroupRequest struct { + CurrentUserID string Name string ParentID string - CurrentUserID string } -func (c *CommandHandler) CreateProjectGroup(ctx context.Context, req *CreateProjectGroupRequest) (*types.ProjectGroup, error) { +func (c *CommandHandler) CreateProjectGroup(ctx context.Context, req *CreateProjectGroupRequest) (*csapi.ProjectGroup, error) { if !util.ValidateName(req.Name) { return nil, util.NewErrBadRequest(errors.Errorf("invalid projectGroup name %q", req.Name)) } @@ -55,11 +56,11 @@ func (c *CommandHandler) CreateProjectGroup(ctx context.Context, req *CreateProj } c.log.Infof("creating projectGroup") - p, resp, err = c.configstoreClient.CreateProjectGroup(ctx, p) + rp, resp, err := c.configstoreClient.CreateProjectGroup(ctx, p) if err != nil { return nil, ErrFromRemote(resp, errors.Wrapf(err, "failed to create projectGroup")) } - c.log.Infof("projectGroup %s created, ID: %s", p.Name, p.ID) + c.log.Infof("projectGroup %s created, ID: %s", rp.Name, rp.ID) - return p, nil + return rp, nil }