diff --git a/pkg/jobparser/evaluator.go b/pkg/jobparser/evaluator.go new file mode 100644 index 0000000..80a1397 --- /dev/null +++ b/pkg/jobparser/evaluator.go @@ -0,0 +1,185 @@ +package jobparser + +import ( + "fmt" + "regexp" + "strings" + + "github.com/nektos/act/pkg/exprparser" + "gopkg.in/yaml.v3" +) + +// ExpressionEvaluator is copied from runner.expressionEvaluator, +// to avoid unnecessary dependencies +type ExpressionEvaluator struct { + interpreter exprparser.Interpreter +} + +func NewExpressionEvaluator(interpreter exprparser.Interpreter) *ExpressionEvaluator { + return &ExpressionEvaluator{interpreter: interpreter} +} + +func (ee ExpressionEvaluator) evaluate(in string, defaultStatusCheck exprparser.DefaultStatusCheck) (interface{}, error) { + evaluated, err := ee.interpreter.Evaluate(in, defaultStatusCheck) + + return evaluated, err +} + +func (ee ExpressionEvaluator) evaluateScalarYamlNode(node *yaml.Node) error { + var in string + if err := node.Decode(&in); err != nil { + return err + } + if !strings.Contains(in, "${{") || !strings.Contains(in, "}}") { + return nil + } + expr, _ := rewriteSubExpression(in, false) + res, err := ee.evaluate(expr, exprparser.DefaultStatusCheckNone) + if err != nil { + return err + } + return node.Encode(res) +} + +func (ee ExpressionEvaluator) evaluateMappingYamlNode(node *yaml.Node) error { + // GitHub has this undocumented feature to merge maps, called insert directive + insertDirective := regexp.MustCompile(`\${{\s*insert\s*}}`) + for i := 0; i < len(node.Content)/2; { + k := node.Content[i*2] + v := node.Content[i*2+1] + if err := ee.EvaluateYamlNode(v); err != nil { + return err + } + var sk string + // Merge the nested map of the insert directive + if k.Decode(&sk) == nil && insertDirective.MatchString(sk) { + node.Content = append(append(node.Content[:i*2], v.Content...), node.Content[(i+1)*2:]...) + i += len(v.Content) / 2 + } else { + if err := ee.EvaluateYamlNode(k); err != nil { + return err + } + i++ + } + } + return nil +} + +func (ee ExpressionEvaluator) evaluateSequenceYamlNode(node *yaml.Node) error { + for i := 0; i < len(node.Content); { + v := node.Content[i] + // Preserve nested sequences + wasseq := v.Kind == yaml.SequenceNode + if err := ee.EvaluateYamlNode(v); err != nil { + return err + } + // GitHub has this undocumented feature to merge sequences / arrays + // We have a nested sequence via evaluation, merge the arrays + if v.Kind == yaml.SequenceNode && !wasseq { + node.Content = append(append(node.Content[:i], v.Content...), node.Content[i+1:]...) + i += len(v.Content) + } else { + i++ + } + } + return nil +} + +func (ee ExpressionEvaluator) EvaluateYamlNode(node *yaml.Node) error { + switch node.Kind { + case yaml.ScalarNode: + return ee.evaluateScalarYamlNode(node) + case yaml.MappingNode: + return ee.evaluateMappingYamlNode(node) + case yaml.SequenceNode: + return ee.evaluateSequenceYamlNode(node) + default: + return nil + } +} + +func (ee ExpressionEvaluator) Interpolate(in string) string { + if !strings.Contains(in, "${{") || !strings.Contains(in, "}}") { + return in + } + + expr, _ := rewriteSubExpression(in, true) + evaluated, err := ee.evaluate(expr, exprparser.DefaultStatusCheckNone) + if err != nil { + return "" + } + + value, ok := evaluated.(string) + if !ok { + panic(fmt.Sprintf("Expression %s did not evaluate to a string", expr)) + } + + return value +} + +func escapeFormatString(in string) string { + return strings.ReplaceAll(strings.ReplaceAll(in, "{", "{{"), "}", "}}") +} + +func rewriteSubExpression(in string, forceFormat bool) (string, error) { + if !strings.Contains(in, "${{") || !strings.Contains(in, "}}") { + return in, nil + } + + strPattern := regexp.MustCompile("(?:''|[^'])*'") + pos := 0 + exprStart := -1 + strStart := -1 + var results []string + formatOut := "" + for pos < len(in) { + if strStart > -1 { + matches := strPattern.FindStringIndex(in[pos:]) + if matches == nil { + panic("unclosed string.") + } + + strStart = -1 + pos += matches[1] + } else if exprStart > -1 { + exprEnd := strings.Index(in[pos:], "}}") + strStart = strings.Index(in[pos:], "'") + + if exprEnd > -1 && strStart > -1 { + if exprEnd < strStart { + strStart = -1 + } else { + exprEnd = -1 + } + } + + if exprEnd > -1 { + formatOut += fmt.Sprintf("{%d}", len(results)) + results = append(results, strings.TrimSpace(in[exprStart:pos+exprEnd])) + pos += exprEnd + 2 + exprStart = -1 + } else if strStart > -1 { + pos += strStart + 1 + } else { + panic("unclosed expression.") + } + } else { + exprStart = strings.Index(in[pos:], "${{") + if exprStart != -1 { + formatOut += escapeFormatString(in[pos : pos+exprStart]) + exprStart = pos + exprStart + 3 + pos = exprStart + } else { + formatOut += escapeFormatString(in[pos:]) + pos = len(in) + } + } + } + + if len(results) == 1 && formatOut == "{0}" && !forceFormat { + return in, nil + } + + out := fmt.Sprintf("format('%s', %s)", strings.ReplaceAll(formatOut, "'", "''"), strings.Join(results, ", ")) + return out, nil +} diff --git a/pkg/jobparser/interpeter.go b/pkg/jobparser/interpeter.go new file mode 100644 index 0000000..65e66eb --- /dev/null +++ b/pkg/jobparser/interpeter.go @@ -0,0 +1,81 @@ +package jobparser + +import ( + "github.com/nektos/act/pkg/exprparser" + "github.com/nektos/act/pkg/model" + "gopkg.in/yaml.v3" +) + +// NewInterpeter returns an interpeter used in the server, +// need github, needs, strategy, matrix, inputs context only, +// see https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability +func NewInterpeter( + jobID string, + job *model.Job, + matrix map[string]interface{}, + gitCtx *model.GithubContext, + results map[string]*JobResult, +) exprparser.Interpreter { + + strategy := make(map[string]interface{}) + if job.Strategy != nil { + strategy["fail-fast"] = job.Strategy.FailFast + strategy["max-parallel"] = job.Strategy.MaxParallel + } + + run := &model.Run{ + Workflow: &model.Workflow{ + Jobs: map[string]*model.Job{}, + }, + JobID: jobID, + } + for id, result := range results { + need := yaml.Node{} + _ = need.Encode(result.Needs) + run.Workflow.Jobs[id] = &model.Job{ + RawNeeds: need, + Result: result.Result, + Outputs: result.Outputs, + } + } + + jobs := run.Workflow.Jobs + jobNeeds := run.Job().Needs() + + using := map[string]map[string]map[string]string{} + for _, need := range jobNeeds { + if v, ok := jobs[need]; ok { + using[need] = map[string]map[string]string{ + "outputs": v.Outputs, + } + } + } + + ee := &exprparser.EvaluationEnvironment{ + Github: gitCtx, + Env: nil, // no need + Job: nil, // no need + Steps: nil, // no need + Runner: nil, // no need + Secrets: nil, // no need + Strategy: strategy, + Matrix: matrix, + Needs: using, + Inputs: nil, // not supported yet + } + + config := exprparser.Config{ + Run: run, + WorkingDir: "", // WorkingDir is used for the function hashFiles, but it's not needed in the server + Context: "job", + } + + return exprparser.NewInterpeter(ee, config) +} + +// JobResult is the minimum requirement of job results for Interpeter +type JobResult struct { + Needs []string + Result string + Outputs map[string]string +} diff --git a/pkg/jobparser/jobparser.go b/pkg/jobparser/jobparser.go new file mode 100644 index 0000000..33b900d --- /dev/null +++ b/pkg/jobparser/jobparser.go @@ -0,0 +1,152 @@ +package jobparser + +import ( + "bytes" + "fmt" + "sort" + "strings" + + "gopkg.in/yaml.v3" + + "github.com/nektos/act/pkg/model" +) + +func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) { + origin, err := model.ReadWorkflow(bytes.NewReader(content)) + if err != nil { + return nil, fmt.Errorf("model.ReadWorkflow: %w", err) + } + + workflow := &SingleWorkflow{} + if err := yaml.Unmarshal(content, workflow); err != nil { + return nil, fmt.Errorf("yaml.Unmarshal: %w", err) + } + + pc := &parseContext{} + for _, o := range options { + o(pc) + } + results := map[string]*JobResult{} + for id, job := range origin.Jobs { + results[id] = &JobResult{ + Needs: job.Needs(), + Result: pc.jobResults[id], + Outputs: nil, // not supported yet + } + } + + var ret []*SingleWorkflow + for id, job := range workflow.Jobs { + for _, matrix := range getMatrixes(origin.GetJob(id)) { + job := job.Clone() + if job.Name == "" { + job.Name = id + } + job.Name = nameWithMatrix(job.Name, matrix) + job.Strategy.RawMatrix = encodeMatrix(matrix) + evaluator := NewExpressionEvaluator(NewInterpeter(id, origin.GetJob(id), matrix, pc.gitContext, results)) + runsOn := origin.GetJob(id).RunsOn() + for i, v := range runsOn { + runsOn[i] = evaluator.Interpolate(v) + } + job.RawRunsOn = encodeRunsOn(runsOn) + ret = append(ret, &SingleWorkflow{ + Name: workflow.Name, + RawOn: workflow.RawOn, + Env: workflow.Env, + Jobs: map[string]*Job{id: job}, + Defaults: workflow.Defaults, + }) + } + } + sortWorkflows(ret) + return ret, nil +} + +func WithJobResults(results map[string]string) ParseOption { + return func(c *parseContext) { + c.jobResults = results + } +} + +func WithGitContext(context *model.GithubContext) ParseOption { + return func(c *parseContext) { + c.gitContext = context + } +} + +type parseContext struct { + jobResults map[string]string + gitContext *model.GithubContext +} + +type ParseOption func(c *parseContext) + +func getMatrixes(job *model.Job) []map[string]interface{} { + ret := job.GetMatrixes() + sort.Slice(ret, func(i, j int) bool { + return matrixName(ret[i]) < matrixName(ret[j]) + }) + return ret +} + +func encodeMatrix(matrix map[string]interface{}) yaml.Node { + if len(matrix) == 0 { + return yaml.Node{} + } + value := map[string][]interface{}{} + for k, v := range matrix { + value[k] = []interface{}{v} + } + node := yaml.Node{} + _ = node.Encode(value) + return node +} + +func encodeRunsOn(runsOn []string) yaml.Node { + node := yaml.Node{} + if len(runsOn) == 1 { + _ = node.Encode(runsOn[0]) + } else { + _ = node.Encode(runsOn) + } + return node +} + +func nameWithMatrix(name string, m map[string]interface{}) string { + if len(m) == 0 { + return name + } + + return name + " " + matrixName(m) +} + +func matrixName(m map[string]interface{}) string { + ks := make([]string, 0, len(m)) + for k := range m { + ks = append(ks, k) + } + sort.Strings(ks) + vs := make([]string, 0, len(m)) + for _, v := range ks { + vs = append(vs, fmt.Sprint(m[v])) + } + + return fmt.Sprintf("(%s)", strings.Join(vs, ", ")) +} + +func sortWorkflows(wfs []*SingleWorkflow) { + sort.Slice(wfs, func(i, j int) bool { + ki := "" + for k := range wfs[i].Jobs { + ki = k + break + } + kj := "" + for k := range wfs[j].Jobs { + kj = k + break + } + return ki < kj + }) +} diff --git a/pkg/jobparser/jobparser_test.go b/pkg/jobparser/jobparser_test.go new file mode 100644 index 0000000..6872c29 --- /dev/null +++ b/pkg/jobparser/jobparser_test.go @@ -0,0 +1,65 @@ +package jobparser + +import ( + "embed" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/stretchr/testify/require" + + "gopkg.in/yaml.v3" +) + +//go:embed testdata +var f embed.FS + +func TestParse(t *testing.T) { + tests := []struct { + name string + options []ParseOption + wantErr bool + }{ + { + name: "multiple_jobs", + options: nil, + wantErr: false, + }, + { + name: "multiple_matrix", + options: nil, + wantErr: false, + }, + { + name: "has_needs", + options: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + content, err := f.ReadFile(filepath.Join("testdata", tt.name+".in.yaml")) + require.NoError(t, err) + want, err := f.ReadFile(filepath.Join("testdata", tt.name+".out.yaml")) + require.NoError(t, err) + got, err := Parse(content, tt.options...) + if tt.wantErr { + require.Error(t, err) + } + require.NoError(t, err) + + builder := &strings.Builder{} + for _, v := range got { + if builder.Len() > 0 { + builder.WriteString("---\n") + } + encoder := yaml.NewEncoder(builder) + encoder.SetIndent(2) + _ = encoder.Encode(v) + } + assert.Equal(t, string(want), builder.String()) + }) + } +} diff --git a/pkg/jobparser/model.go b/pkg/jobparser/model.go new file mode 100644 index 0000000..b6d8d56 --- /dev/null +++ b/pkg/jobparser/model.go @@ -0,0 +1,123 @@ +package jobparser + +import ( + "github.com/nektos/act/pkg/model" + "gopkg.in/yaml.v3" +) + +// SingleWorkflow is a workflow with single job and single matrix +type SingleWorkflow struct { + Name string `yaml:"name,omitempty"` + RawOn yaml.Node `yaml:"on,omitempty"` + Env map[string]string `yaml:"env,omitempty"` + Jobs map[string]*Job `yaml:"jobs,omitempty"` + Defaults Defaults `yaml:"defaults,omitempty"` +} + +func (w *SingleWorkflow) Job() (string, *Job) { + for k, v := range w.Jobs { + return k, v + } + return "", nil +} + +func (w *SingleWorkflow) Marshal() ([]byte, error) { + return yaml.Marshal(w) +} + +type Job struct { + Name string `yaml:"name,omitempty"` + RawNeeds yaml.Node `yaml:"needs,omitempty"` + RawRunsOn yaml.Node `yaml:"runs-on,omitempty"` + Env yaml.Node `yaml:"env,omitempty"` + If yaml.Node `yaml:"if,omitempty"` + Steps []*Step `yaml:"steps,omitempty"` + TimeoutMinutes string `yaml:"timeout-minutes,omitempty"` + Services map[string]*ContainerSpec `yaml:"services,omitempty"` + Strategy Strategy `yaml:"strategy,omitempty"` + RawContainer yaml.Node `yaml:"container,omitempty"` + Defaults Defaults `yaml:"defaults,omitempty"` + Outputs map[string]string `yaml:"outputs,omitempty"` + Uses string `yaml:"uses,omitempty"` +} + +func (j *Job) Clone() *Job { + if j == nil { + return nil + } + return &Job{ + Name: j.Name, + RawNeeds: j.RawNeeds, + RawRunsOn: j.RawRunsOn, + Env: j.Env, + If: j.If, + Steps: j.Steps, + TimeoutMinutes: j.TimeoutMinutes, + Services: j.Services, + Strategy: j.Strategy, + RawContainer: j.RawContainer, + Defaults: j.Defaults, + Outputs: j.Outputs, + Uses: j.Uses, + } +} + +func (j *Job) Needs() []string { + return (&model.Job{RawNeeds: j.RawNeeds}).Needs() +} + +func (j *Job) EraseNeeds() { + j.RawNeeds = yaml.Node{} +} + +func (j *Job) RunsOn() []string { + return (&model.Job{RawRunsOn: j.RawRunsOn}).RunsOn() +} + +type Step struct { + ID string `yaml:"id,omitempty"` + If yaml.Node `yaml:"if,omitempty"` + Name string `yaml:"name,omitempty"` + Uses string `yaml:"uses,omitempty"` + Run string `yaml:"run,omitempty"` + WorkingDirectory string `yaml:"working-directory,omitempty"` + Shell string `yaml:"shell,omitempty"` + Env yaml.Node `yaml:"env,omitempty"` + With map[string]string `yaml:"with,omitempty"` + ContinueOnError bool `yaml:"continue-on-error,omitempty"` + TimeoutMinutes string `yaml:"timeout-minutes,omitempty"` +} + +// String gets the name of step +func (s *Step) String() string { + return (&model.Step{ + ID: s.ID, + Name: s.Name, + Uses: s.Uses, + Run: s.Run, + }).String() +} + +type ContainerSpec struct { + Image string `yaml:"image,omitempty"` + Env map[string]string `yaml:"env,omitempty"` + Ports []string `yaml:"ports,omitempty"` + Volumes []string `yaml:"volumes,omitempty"` + Options string `yaml:"options,omitempty"` + Credentials map[string]string `yaml:"credentials,omitempty"` +} + +type Strategy struct { + FailFastString string `yaml:"fail-fast,omitempty"` + MaxParallelString string `yaml:"max-parallel,omitempty"` + RawMatrix yaml.Node `yaml:"matrix,omitempty"` +} + +type Defaults struct { + Run RunDefaults `yaml:"run,omitempty"` +} + +type RunDefaults struct { + Shell string `yaml:"shell,omitempty"` + WorkingDirectory string `yaml:"working-directory,omitempty"` +} diff --git a/pkg/jobparser/testdata/has_needs.in.yaml b/pkg/jobparser/testdata/has_needs.in.yaml new file mode 100644 index 0000000..a7d1f9b --- /dev/null +++ b/pkg/jobparser/testdata/has_needs.in.yaml @@ -0,0 +1,16 @@ +name: test +jobs: + job1: + runs-on: linux + steps: + - run: uname -a + job2: + runs-on: linux + steps: + - run: uname -a + needs: job1 + job3: + runs-on: linux + steps: + - run: uname -a + needs: [job1, job2] diff --git a/pkg/jobparser/testdata/has_needs.out.yaml b/pkg/jobparser/testdata/has_needs.out.yaml new file mode 100644 index 0000000..959960d --- /dev/null +++ b/pkg/jobparser/testdata/has_needs.out.yaml @@ -0,0 +1,23 @@ +name: test +jobs: + job1: + name: job1 + runs-on: linux + steps: + - run: uname -a +--- +name: test +jobs: + job2: + name: job2 + runs-on: linux + steps: + - run: uname -a +--- +name: test +jobs: + job3: + name: job3 + runs-on: linux + steps: + - run: uname -a diff --git a/pkg/jobparser/testdata/multiple_jobs.in.yaml b/pkg/jobparser/testdata/multiple_jobs.in.yaml new file mode 100644 index 0000000..3474ce1 --- /dev/null +++ b/pkg/jobparser/testdata/multiple_jobs.in.yaml @@ -0,0 +1,14 @@ +name: test +jobs: + job1: + runs-on: linux + steps: + - run: uname -a && go version + job2: + runs-on: linux + steps: + - run: uname -a && go version + job3: + runs-on: linux + steps: + - run: uname -a && go version \ No newline at end of file diff --git a/pkg/jobparser/testdata/multiple_jobs.out.yaml b/pkg/jobparser/testdata/multiple_jobs.out.yaml new file mode 100644 index 0000000..2852d9d --- /dev/null +++ b/pkg/jobparser/testdata/multiple_jobs.out.yaml @@ -0,0 +1,23 @@ +name: test +jobs: + job1: + name: job1 + runs-on: linux + steps: + - run: uname -a && go version +--- +name: test +jobs: + job2: + name: job2 + runs-on: linux + steps: + - run: uname -a && go version +--- +name: test +jobs: + job3: + name: job3 + runs-on: linux + steps: + - run: uname -a && go version diff --git a/pkg/jobparser/testdata/multiple_matrix.in.yaml b/pkg/jobparser/testdata/multiple_matrix.in.yaml new file mode 100644 index 0000000..99985f3 --- /dev/null +++ b/pkg/jobparser/testdata/multiple_matrix.in.yaml @@ -0,0 +1,13 @@ +name: test +jobs: + job1: + strategy: + matrix: + os: [ubuntu-22.04, ubuntu-20.04] + version: [1.17, 1.18, 1.19] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.version }} + - run: uname -a && go version \ No newline at end of file diff --git a/pkg/jobparser/testdata/multiple_matrix.out.yaml b/pkg/jobparser/testdata/multiple_matrix.out.yaml new file mode 100644 index 0000000..e277cdd --- /dev/null +++ b/pkg/jobparser/testdata/multiple_matrix.out.yaml @@ -0,0 +1,101 @@ +name: test +jobs: + job1: + name: job1 (ubuntu-20.04, 1.17) + runs-on: ubuntu-20.04 + steps: + - uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.version }} + - run: uname -a && go version + strategy: + matrix: + os: + - ubuntu-20.04 + version: + - 1.17 +--- +name: test +jobs: + job1: + name: job1 (ubuntu-20.04, 1.18) + runs-on: ubuntu-20.04 + steps: + - uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.version }} + - run: uname -a && go version + strategy: + matrix: + os: + - ubuntu-20.04 + version: + - 1.18 +--- +name: test +jobs: + job1: + name: job1 (ubuntu-20.04, 1.19) + runs-on: ubuntu-20.04 + steps: + - uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.version }} + - run: uname -a && go version + strategy: + matrix: + os: + - ubuntu-20.04 + version: + - 1.19 +--- +name: test +jobs: + job1: + name: job1 (ubuntu-22.04, 1.17) + runs-on: ubuntu-22.04 + steps: + - uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.version }} + - run: uname -a && go version + strategy: + matrix: + os: + - ubuntu-22.04 + version: + - 1.17 +--- +name: test +jobs: + job1: + name: job1 (ubuntu-22.04, 1.18) + runs-on: ubuntu-22.04 + steps: + - uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.version }} + - run: uname -a && go version + strategy: + matrix: + os: + - ubuntu-22.04 + version: + - 1.18 +--- +name: test +jobs: + job1: + name: job1 (ubuntu-22.04, 1.19) + runs-on: ubuntu-22.04 + steps: + - uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.version }} + - run: uname -a && go version + strategy: + matrix: + os: + - ubuntu-22.04 + version: + - 1.19 diff --git a/pkg/model/planner.go b/pkg/model/planner.go index 73e3488..94cec51 100644 --- a/pkg/model/planner.go +++ b/pkg/model/planner.go @@ -164,6 +164,13 @@ func NewWorkflowPlanner(path string, noWorkflowRecurse bool) (WorkflowPlanner, e return wp, nil } +// CombineWorkflowPlanner combines workflows to a WorkflowPlanner +func CombineWorkflowPlanner(workflows ...*Workflow) WorkflowPlanner { + return &workflowPlanner{ + workflows: workflows, + } +} + type workflowPlanner struct { workflows []*Workflow } diff --git a/pkg/runner/run_context.go b/pkg/runner/run_context.go index 2e945ad..5abe348 100644 --- a/pkg/runner/run_context.go +++ b/pkg/runner/run_context.go @@ -66,7 +66,7 @@ func (rc *RunContext) GetEnv() map[string]string { } func (rc *RunContext) jobContainerName() string { - return createContainerName("act", rc.String()) + return createContainerName(rc.Config.ContainerNamePrefix, rc.String()) } // Returns the binds and mounts for the container, resolving paths as appopriate @@ -447,6 +447,25 @@ func (rc *RunContext) getGithubContext(ctx context.Context) *model.GithubContext ghc.Actor = "nektos/act" } + if preset := rc.Config.PresetGitHubContext; preset != nil { + ghc.Event = preset.Event + ghc.RunID = preset.RunID + ghc.RunNumber = preset.RunNumber + ghc.Actor = preset.Actor + ghc.Repository = preset.Repository + ghc.EventName = preset.EventName + ghc.Sha = preset.Sha + ghc.Ref = preset.Ref + ghc.RefName = preset.RefName + ghc.RefType = preset.RefType + ghc.HeadRef = preset.HeadRef + ghc.BaseRef = preset.BaseRef + ghc.Token = preset.Token + ghc.RepositoryOwner = preset.RepositoryOwner + ghc.RetentionDays = preset.RetentionDays + return ghc + } + repoPath := rc.Config.Workdir repo, err := git.FindGithubRepo(ctx, repoPath, rc.Config.GitHubInstance, rc.Config.RemoteName) if err != nil { diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index 9c10fbe..f92d376 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -55,6 +55,10 @@ type Config struct { RemoteName string // remote name in local git repo config ReplaceGheActionWithGithubCom []string // Use actions from GitHub Enterprise instance to GitHub ReplaceGheActionTokenWithGithubCom string // Token of private action repo on GitHub. + + PresetGitHubContext *model.GithubContext // the preset github context, overrides some fields like DefaultBranch, Env, Secrets etc. + EventJSON string // the content of JSON file to use for event.json in containers, overrides EventPath + ContainerNamePrefix string // the prefix of container name } // Resolves the equivalent host path inside the container @@ -109,7 +113,9 @@ func New(runnerConfig *Config) (Runner, error) { } runner.eventJSON = "{}" - if runnerConfig.EventPath != "" { + if runnerConfig.EventJSON != "" { + runner.eventJSON = runnerConfig.EventJSON + } else if runnerConfig.EventPath != "" { log.Debugf("Reading event.json from %s", runner.config.EventPath) eventJSONBytes, err := os.ReadFile(runner.config.EventPath) if err != nil {