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:
parent
fafcf3a623
commit
98c2f76f5d
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -33,4 +33,5 @@ type RunCreationTriggerType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RunCreationTriggerTypeWebhook RunCreationTriggerType = "webhook"
|
RunCreationTriggerTypeWebhook RunCreationTriggerType = "webhook"
|
||||||
|
RunCreationTriggerTypeManual RunCreationTriggerType = "manual"
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue