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:
Simone Gotti 2019-06-11 09:31:12 +02:00
parent 5b22ebc2d3
commit 8242dc3a9d
4 changed files with 340 additions and 259 deletions

View File

@ -18,15 +18,52 @@ import (
"context"
"encoding/json"
"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"
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/util"
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) {
runResp, resp, err := h.runserviceClient.GetRun(ctx, runID, nil)
if err != nil {
@ -249,3 +286,270 @@ func (h *ActionHandler) RunTaskAction(ctx context.Context, req *RunTaskActionsRe
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
}

View File

@ -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)
}
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) {
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{}
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
}

View File

@ -15,20 +15,15 @@
package gateway
import (
"context"
"fmt"
"net/http"
"path"
"github.com/sorintlab/agola/internal/config"
gitsource "github.com/sorintlab/agola/internal/gitsources"
"github.com/sorintlab/agola/internal/gitsources/agolagit"
"github.com/sorintlab/agola/internal/runconfig"
"github.com/sorintlab/agola/internal/services/common"
csapi "github.com/sorintlab/agola/internal/services/configstore/api"
"github.com/sorintlab/agola/internal/services/gateway/action"
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/util"
@ -36,37 +31,6 @@ import (
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 {
log *zap.SugaredLogger
ah *action.ActionHandler
@ -106,7 +70,6 @@ func (h *webhooksHandler) handleWebhook(r *http.Request) (int, string, error) {
var cloneURL string
var sshHostKey string
var skipSSHHostKeyCheck bool
variables := map[string]string{}
var gitSource gitsource.GitSource
if runType == types.RunTypeProject {
@ -114,7 +77,6 @@ func (h *webhooksHandler) handleWebhook(r *http.Request) (int, string, error) {
if err != nil {
return http.StatusBadRequest, "", errors.Errorf("failed to get project %s: %w", projectID, err)
}
h.log.Infof("project: %s", util.Dump(project))
project = csProject.Project
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
// 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 {
gitSource = agolagit.New(h.apiExposedURL + "/repos")
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))
data, filename, err := h.fetchConfigFiles(gitSource, webhookData)
if err != nil {
return http.StatusInternalServerError, "", errors.Errorf("failed to fetch config file: %w", err)
}
h.log.Debug("data: %s", data)
req := &action.CreateRunRequest{
RunType: runType,
RefType: common.WebHookEventToRunRefType(webhookData.Event),
RunCreationTrigger: types.RunCreationTriggerTypeWebhook,
gitURL, err := util.ParseGitURL(cloneURL)
if err != nil {
return http.StatusInternalServerError, "", errors.Errorf("failed to parse clone url: %w", err)
}
gitHost := gitURL.Hostname()
gitPort := gitURL.Port()
if gitPort == "" {
gitPort = defaultSSHPort
}
Project: project,
User: user,
RepoPath: webhookData.Repo.Path,
GitSource: gitSource,
CommitSHA: webhookData.CommitSHA,
Message: webhookData.Message,
Branch: webhookData.Branch,
Tag: webhookData.Tag,
PullRequestID: webhookData.PullRequestID,
Ref: webhookData.Ref,
SSHPrivKey: sshPrivKey,
SSHHostKey: sshHostKey,
SkipSSHHostKeyCheck: skipSSHHostKeyCheck,
CloneURL: cloneURL,
// this env vars ovverrides other env vars
env := map[string]string{
"CI": "true",
"AGOLA_SSHPRIVKEY": sshPrivKey,
"AGOLA_REPOSITORY_URL": cloneURL,
"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,
CommitLink: webhookData.CommitLink,
BranchLink: webhookData.BranchLink,
TagLink: webhookData.TagLink,
PullRequestLink: webhookData.PullRequestLink,
CompareLink: webhookData.CompareLink,
}
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 {
if err := h.ah.CreateRuns(ctx, req); err != nil {
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
}
// 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
}

View File

@ -21,7 +21,7 @@ import (
gitsource "github.com/sorintlab/agola/internal/gitsources"
"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"
errors "golang.org/x/xerrors"
@ -98,7 +98,7 @@ func (n *NotificationService) updateCommitStatus(ctx context.Context, ev *rstype
description := statusDescription(commitStatus)
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
}