Add custom enhancements
This commit is contained in:
parent
c051090583
commit
7815eec33b
14 changed files with 830 additions and 2 deletions
185
pkg/jobparser/evaluator.go
Normal file
185
pkg/jobparser/evaluator.go
Normal file
|
@ -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
|
||||||
|
}
|
81
pkg/jobparser/interpeter.go
Normal file
81
pkg/jobparser/interpeter.go
Normal file
|
@ -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
|
||||||
|
}
|
152
pkg/jobparser/jobparser.go
Normal file
152
pkg/jobparser/jobparser.go
Normal file
|
@ -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
|
||||||
|
})
|
||||||
|
}
|
65
pkg/jobparser/jobparser_test.go
Normal file
65
pkg/jobparser/jobparser_test.go
Normal file
|
@ -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())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
123
pkg/jobparser/model.go
Normal file
123
pkg/jobparser/model.go
Normal file
|
@ -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"`
|
||||||
|
}
|
16
pkg/jobparser/testdata/has_needs.in.yaml
vendored
Normal file
16
pkg/jobparser/testdata/has_needs.in.yaml
vendored
Normal file
|
@ -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]
|
23
pkg/jobparser/testdata/has_needs.out.yaml
vendored
Normal file
23
pkg/jobparser/testdata/has_needs.out.yaml
vendored
Normal file
|
@ -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
|
14
pkg/jobparser/testdata/multiple_jobs.in.yaml
vendored
Normal file
14
pkg/jobparser/testdata/multiple_jobs.in.yaml
vendored
Normal file
|
@ -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
|
23
pkg/jobparser/testdata/multiple_jobs.out.yaml
vendored
Normal file
23
pkg/jobparser/testdata/multiple_jobs.out.yaml
vendored
Normal file
|
@ -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
|
13
pkg/jobparser/testdata/multiple_matrix.in.yaml
vendored
Normal file
13
pkg/jobparser/testdata/multiple_matrix.in.yaml
vendored
Normal file
|
@ -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
|
101
pkg/jobparser/testdata/multiple_matrix.out.yaml
vendored
Normal file
101
pkg/jobparser/testdata/multiple_matrix.out.yaml
vendored
Normal file
|
@ -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
|
|
@ -164,6 +164,13 @@ func NewWorkflowPlanner(path string, noWorkflowRecurse bool) (WorkflowPlanner, e
|
||||||
return wp, nil
|
return wp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CombineWorkflowPlanner combines workflows to a WorkflowPlanner
|
||||||
|
func CombineWorkflowPlanner(workflows ...*Workflow) WorkflowPlanner {
|
||||||
|
return &workflowPlanner{
|
||||||
|
workflows: workflows,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type workflowPlanner struct {
|
type workflowPlanner struct {
|
||||||
workflows []*Workflow
|
workflows []*Workflow
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ func (rc *RunContext) GetEnv() map[string]string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *RunContext) jobContainerName() 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
|
// 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"
|
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
|
repoPath := rc.Config.Workdir
|
||||||
repo, err := git.FindGithubRepo(ctx, repoPath, rc.Config.GitHubInstance, rc.Config.RemoteName)
|
repo, err := git.FindGithubRepo(ctx, repoPath, rc.Config.GitHubInstance, rc.Config.RemoteName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -55,6 +55,10 @@ type Config struct {
|
||||||
RemoteName string // remote name in local git repo config
|
RemoteName string // remote name in local git repo config
|
||||||
ReplaceGheActionWithGithubCom []string // Use actions from GitHub Enterprise instance to GitHub
|
ReplaceGheActionWithGithubCom []string // Use actions from GitHub Enterprise instance to GitHub
|
||||||
ReplaceGheActionTokenWithGithubCom string // Token of private action repo on 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
|
// Resolves the equivalent host path inside the container
|
||||||
|
@ -109,7 +113,9 @@ func New(runnerConfig *Config) (Runner, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
runner.eventJSON = "{}"
|
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)
|
log.Debugf("Reading event.json from %s", runner.config.EventPath)
|
||||||
eventJSONBytes, err := os.ReadFile(runner.config.EventPath)
|
eventJSONBytes, err := os.ReadFile(runner.config.EventPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Add table
Reference in a new issue