gateway: move create run to own action
* Move all run creation logic to the action handler. * Cleanup webhook to use it
This commit is contained in:
parent
5b22ebc2d3
commit
8242dc3a9d
|
@ -18,15 +18,52 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/sorintlab/agola/internal/config"
|
||||||
|
gitsource "github.com/sorintlab/agola/internal/gitsources"
|
||||||
|
"github.com/sorintlab/agola/internal/runconfig"
|
||||||
"github.com/sorintlab/agola/internal/services/common"
|
"github.com/sorintlab/agola/internal/services/common"
|
||||||
rsapi "github.com/sorintlab/agola/internal/services/runservice/api"
|
rsapi "github.com/sorintlab/agola/internal/services/runservice/api"
|
||||||
rstypes "github.com/sorintlab/agola/internal/services/runservice/types"
|
rstypes "github.com/sorintlab/agola/internal/services/runservice/types"
|
||||||
|
"github.com/sorintlab/agola/internal/services/types"
|
||||||
"github.com/sorintlab/agola/internal/util"
|
"github.com/sorintlab/agola/internal/util"
|
||||||
|
|
||||||
errors "golang.org/x/xerrors"
|
errors "golang.org/x/xerrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultSSHPort = "22"
|
||||||
|
|
||||||
|
agolaDefaultConfigDir = ".agola"
|
||||||
|
agolaDefaultJsonnetConfigFile = "config.jsonnet"
|
||||||
|
agolaDefaultJsonConfigFile = "config.json"
|
||||||
|
agolaDefaultYamlConfigFile = "config.yml"
|
||||||
|
|
||||||
|
// List of runs annotations
|
||||||
|
AnnotationRunType = "run_type"
|
||||||
|
AnnotationRefType = "ref_type"
|
||||||
|
AnnotationProjectID = "projectid"
|
||||||
|
AnnotationUserID = "userid"
|
||||||
|
|
||||||
|
AnnotationRunCreationTrigger = "run_creation_trigger"
|
||||||
|
AnnotationWebhookEvent = "webhook_event"
|
||||||
|
AnnotationWebhookSender = "webhook_sender"
|
||||||
|
|
||||||
|
AnnotationCommitSHA = "commit_sha"
|
||||||
|
AnnotationRef = "ref"
|
||||||
|
AnnotationMessage = "message"
|
||||||
|
AnnotationCommitLink = "commit_link"
|
||||||
|
AnnotationCompareLink = "compare_link"
|
||||||
|
|
||||||
|
AnnotationBranch = "branch"
|
||||||
|
AnnotationBranchLink = "branch_link"
|
||||||
|
AnnotationTag = "tag"
|
||||||
|
AnnotationTagLink = "tag_link"
|
||||||
|
AnnotationPullRequestID = "pull_request_id"
|
||||||
|
AnnotationPullRequestLink = "pull_request_link"
|
||||||
|
)
|
||||||
|
|
||||||
func (h *ActionHandler) GetRun(ctx context.Context, runID string) (*rsapi.RunResponse, error) {
|
func (h *ActionHandler) GetRun(ctx context.Context, runID string) (*rsapi.RunResponse, error) {
|
||||||
runResp, resp, err := h.runserviceClient.GetRun(ctx, runID, nil)
|
runResp, resp, err := h.runserviceClient.GetRun(ctx, runID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -249,3 +286,270 @@ func (h *ActionHandler) RunTaskAction(ctx context.Context, req *RunTaskActionsRe
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateRunRequest struct {
|
||||||
|
RunType types.RunType
|
||||||
|
RefType types.RunRefType
|
||||||
|
RunCreationTrigger types.RunCreationTriggerType
|
||||||
|
|
||||||
|
Project *types.Project
|
||||||
|
User *types.User
|
||||||
|
RepoPath string
|
||||||
|
GitSource gitsource.GitSource
|
||||||
|
CommitSHA string
|
||||||
|
Message string
|
||||||
|
Branch string
|
||||||
|
Tag string
|
||||||
|
Ref string
|
||||||
|
PullRequestID string
|
||||||
|
SSHPrivKey string
|
||||||
|
SSHHostKey string
|
||||||
|
SkipSSHHostKeyCheck bool
|
||||||
|
CloneURL string
|
||||||
|
|
||||||
|
WebhookEvent string
|
||||||
|
WebhookSender string
|
||||||
|
|
||||||
|
CommitLink string
|
||||||
|
BranchLink string
|
||||||
|
TagLink string
|
||||||
|
PullRequestLink string
|
||||||
|
|
||||||
|
// CompareLink is provided only when triggered by a webhook and contains the
|
||||||
|
// commit compare link
|
||||||
|
CompareLink string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ActionHandler) CreateRuns(ctx context.Context, req *CreateRunRequest) error {
|
||||||
|
setupErrors := []string{}
|
||||||
|
|
||||||
|
if req.CommitSHA == "" {
|
||||||
|
return util.NewErrBadRequest(errors.Errorf("empty commit SHA"))
|
||||||
|
}
|
||||||
|
if req.Message == "" {
|
||||||
|
return util.NewErrBadRequest(errors.Errorf("empty message"))
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseGroupType common.GroupType
|
||||||
|
var baseGroupID string
|
||||||
|
var groupType common.GroupType
|
||||||
|
var group string
|
||||||
|
|
||||||
|
if req.RunType == types.RunTypeProject {
|
||||||
|
baseGroupType = common.GroupTypeProject
|
||||||
|
baseGroupID = req.Project.ID
|
||||||
|
} else {
|
||||||
|
baseGroupType = common.GroupTypeUser
|
||||||
|
baseGroupID = req.User.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
switch req.RefType {
|
||||||
|
case types.RunRefTypeBranch:
|
||||||
|
groupType = common.GroupTypeBranch
|
||||||
|
group = req.Branch
|
||||||
|
case types.RunRefTypeTag:
|
||||||
|
groupType = common.GroupTypeTag
|
||||||
|
group = req.Tag
|
||||||
|
case types.RunRefTypePullRequest:
|
||||||
|
groupType = common.GroupTypePullRequest
|
||||||
|
group = req.PullRequestID
|
||||||
|
}
|
||||||
|
|
||||||
|
runGroup := common.GenRunGroup(baseGroupType, baseGroupID, groupType, group)
|
||||||
|
|
||||||
|
gitURL, err := util.ParseGitURL(req.CloneURL)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Errorf("failed to parse clone url: %w", err)
|
||||||
|
}
|
||||||
|
gitHost := gitURL.Hostname()
|
||||||
|
gitPort := gitURL.Port()
|
||||||
|
if gitPort == "" {
|
||||||
|
gitPort = defaultSSHPort
|
||||||
|
}
|
||||||
|
|
||||||
|
// this env vars overrides other env vars
|
||||||
|
env := map[string]string{
|
||||||
|
"CI": "true",
|
||||||
|
"AGOLA_SSHPRIVKEY": req.SSHPrivKey,
|
||||||
|
"AGOLA_REPOSITORY_URL": req.CloneURL,
|
||||||
|
"AGOLA_GIT_HOST": gitHost,
|
||||||
|
"AGOLA_GIT_PORT": gitPort,
|
||||||
|
"AGOLA_GIT_BRANCH": req.Branch,
|
||||||
|
"AGOLA_GIT_TAG": req.Tag,
|
||||||
|
"AGOLA_GIT_REF": req.Ref,
|
||||||
|
"AGOLA_GIT_COMMITSHA": req.CommitSHA,
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.SSHHostKey != "" {
|
||||||
|
env["AGOLA_SSHHOSTKEY"] = req.SSHHostKey
|
||||||
|
}
|
||||||
|
if req.SkipSSHHostKeyCheck {
|
||||||
|
env["AGOLA_SKIPSSHHOSTKEYCHECK"] = "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
variables := map[string]string{}
|
||||||
|
if req.RunType == types.RunTypeProject {
|
||||||
|
var err error
|
||||||
|
variables, err = h.genRunVariables(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
annotations := map[string]string{
|
||||||
|
AnnotationRunType: string(req.RunType),
|
||||||
|
AnnotationRefType: string(req.RefType),
|
||||||
|
AnnotationRunCreationTrigger: string(req.RunCreationTrigger),
|
||||||
|
AnnotationWebhookEvent: req.WebhookEvent,
|
||||||
|
AnnotationWebhookSender: req.WebhookSender,
|
||||||
|
AnnotationCommitSHA: req.CommitSHA,
|
||||||
|
AnnotationRef: req.Ref,
|
||||||
|
AnnotationMessage: req.Message,
|
||||||
|
AnnotationCommitLink: req.CommitLink,
|
||||||
|
AnnotationCompareLink: req.CompareLink,
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.RunType == types.RunTypeProject {
|
||||||
|
annotations[AnnotationProjectID] = req.Project.ID
|
||||||
|
} else {
|
||||||
|
annotations[AnnotationUserID] = req.User.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Branch != "" {
|
||||||
|
annotations[AnnotationBranch] = req.Branch
|
||||||
|
annotations[AnnotationBranchLink] = req.BranchLink
|
||||||
|
}
|
||||||
|
if req.Tag != "" {
|
||||||
|
annotations[AnnotationTag] = req.Tag
|
||||||
|
annotations[AnnotationTagLink] = req.TagLink
|
||||||
|
}
|
||||||
|
if req.PullRequestID != "" {
|
||||||
|
annotations[AnnotationPullRequestID] = req.PullRequestID
|
||||||
|
annotations[AnnotationPullRequestLink] = req.PullRequestLink
|
||||||
|
}
|
||||||
|
|
||||||
|
data, filename, err := h.fetchConfigFiles(req.GitSource, req.RepoPath, req.CommitSHA)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Errorf("failed to fetch config file: %w", err)
|
||||||
|
}
|
||||||
|
h.log.Debug("data: %s", data)
|
||||||
|
|
||||||
|
var configFormat config.ConfigFormat
|
||||||
|
switch path.Ext(filename) {
|
||||||
|
case ".jsonnet":
|
||||||
|
configFormat = config.ConfigFormatJsonnet
|
||||||
|
case ".json":
|
||||||
|
fallthrough
|
||||||
|
case ".yml":
|
||||||
|
configFormat = config.ConfigFormatJSON
|
||||||
|
|
||||||
|
}
|
||||||
|
config, err := config.ParseConfig([]byte(data), configFormat)
|
||||||
|
if err != nil {
|
||||||
|
h.log.Errorf("failed to parse config: %+v", err)
|
||||||
|
|
||||||
|
// create a run (per config file) with a generic error since we cannot parse
|
||||||
|
// it and know how many runs are defined
|
||||||
|
setupErrors = append(setupErrors, err.Error())
|
||||||
|
createRunReq := &rsapi.RunCreateRequest{
|
||||||
|
RunConfigTasks: nil,
|
||||||
|
Group: runGroup,
|
||||||
|
SetupErrors: setupErrors,
|
||||||
|
Name: rstypes.RunGenericSetupErrorName,
|
||||||
|
StaticEnvironment: env,
|
||||||
|
Annotations: annotations,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, _, err := h.runserviceClient.CreateRun(ctx, createRunReq); err != nil {
|
||||||
|
h.log.Errorf("failed to create run: %+v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, run := range config.Runs {
|
||||||
|
rcts := runconfig.GenRunConfigTasks(util.DefaultUUIDGenerator{}, config, run.Name, variables, req.Branch, req.Tag, req.Ref)
|
||||||
|
|
||||||
|
createRunReq := &rsapi.RunCreateRequest{
|
||||||
|
RunConfigTasks: rcts,
|
||||||
|
Group: runGroup,
|
||||||
|
SetupErrors: setupErrors,
|
||||||
|
Name: run.Name,
|
||||||
|
StaticEnvironment: env,
|
||||||
|
Annotations: annotations,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, _, err := h.runserviceClient.CreateRun(ctx, createRunReq); err != nil {
|
||||||
|
h.log.Errorf("failed to create run: %+v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ActionHandler) fetchConfigFiles(gitSource gitsource.GitSource, repopath, commitSHA string) ([]byte, string, error) {
|
||||||
|
var data []byte
|
||||||
|
var filename string
|
||||||
|
err := util.ExponentialBackoff(util.FetchFileBackoff, func() (bool, error) {
|
||||||
|
for _, filename = range []string{agolaDefaultJsonnetConfigFile, agolaDefaultJsonConfigFile, agolaDefaultYamlConfigFile} {
|
||||||
|
var err error
|
||||||
|
data, err = gitSource.GetFile(repopath, commitSHA, path.Join(agolaDefaultConfigDir, filename))
|
||||||
|
if err == nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
h.log.Errorf("get file err: %v", err)
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
return data, filename, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ActionHandler) genRunVariables(ctx context.Context, req *CreateRunRequest) (map[string]string, error) {
|
||||||
|
variables := map[string]string{}
|
||||||
|
|
||||||
|
// get project variables
|
||||||
|
pvars, _, err := h.configstoreClient.GetProjectVariables(ctx, req.Project.ID, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Errorf("failed to get project variables: %w", err)
|
||||||
|
}
|
||||||
|
h.log.Infof("pvars: %v", util.Dump(pvars))
|
||||||
|
|
||||||
|
// remove overriden variables
|
||||||
|
pvars = common.FilterOverriddenVariables(pvars)
|
||||||
|
h.log.Infof("pvars: %v", util.Dump(pvars))
|
||||||
|
|
||||||
|
// get project secrets
|
||||||
|
secrets, _, err := h.configstoreClient.GetProjectSecrets(ctx, req.Project.ID, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Errorf("failed to get project secrets: %w", err)
|
||||||
|
}
|
||||||
|
h.log.Infof("secrets: %v", util.Dump(secrets))
|
||||||
|
for _, pvar := range pvars {
|
||||||
|
// find the value match
|
||||||
|
var varval types.VariableValue
|
||||||
|
for _, varval = range pvar.Values {
|
||||||
|
h.log.Infof("varval: %v", util.Dump(varval))
|
||||||
|
match := types.MatchWhen(varval.When, req.Branch, req.Tag, req.Ref)
|
||||||
|
if !match {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// get the secret value referenced by the variable, it must be a secret at the same level or a lower level
|
||||||
|
secret := common.GetVarValueMatchingSecret(varval, pvar.ParentPath, secrets)
|
||||||
|
h.log.Infof("secret: %v", util.Dump(secret))
|
||||||
|
if secret != nil {
|
||||||
|
varValue, ok := secret.Data[varval.SecretVar]
|
||||||
|
if ok {
|
||||||
|
variables[pvar.Name] = varValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h.log.Infof("variables: %v", util.Dump(variables))
|
||||||
|
|
||||||
|
return variables, nil
|
||||||
|
}
|
||||||
|
|
|
@ -225,6 +225,15 @@ func (c *Client) DeleteProject(ctx context.Context, projectRef string) (*http.Re
|
||||||
return c.getResponse(ctx, "DELETE", fmt.Sprintf("/projects/%s", url.PathEscape(projectRef)), nil, jsonContent, nil)
|
return c.getResponse(ctx, "DELETE", fmt.Sprintf("/projects/%s", url.PathEscape(projectRef)), nil, jsonContent, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) ProjectCreateRun(ctx context.Context, projectRef string, req *ProjectCreateRunRequest) (*http.Response, error) {
|
||||||
|
reqj, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.getResponse(ctx, "POST", fmt.Sprintf("/projects/%s/createrun", url.PathEscape(projectRef)), nil, jsonContent, bytes.NewReader(reqj))
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) ReconfigProject(ctx context.Context, projectRef string) (*http.Response, error) {
|
func (c *Client) ReconfigProject(ctx context.Context, projectRef string) (*http.Response, error) {
|
||||||
return c.getResponse(ctx, "PUT", fmt.Sprintf("/projects/%s/reconfig", url.PathEscape(projectRef)), nil, jsonContent, nil)
|
return c.getResponse(ctx, "PUT", fmt.Sprintf("/projects/%s/reconfig", url.PathEscape(projectRef)), nil, jsonContent, nil)
|
||||||
}
|
}
|
||||||
|
@ -342,7 +351,7 @@ func (c *Client) GetRuns(ctx context.Context, phaseFilter, groups, runGroups []s
|
||||||
}
|
}
|
||||||
|
|
||||||
getRunsResponse := []*RunsResponse{}
|
getRunsResponse := []*RunsResponse{}
|
||||||
resp, err := c.getParsedResponse(ctx, "GET", "/runs", q, jsonContent, nil, getRunsResponse)
|
resp, err := c.getParsedResponse(ctx, "GET", "/runs", q, jsonContent, nil, &getRunsResponse)
|
||||||
return getRunsResponse, resp, err
|
return getRunsResponse, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,20 +15,15 @@
|
||||||
package gateway
|
package gateway
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/sorintlab/agola/internal/config"
|
|
||||||
gitsource "github.com/sorintlab/agola/internal/gitsources"
|
gitsource "github.com/sorintlab/agola/internal/gitsources"
|
||||||
"github.com/sorintlab/agola/internal/gitsources/agolagit"
|
"github.com/sorintlab/agola/internal/gitsources/agolagit"
|
||||||
"github.com/sorintlab/agola/internal/runconfig"
|
|
||||||
"github.com/sorintlab/agola/internal/services/common"
|
"github.com/sorintlab/agola/internal/services/common"
|
||||||
csapi "github.com/sorintlab/agola/internal/services/configstore/api"
|
csapi "github.com/sorintlab/agola/internal/services/configstore/api"
|
||||||
"github.com/sorintlab/agola/internal/services/gateway/action"
|
"github.com/sorintlab/agola/internal/services/gateway/action"
|
||||||
rsapi "github.com/sorintlab/agola/internal/services/runservice/api"
|
rsapi "github.com/sorintlab/agola/internal/services/runservice/api"
|
||||||
rstypes "github.com/sorintlab/agola/internal/services/runservice/types"
|
|
||||||
"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"
|
||||||
|
|
||||||
|
@ -36,37 +31,6 @@ import (
|
||||||
errors "golang.org/x/xerrors"
|
errors "golang.org/x/xerrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
defaultSSHPort = "22"
|
|
||||||
|
|
||||||
agolaDefaultConfigDir = ".agola"
|
|
||||||
agolaDefaultJsonnetConfigFile = "config.jsonnet"
|
|
||||||
agolaDefaultJsonConfigFile = "config.json"
|
|
||||||
agolaDefaultYamlConfigFile = "config.yml"
|
|
||||||
|
|
||||||
// List of runs annotations
|
|
||||||
AnnotationRunType = "run_type"
|
|
||||||
AnnotationRefType = "ref_type"
|
|
||||||
AnnotationProjectID = "projectid"
|
|
||||||
AnnotationUserID = "userid"
|
|
||||||
|
|
||||||
AnnotationRunCreationTrigger = "run_creation_trigger"
|
|
||||||
|
|
||||||
AnnotationCommitSHA = "commit_sha"
|
|
||||||
AnnotationRef = "ref"
|
|
||||||
AnnotationSender = "sender"
|
|
||||||
AnnotationMessage = "message"
|
|
||||||
AnnotationCommitLink = "commit_link"
|
|
||||||
AnnotationCompareLink = "compare_link"
|
|
||||||
|
|
||||||
AnnotationBranch = "branch"
|
|
||||||
AnnotationBranchLink = "branch_link"
|
|
||||||
AnnotationTag = "tag"
|
|
||||||
AnnotationTagLink = "tag_link"
|
|
||||||
AnnotationPullRequestID = "pull_request_id"
|
|
||||||
AnnotationPullRequestLink = "pull_request_link"
|
|
||||||
)
|
|
||||||
|
|
||||||
type webhooksHandler struct {
|
type webhooksHandler struct {
|
||||||
log *zap.SugaredLogger
|
log *zap.SugaredLogger
|
||||||
ah *action.ActionHandler
|
ah *action.ActionHandler
|
||||||
|
@ -106,7 +70,6 @@ func (h *webhooksHandler) handleWebhook(r *http.Request) (int, string, error) {
|
||||||
var cloneURL string
|
var cloneURL string
|
||||||
var sshHostKey string
|
var sshHostKey string
|
||||||
var skipSSHHostKeyCheck bool
|
var skipSSHHostKeyCheck bool
|
||||||
variables := map[string]string{}
|
|
||||||
|
|
||||||
var gitSource gitsource.GitSource
|
var gitSource gitsource.GitSource
|
||||||
if runType == types.RunTypeProject {
|
if runType == types.RunTypeProject {
|
||||||
|
@ -114,7 +77,6 @@ func (h *webhooksHandler) handleWebhook(r *http.Request) (int, string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusBadRequest, "", errors.Errorf("failed to get project %s: %w", projectID, err)
|
return http.StatusBadRequest, "", errors.Errorf("failed to get project %s: %w", projectID, err)
|
||||||
}
|
}
|
||||||
h.log.Infof("project: %s", util.Dump(project))
|
|
||||||
project = csProject.Project
|
project = csProject.Project
|
||||||
|
|
||||||
user, _, err := h.configstoreClient.GetUserByLinkedAccount(ctx, project.LinkedAccountID)
|
user, _, err := h.configstoreClient.GetUserByLinkedAccount(ctx, project.LinkedAccountID)
|
||||||
|
@ -159,46 +121,6 @@ func (h *webhooksHandler) handleWebhook(r *http.Request) (int, string, error) {
|
||||||
|
|
||||||
cloneURL = webhookData.SSHURL
|
cloneURL = webhookData.SSHURL
|
||||||
|
|
||||||
// get project variables
|
|
||||||
pvars, _, err := h.configstoreClient.GetProjectVariables(ctx, project.ID, true)
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusInternalServerError, "", errors.Errorf("failed to get project variables: %w", err)
|
|
||||||
}
|
|
||||||
h.log.Infof("pvars: %v", util.Dump(pvars))
|
|
||||||
|
|
||||||
// remove overriden variables
|
|
||||||
pvars = common.FilterOverriddenVariables(pvars)
|
|
||||||
h.log.Infof("pvars: %v", util.Dump(pvars))
|
|
||||||
|
|
||||||
// get project secrets
|
|
||||||
secrets, _, err := h.configstoreClient.GetProjectSecrets(ctx, project.ID, true)
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusInternalServerError, "", errors.Errorf("failed to get project secrets: %w", err)
|
|
||||||
}
|
|
||||||
h.log.Infof("secrets: %v", util.Dump(secrets))
|
|
||||||
for _, pvar := range pvars {
|
|
||||||
// find the value match
|
|
||||||
var varval types.VariableValue
|
|
||||||
for _, varval = range pvar.Values {
|
|
||||||
h.log.Infof("varval: %v", util.Dump(varval))
|
|
||||||
match := types.MatchWhen(varval.When, webhookData.Branch, webhookData.Tag, webhookData.Ref)
|
|
||||||
if !match {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// get the secret value referenced by the variable, it must be a secret at the same level or a lower level
|
|
||||||
secret := common.GetVarValueMatchingSecret(varval, pvar.ParentPath, secrets)
|
|
||||||
h.log.Infof("secret: %v", util.Dump(secret))
|
|
||||||
if secret != nil {
|
|
||||||
varValue, ok := secret.Data[varval.SecretVar]
|
|
||||||
if ok {
|
|
||||||
variables[pvar.Name] = varValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h.log.Infof("variables: %v", util.Dump(variables))
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
gitSource = agolagit.New(h.apiExposedURL + "/repos")
|
gitSource = agolagit.New(h.apiExposedURL + "/repos")
|
||||||
var err error
|
var err error
|
||||||
|
@ -225,189 +147,35 @@ func (h *webhooksHandler) handleWebhook(r *http.Request) (int, string, error) {
|
||||||
|
|
||||||
h.log.Infof("webhookData: %s", util.Dump(webhookData))
|
h.log.Infof("webhookData: %s", util.Dump(webhookData))
|
||||||
|
|
||||||
data, filename, err := h.fetchConfigFiles(gitSource, webhookData)
|
req := &action.CreateRunRequest{
|
||||||
if err != nil {
|
RunType: runType,
|
||||||
return http.StatusInternalServerError, "", errors.Errorf("failed to fetch config file: %w", err)
|
RefType: common.WebHookEventToRunRefType(webhookData.Event),
|
||||||
}
|
RunCreationTrigger: types.RunCreationTriggerTypeWebhook,
|
||||||
h.log.Debug("data: %s", data)
|
|
||||||
|
|
||||||
gitURL, err := util.ParseGitURL(cloneURL)
|
Project: project,
|
||||||
if err != nil {
|
User: user,
|
||||||
return http.StatusInternalServerError, "", errors.Errorf("failed to parse clone url: %w", err)
|
RepoPath: webhookData.Repo.Path,
|
||||||
}
|
GitSource: gitSource,
|
||||||
gitHost := gitURL.Hostname()
|
CommitSHA: webhookData.CommitSHA,
|
||||||
gitPort := gitURL.Port()
|
Message: webhookData.Message,
|
||||||
if gitPort == "" {
|
Branch: webhookData.Branch,
|
||||||
gitPort = defaultSSHPort
|
Tag: webhookData.Tag,
|
||||||
}
|
PullRequestID: webhookData.PullRequestID,
|
||||||
|
Ref: webhookData.Ref,
|
||||||
|
SSHPrivKey: sshPrivKey,
|
||||||
|
SSHHostKey: sshHostKey,
|
||||||
|
SkipSSHHostKeyCheck: skipSSHHostKeyCheck,
|
||||||
|
CloneURL: cloneURL,
|
||||||
|
|
||||||
// this env vars ovverrides other env vars
|
CommitLink: webhookData.CommitLink,
|
||||||
env := map[string]string{
|
BranchLink: webhookData.BranchLink,
|
||||||
"CI": "true",
|
TagLink: webhookData.TagLink,
|
||||||
"AGOLA_SSHPRIVKEY": sshPrivKey,
|
PullRequestLink: webhookData.PullRequestLink,
|
||||||
"AGOLA_REPOSITORY_URL": cloneURL,
|
CompareLink: webhookData.CompareLink,
|
||||||
"AGOLA_GIT_HOST": gitHost,
|
|
||||||
"AGOLA_GIT_PORT": gitPort,
|
|
||||||
"AGOLA_GIT_BRANCH": webhookData.Branch,
|
|
||||||
"AGOLA_GIT_TAG": webhookData.Tag,
|
|
||||||
"AGOLA_GIT_REF": webhookData.Ref,
|
|
||||||
"AGOLA_GIT_COMMITSHA": webhookData.CommitSHA,
|
|
||||||
}
|
}
|
||||||
|
if err := h.ah.CreateRuns(ctx, req); err != nil {
|
||||||
if sshHostKey != "" {
|
|
||||||
env["AGOLA_SSHHOSTKEY"] = sshHostKey
|
|
||||||
}
|
|
||||||
if skipSSHHostKeyCheck {
|
|
||||||
env["AGOLA_SKIPSSHHOSTKEYCHECK"] = "1"
|
|
||||||
}
|
|
||||||
|
|
||||||
refType := common.WebHookEventToRunRefType(webhookData.Event)
|
|
||||||
|
|
||||||
annotations := map[string]string{
|
|
||||||
AnnotationRunType: string(runType),
|
|
||||||
AnnotationRefType: string(refType),
|
|
||||||
AnnotationRunCreationTrigger: string(types.RunCreationTriggerTypeWebhook),
|
|
||||||
AnnotationCommitSHA: webhookData.CommitSHA,
|
|
||||||
AnnotationRef: webhookData.Ref,
|
|
||||||
AnnotationSender: webhookData.Sender,
|
|
||||||
AnnotationMessage: webhookData.Message,
|
|
||||||
AnnotationCommitLink: webhookData.CommitLink,
|
|
||||||
AnnotationCompareLink: webhookData.CompareLink,
|
|
||||||
}
|
|
||||||
|
|
||||||
if runType == types.RunTypeProject {
|
|
||||||
annotations[AnnotationProjectID] = webhookData.ProjectID
|
|
||||||
} else {
|
|
||||||
annotations[AnnotationUserID] = userID
|
|
||||||
}
|
|
||||||
|
|
||||||
if webhookData.Event == types.WebhookEventPush {
|
|
||||||
annotations[AnnotationBranch] = webhookData.Branch
|
|
||||||
annotations[AnnotationBranchLink] = webhookData.BranchLink
|
|
||||||
}
|
|
||||||
if webhookData.Event == types.WebhookEventTag {
|
|
||||||
annotations[AnnotationTag] = webhookData.Tag
|
|
||||||
annotations[AnnotationTagLink] = webhookData.TagLink
|
|
||||||
}
|
|
||||||
if webhookData.Event == types.WebhookEventPullRequest {
|
|
||||||
annotations[AnnotationPullRequestID] = webhookData.PullRequestID
|
|
||||||
annotations[AnnotationPullRequestLink] = webhookData.PullRequestLink
|
|
||||||
}
|
|
||||||
|
|
||||||
var baseGroupType common.GroupType
|
|
||||||
var baseGroupID string
|
|
||||||
var groupType common.GroupType
|
|
||||||
var group string
|
|
||||||
|
|
||||||
if runType == types.RunTypeProject {
|
|
||||||
baseGroupType = common.GroupTypeProject
|
|
||||||
baseGroupID = project.ID
|
|
||||||
} else {
|
|
||||||
baseGroupType = common.GroupTypeUser
|
|
||||||
baseGroupID = user.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
switch refType {
|
|
||||||
case types.RunRefTypeBranch:
|
|
||||||
groupType = common.GroupTypeBranch
|
|
||||||
group = webhookData.Branch
|
|
||||||
case types.RunRefTypeTag:
|
|
||||||
groupType = common.GroupTypeTag
|
|
||||||
group = webhookData.Tag
|
|
||||||
case types.RunRefTypePullRequest:
|
|
||||||
groupType = common.GroupTypePullRequest
|
|
||||||
group = webhookData.PullRequestID
|
|
||||||
}
|
|
||||||
|
|
||||||
runGroup := common.GenRunGroup(baseGroupType, baseGroupID, groupType, group)
|
|
||||||
|
|
||||||
if err := h.createRuns(ctx, filename, data, runGroup, annotations, env, variables, webhookData); err != nil {
|
|
||||||
return http.StatusInternalServerError, "", errors.Errorf("failed to create run: %w", err)
|
return http.StatusInternalServerError, "", errors.Errorf("failed to create run: %w", err)
|
||||||
}
|
}
|
||||||
//if err := gitSource.CreateStatus(webhookData.Repo.Owner, webhookData.Repo.Name, webhookData.CommitSHA, gitsource.CommitStatusPending, "localhost:8080", "build %s", "agola"); err != nil {
|
|
||||||
// h.log.Errorf("failed to update commit status: %v", err)
|
|
||||||
//}
|
|
||||||
|
|
||||||
return 0, "", nil
|
return 0, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchConfigFiles tries to fetch a config file in one of the supported formats. The precedence is for jsonnet, then json and then yml
|
|
||||||
// TODO(sgotti) For jsonnet, if we'll support custom import files inside the configdir, also fetch them.
|
|
||||||
func (h *webhooksHandler) fetchConfigFiles(gitSource gitsource.GitSource, webhookData *types.WebhookData) ([]byte, string, error) {
|
|
||||||
var data []byte
|
|
||||||
var filename string
|
|
||||||
err := util.ExponentialBackoff(util.FetchFileBackoff, func() (bool, error) {
|
|
||||||
for _, filename = range []string{agolaDefaultJsonnetConfigFile, agolaDefaultJsonConfigFile, agolaDefaultYamlConfigFile} {
|
|
||||||
var err error
|
|
||||||
data, err = gitSource.GetFile(webhookData.Repo.Path, webhookData.CommitSHA, path.Join(agolaDefaultConfigDir, filename))
|
|
||||||
if err == nil {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
h.log.Errorf("get file err: %v", err)
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
return data, filename, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *webhooksHandler) createRuns(ctx context.Context, filename string, configData []byte, runGroup string, annotations, staticEnv, variables map[string]string, webhookData *types.WebhookData) error {
|
|
||||||
setupErrors := []string{}
|
|
||||||
|
|
||||||
var configFormat config.ConfigFormat
|
|
||||||
switch path.Ext(filename) {
|
|
||||||
case ".jsonnet":
|
|
||||||
configFormat = config.ConfigFormatJsonnet
|
|
||||||
case ".json":
|
|
||||||
fallthrough
|
|
||||||
case ".yml":
|
|
||||||
configFormat = config.ConfigFormatJSON
|
|
||||||
|
|
||||||
}
|
|
||||||
config, err := config.ParseConfig([]byte(configData), configFormat)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to parse config: %+v", err)
|
|
||||||
|
|
||||||
// create a run (per config file) with a generic error since we cannot parse
|
|
||||||
// it and know how many runs are defined
|
|
||||||
setupErrors = append(setupErrors, err.Error())
|
|
||||||
createRunReq := &rsapi.RunCreateRequest{
|
|
||||||
RunConfigTasks: nil,
|
|
||||||
Group: runGroup,
|
|
||||||
SetupErrors: setupErrors,
|
|
||||||
Name: rstypes.RunGenericSetupErrorName,
|
|
||||||
StaticEnvironment: staticEnv,
|
|
||||||
Annotations: annotations,
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, _, err := h.runserviceClient.CreateRun(ctx, createRunReq); err != nil {
|
|
||||||
log.Errorf("failed to create run: %+v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, run := range config.Runs {
|
|
||||||
rcts := runconfig.GenRunConfigTasks(util.DefaultUUIDGenerator{}, config, run.Name, variables, webhookData.Branch, webhookData.Tag, webhookData.Ref)
|
|
||||||
|
|
||||||
h.log.Debugf("rcts: %s", util.Dump(rcts))
|
|
||||||
h.log.Infof("group: %s", runGroup)
|
|
||||||
createRunReq := &rsapi.RunCreateRequest{
|
|
||||||
RunConfigTasks: rcts,
|
|
||||||
Group: runGroup,
|
|
||||||
SetupErrors: setupErrors,
|
|
||||||
Name: run.Name,
|
|
||||||
StaticEnvironment: staticEnv,
|
|
||||||
Annotations: annotations,
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, _, err := h.runserviceClient.CreateRun(ctx, createRunReq); err != nil {
|
|
||||||
log.Errorf("failed to create run: %+v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ import (
|
||||||
|
|
||||||
gitsource "github.com/sorintlab/agola/internal/gitsources"
|
gitsource "github.com/sorintlab/agola/internal/gitsources"
|
||||||
"github.com/sorintlab/agola/internal/services/common"
|
"github.com/sorintlab/agola/internal/services/common"
|
||||||
"github.com/sorintlab/agola/internal/services/gateway"
|
"github.com/sorintlab/agola/internal/services/gateway/action"
|
||||||
rstypes "github.com/sorintlab/agola/internal/services/runservice/types"
|
rstypes "github.com/sorintlab/agola/internal/services/runservice/types"
|
||||||
|
|
||||||
errors "golang.org/x/xerrors"
|
errors "golang.org/x/xerrors"
|
||||||
|
@ -98,7 +98,7 @@ func (n *NotificationService) updateCommitStatus(ctx context.Context, ev *rstype
|
||||||
description := statusDescription(commitStatus)
|
description := statusDescription(commitStatus)
|
||||||
context := fmt.Sprintf("%s/%s/%s", n.gc.ID, project.Name, run.RunConfig.Name)
|
context := fmt.Sprintf("%s/%s/%s", n.gc.ID, project.Name, run.RunConfig.Name)
|
||||||
|
|
||||||
if err := gitSource.CreateCommitStatus(project.RepositoryPath, run.Run.Annotations[gateway.AnnotationCommitSHA], commitStatus, targetURL, description, context); err != nil {
|
if err := gitSource.CreateCommitStatus(project.RepositoryPath, run.Run.Annotations[action.AnnotationCommitSHA], commitStatus, targetURL, description, context); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue