diff --git a/internal/config/config.go b/internal/config/config.go index 0efc11c..b0ba1c2 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -20,6 +20,7 @@ import ( "regexp" "strings" + itypes "agola.io/agola/internal/services/types" "agola.io/agola/internal/util" "agola.io/agola/services/types" @@ -645,11 +646,28 @@ func (r *Run) Task(taskName string) *Task { var DefaultConfig = Config{} -func ParseConfig(configData []byte, format ConfigFormat) (*Config, error) { +// ConfigContext is the context to pass to the config generator. Fields are not marked as omitempty since +// we want to provide all of them with empty value if not existing in such context +// (i.e. pull_request_id will be an empty string when not a pull request) +type ConfigContext struct { + RefType itypes.RunRefType `json:"ref_type"` + Ref string `json:"ref"` + Branch string `json:"branch"` + Tag string `json:"tag"` + PullRequestID string `json:"pull_request_id"` + CommitSHA string `json:"commit_sha"` +} + +func ParseConfig(configData []byte, format ConfigFormat, configContext *ConfigContext) (*Config, error) { // Generate json from jsonnet if format == ConfigFormatJsonnet { // TODO(sgotti) support custom import files inside the configdir ??? vm := jsonnet.MakeVM() + cj, err := json.Marshal(configContext) + if err != nil { + return nil, errors.Errorf("failed to marshal config context: %w", err) + } + vm.TLACode("ctx", string(cj)) out, err := vm.EvaluateSnippet("", string(configData)) if err != nil { return nil, errors.Errorf("failed to evaluate jsonnet config: %w", err) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 2879e49..6dca33e 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -58,7 +58,7 @@ func TestParseConfig(t *testing.T) { runs: - name: run01 tasks: - - + - `, err: fmt.Errorf(`run "run01": task at index 0 is empty`), }, @@ -203,7 +203,7 @@ func TestParseConfig(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if _, err := ParseConfig([]byte(tt.in), ConfigFormatJSON); err != nil { + if _, err := ParseConfig([]byte(tt.in), ConfigFormatJSON, &ConfigContext{}); err != nil { if tt.err == nil { t.Fatalf("got error: %v, expected no error", err) } @@ -593,7 +593,7 @@ func TestParseOutput(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - out, err := ParseConfig([]byte(tt.in), ConfigFormatJSON) + out, err := ParseConfig([]byte(tt.in), ConfigFormatJSON, &ConfigContext{}) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/internal/services/gateway/action/run.go b/internal/services/gateway/action/run.go index 801469b..9350cf2 100644 --- a/internal/services/gateway/action/run.go +++ b/internal/services/gateway/action/run.go @@ -495,7 +495,17 @@ func (h *ActionHandler) CreateRuns(ctx context.Context, req *CreateRunRequest) e configFormat = config.ConfigFormatJSON } - config, err := config.ParseConfig([]byte(data), configFormat) + + configContext := &config.ConfigContext{ + RefType: req.RefType, + Ref: req.Ref, + Branch: req.Branch, + Tag: req.Tag, + PullRequestID: req.PullRequestID, + CommitSHA: req.CommitSHA, + } + + config, err := config.ParseConfig([]byte(data), configFormat, configContext) if err != nil { h.log.Errorf("failed to parse config: %+v", err) diff --git a/tests/setup_test.go b/tests/setup_test.go index 7b64d55..83a6838 100644 --- a/tests/setup_test.go +++ b/tests/setup_test.go @@ -1680,3 +1680,189 @@ func TestPullRequest(t *testing.T) { }) } } + +func TestConfigContext(t *testing.T) { + config := ` +function(ctx) { + runs: [ + { + name: 'run01', + tasks: [ + { + name: 'task01', + runtime: { + containers: [ + { + image: 'alpine/git', + }, + ], + }, + environment: { + REF_TYPE: ctx.ref_type, + REF: ctx.ref, + BRANCH: ctx.branch, + TAG: ctx.tag, + PULL_REQUEST_ID: ctx.pull_request_id, + COMMIT_SHA: ctx.commit_sha, + }, + steps: [ + { type: 'clone' }, + { type: 'run', command: 'env' }, + ], + }, + ], + }, + ], +} +` + + tests := []struct { + name string + args []string + env map[string]string + }{ + { + name: "test direct run branch", + env: map[string]string{ + "REF_TYPE": "branch", + "REF": "refs/heads/master", + "BRANCH": "master", + "TAG": "", + "PULL_REQUEST_ID": "", + "COMMIT_SHA": "", + }, + }, + { + name: "test direct run tag", + args: []string{"--tag", "v0.1.0"}, + env: map[string]string{ + "REF_TYPE": "tag", + "REF": "refs/tags/v0.1.0", + "BRANCH": "", + "TAG": "v0.1.0", + "PULL_REQUEST_ID": "", + "COMMIT_SHA": "", + }, + }, + { + name: "test direct run with pr", + args: []string{"--ref", "refs/pull/1/head"}, + env: map[string]string{ + "REF_TYPE": "pull_request", + "REF": "refs/pull/1/head", + "BRANCH": "", + "TAG": "", + "PULL_REQUEST_ID": "1", + "COMMIT_SHA": "", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir, err := ioutil.TempDir("", "agola") + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + defer os.RemoveAll(dir) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + tetcd, tgitea, c := setup(ctx, t, dir) + defer shutdownGitea(tgitea) + defer shutdownEtcd(tetcd) + + gwClient := gwclient.NewClient(c.Gateway.APIExposedURL, "admintoken") + user, _, err := gwClient.CreateUser(ctx, &gwapitypes.CreateUserRequest{UserName: agolaUser01}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + t.Logf("created agola user: %s", user.UserName) + + token := createAgolaUserToken(ctx, t, c) + + // From now use the user token + gwClient = gwclient.NewClient(c.Gateway.APIExposedURL, token) + + directRun(t, dir, config, c.Gateway.APIExposedURL, token, tt.args...) + + // TODO(sgotti) add an util to wait for a run phase + _ = testutil.Wait(30*time.Second, func() (bool, error) { + runs, _, err := gwClient.GetRuns(ctx, nil, nil, []string{path.Join("/user", user.ID)}, nil, "", 0, false) + if err != nil { + return false, nil + } + + if len(runs) != 1 { + return false, nil + } + + run := runs[0] + if run.Phase != rstypes.RunPhaseFinished { + return false, nil + } + + return true, nil + }) + + runs, _, err := gwClient.GetRuns(ctx, nil, nil, []string{path.Join("/user", user.ID)}, nil, "", 0, false) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + + t.Logf("runs: %s", util.Dump(runs)) + + if len(runs) != 1 { + t.Fatalf("expected 1 run got: %d", len(runs)) + } + + run, _, err := gwClient.GetRun(ctx, runs[0].ID) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + if run.Phase != rstypes.RunPhaseFinished { + t.Fatalf("expected run phase %q, got %q", rstypes.RunPhaseFinished, run.Phase) + } + if run.Result != rstypes.RunResultSuccess { + t.Fatalf("expected run result %q, got %q", rstypes.RunResultSuccess, run.Result) + } + + var task *gwapitypes.RunResponseTask + for _, t := range run.Tasks { + if t.Name == "task01" { + task = t + break + } + } + + resp, err := gwClient.GetLogs(ctx, run.ID, task.ID, false, 1, false) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + defer resp.Body.Close() + + logs, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + curEnv, err := testutil.ParseEnvs(bytes.NewReader(logs)) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + + // update commit sha from annotations since it will change at every test + tt.env["COMMIT_SHA"] = run.Annotations["commit_sha"] + + for n, e := range tt.env { + if ce, ok := curEnv[n]; !ok { + t.Fatalf("missing env var %s", n) + } else { + if ce != e { + t.Fatalf("different env var %s value, want: %q, got %q", n, e, ce) + } + } + } + }) + } +}