gateway: add manual run creation

Add an API and related action to manually create a run from a git branch/tag/ref
with optional commitSHA.

Currently only branches and tags are supported (no pull requests).
If not commitSHA is provided the commit sha referenced by the provided branch/tag/ref is
used.
This commit is contained in:
Simone Gotti 2019-06-11 11:08:40 +02:00
parent fafcf3a623
commit 98c2f76f5d
5 changed files with 212 additions and 3 deletions

View File

@ -21,6 +21,7 @@ import (
"net/url" "net/url"
"path" "path"
gitsource "github.com/sorintlab/agola/internal/gitsources"
csapi "github.com/sorintlab/agola/internal/services/configstore/api" csapi "github.com/sorintlab/agola/internal/services/configstore/api"
"github.com/sorintlab/agola/internal/services/types" "github.com/sorintlab/agola/internal/services/types"
"github.com/sorintlab/agola/internal/util" "github.com/sorintlab/agola/internal/util"
@ -120,12 +121,12 @@ func (h *ActionHandler) CreateProject(ctx context.Context, req *CreateProjectReq
return nil, errors.Errorf("user doesn't have a linked account for remote source %q", rs.Name) return nil, errors.Errorf("user doesn't have a linked account for remote source %q", rs.Name)
} }
gitsource, err := h.GetGitSource(ctx, rs, user.Name, la) gitSource, err := h.GetGitSource(ctx, rs, user.Name, la)
if err != nil { if err != nil {
return nil, errors.Errorf("failed to create gitsource client: %w", err) return nil, errors.Errorf("failed to create gitsource client: %w", err)
} }
repo, err := gitsource.GetRepoInfo(req.RepoPath) repo, err := gitSource.GetRepoInfo(req.RepoPath)
if err != nil { if err != nil {
return nil, errors.Errorf("failed to get repository info from gitsource: %w", err) return nil, errors.Errorf("failed to get repository info from gitsource: %w", err)
} }
@ -350,3 +351,165 @@ func (h *ActionHandler) DeleteProject(ctx context.Context, projectRef string) er
} }
return nil return nil
} }
func (h *ActionHandler) ProjectCreateRun(ctx context.Context, projectRef, branch, tag, refName, commitSHA string) error {
curUserID := h.CurrentUserID(ctx)
user, resp, err := h.configstoreClient.GetUser(ctx, curUserID)
if err != nil {
return errors.Errorf("failed to get user %q: %w", curUserID, ErrFromRemote(resp, err))
}
p, resp, err := h.configstoreClient.GetProject(ctx, projectRef)
if err != nil {
return errors.Errorf("failed to get project %q: %w", projectRef, ErrFromRemote(resp, err))
}
isProjectOwner, err := h.IsProjectOwner(ctx, p.OwnerType, p.OwnerID)
if err != nil {
return errors.Errorf("failed to determine ownership: %w", err)
}
if !isProjectOwner {
return util.NewErrForbidden(errors.Errorf("user not authorized"))
}
rs, resp, err := h.configstoreClient.GetRemoteSource(ctx, p.RemoteSourceID)
if err != nil {
return errors.Errorf("failed to get remote source %q: %w", p.RemoteSourceID, ErrFromRemote(resp, err))
}
h.log.Infof("rs: %s", util.Dump(rs))
var la *types.LinkedAccount
for _, v := range user.LinkedAccounts {
if v.RemoteSourceID == rs.ID {
la = v
break
}
}
h.log.Infof("la: %s", util.Dump(la))
if la == nil {
return util.NewErrBadRequest(errors.Errorf("user doesn't have a linked account for remote source %q", rs.Name))
}
gitSource, err := h.GetGitSource(ctx, rs, user.Name, la)
if err != nil {
return errors.Errorf("failed to create gitsource client: %w", err)
}
// check user has access to the repository
repoInfo, err := gitSource.GetRepoInfo(p.RepositoryPath)
if err != nil {
return errors.Errorf("failed to get repository info from gitsource: %w", err)
}
set := 0
if branch != "" {
set++
}
if tag != "" {
set++
}
if refName != "" {
set++
}
if set == 0 {
return util.NewErrBadRequest(errors.Errorf("one of branch, tag or ref is required"))
}
if set > 1 {
return util.NewErrBadRequest(errors.Errorf("only one of branch, tag or ref can be provided"))
}
var refType types.RunRefType
var message string
var branchLink, tagLink string
var refCommitSHA string
if refName == "" {
if branch != "" {
refName = gitSource.BranchRef(branch)
}
if tag != "" {
refName = gitSource.TagRef(tag)
}
}
gitRefType, name, err := gitSource.RefType(refName)
if err != nil {
return util.NewErrBadRequest(errors.Errorf("failed to get refType for ref %q: %w", refName, err))
}
ref, err := gitSource.GetRef(p.RepositoryPath, refName)
if err != nil {
return errors.Errorf("failed to get ref information from git source for ref %q: %w", refName, err)
}
refCommitSHA = ref.CommitSHA
switch gitRefType {
case gitsource.RefTypeBranch:
branch = name
case gitsource.RefTypeTag:
tag = name
// TODO(sgotti) implement manual run creation on a pull request if really needed
default:
return errors.Errorf("unsupported ref %q for manual run creation", refName)
}
// TODO(sgotti) check that the provided ref contains the provided commitSHA
// if no commitSHA has been provided use the ref commit sha
if commitSHA == "" && refCommitSHA != "" {
commitSHA = refCommitSHA
}
commit, err := gitSource.GetCommit(p.RepositoryPath, commitSHA)
if err != nil {
return errors.Errorf("failed to get commit information from git source for commit sha %q: %w", commitSHA, err)
}
// use the commit full sha since the user could have provided a short commit sha
commitSHA = commit.SHA
if branch != "" {
refType = types.RunRefTypeBranch
message = commit.Message
branchLink = gitSource.BranchLink(repoInfo, branch)
}
if tag != "" {
refType = types.RunRefTypeBranch
message = fmt.Sprintf("Tag %s", tag)
tagLink = gitSource.TagLink(repoInfo, tag)
}
// use remotesource skipSSHHostKeyCheck config and override with project config if set to true there
skipSSHHostKeyCheck := rs.SkipSSHHostKeyCheck
if p.SkipSSHHostKeyCheck {
skipSSHHostKeyCheck = p.SkipSSHHostKeyCheck
}
req := &CreateRunRequest{
RunType: types.RunTypeProject,
RefType: refType,
RunCreationTrigger: types.RunCreationTriggerTypeManual,
Project: p.Project,
RepoPath: p.RepositoryPath,
GitSource: gitSource,
CommitSHA: commitSHA,
Message: message,
Branch: branch,
Tag: tag,
PullRequestID: "",
Ref: refName,
SSHPrivKey: p.SSHPrivateKey,
SSHHostKey: rs.SSHHostKey,
SkipSSHHostKeyCheck: skipSSHHostKeyCheck,
CloneURL: repoInfo.SSHCloneURL,
CommitLink: gitSource.CommitLink(repoInfo, commitSHA),
BranchLink: branchLink,
TagLink: tagLink,
PullRequestLink: "",
}
return h.CreateRuns(ctx, req)
}

View File

@ -430,7 +430,7 @@ func (h *ActionHandler) CreateRuns(ctx context.Context, req *CreateRunRequest) e
data, filename, err := h.fetchConfigFiles(req.GitSource, req.RepoPath, req.CommitSHA) data, filename, err := h.fetchConfigFiles(req.GitSource, req.RepoPath, req.CommitSHA)
if err != nil { if err != nil {
return errors.Errorf("failed to fetch config file: %w", err) return util.NewErrInternal(errors.Errorf("failed to fetch config file: %w", err))
} }
h.log.Debug("data: %s", data) h.log.Debug("data: %s", data)

View File

@ -260,3 +260,46 @@ func createProjectResponse(r *csapi.Project) *ProjectResponse {
return res return res
} }
type ProjectCreateRunRequest struct {
Branch string `json:"branch,omitempty"`
Tag string `json:"tag,omitempty"`
Ref string `json:"ref,omitempty"`
CommitSHA string `json:"commit_sha,omitempty"`
}
type ProjectCreateRunHandler struct {
log *zap.SugaredLogger
ah *action.ActionHandler
}
func NewProjectCreateRunHandler(logger *zap.Logger, ah *action.ActionHandler) *ProjectCreateRunHandler {
return &ProjectCreateRunHandler{log: logger.Sugar(), ah: ah}
}
func (h *ProjectCreateRunHandler) 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 req ProjectCreateRunRequest
d := json.NewDecoder(r.Body)
if err := d.Decode(&req); err != nil {
httpError(w, util.NewErrBadRequest(err))
return
}
err = h.ah.ProjectCreateRun(ctx, projectRef, req.Branch, req.Tag, req.Ref, req.CommitSHA)
if httpError(w, err) {
h.log.Errorf("err: %+v", err)
return
}
if err := httpResponse(w, http.StatusCreated, nil); err != nil {
h.log.Errorf("err: %+v", err)
}
}

View File

@ -162,6 +162,7 @@ func (g *Gateway) Run(ctx context.Context) error {
deleteProjectHandler := api.NewDeleteProjectHandler(logger, g.ah) deleteProjectHandler := api.NewDeleteProjectHandler(logger, g.ah)
projectReconfigHandler := api.NewProjectReconfigHandler(logger, g.ah) projectReconfigHandler := api.NewProjectReconfigHandler(logger, g.ah)
projectUpdateRepoLinkedAccountHandler := api.NewProjectUpdateRepoLinkedAccountHandler(logger, g.ah) projectUpdateRepoLinkedAccountHandler := api.NewProjectUpdateRepoLinkedAccountHandler(logger, g.ah)
projectCreateRunHandler := api.NewProjectCreateRunHandler(logger, g.ah)
secretHandler := api.NewSecretHandler(logger, g.ah) secretHandler := api.NewSecretHandler(logger, g.ah)
createSecretHandler := api.NewCreateSecretHandler(logger, g.ah) createSecretHandler := api.NewCreateSecretHandler(logger, g.ah)
@ -242,6 +243,7 @@ func (g *Gateway) Run(ctx context.Context) error {
apirouter.Handle("/projects/{projectref}", authForcedHandler(deleteProjectHandler)).Methods("DELETE") apirouter.Handle("/projects/{projectref}", authForcedHandler(deleteProjectHandler)).Methods("DELETE")
apirouter.Handle("/projects/{projectref}/reconfig", authForcedHandler(projectReconfigHandler)).Methods("PUT") apirouter.Handle("/projects/{projectref}/reconfig", authForcedHandler(projectReconfigHandler)).Methods("PUT")
apirouter.Handle("/projects/{projectref}/updaterepolinkedaccount", authForcedHandler(projectUpdateRepoLinkedAccountHandler)).Methods("PUT") apirouter.Handle("/projects/{projectref}/updaterepolinkedaccount", authForcedHandler(projectUpdateRepoLinkedAccountHandler)).Methods("PUT")
apirouter.Handle("/projects/{projectref}/createrun", authForcedHandler(projectCreateRunHandler)).Methods("PUT")
apirouter.Handle("/projectgroups/{projectgroupref}/secrets", authForcedHandler(secretHandler)).Methods("GET") apirouter.Handle("/projectgroups/{projectgroupref}/secrets", authForcedHandler(secretHandler)).Methods("GET")
apirouter.Handle("/projects/{projectref}/secrets", authForcedHandler(secretHandler)).Methods("GET") apirouter.Handle("/projects/{projectref}/secrets", authForcedHandler(secretHandler)).Methods("GET")

View File

@ -33,4 +33,5 @@ type RunCreationTriggerType string
const ( const (
RunCreationTriggerTypeWebhook RunCreationTriggerType = "webhook" RunCreationTriggerTypeWebhook RunCreationTriggerType = "webhook"
RunCreationTriggerTypeManual RunCreationTriggerType = "manual"
) )