Compare commits
12 Commits
nektos/v0.
...
v0.233.4
Author | SHA1 | Date | |
---|---|---|---|
|
c6b8548d35 | ||
|
64cae197a4 | ||
|
7fb84a54a8 | ||
|
70cc6c017b | ||
|
d7e9ea75fc | ||
|
b9c20dcaa4 | ||
|
97629ae8af | ||
|
b9a9812ad9 | ||
|
113c3e98fb | ||
|
7815eec33b | ||
|
c051090583 | ||
|
0fa1fe0310 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -31,3 +31,4 @@ coverage.txt
|
|||||||
|
|
||||||
# megalinter
|
# megalinter
|
||||||
report/
|
report/
|
||||||
|
act
|
||||||
|
1
LICENSE
1
LICENSE
@@ -1,5 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 The Gitea Authors
|
||||||
Copyright (c) 2019
|
Copyright (c) 2019
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
14
README.md
14
README.md
@@ -1,3 +1,17 @@
|
|||||||
|
## Naming rules:
|
||||||
|
|
||||||
|
Branches:
|
||||||
|
|
||||||
|
- `main`: default branch, contains custom changes.
|
||||||
|
- `nektos/master`: mirror for `master` of [nektos/act](https://github.com/nektos/act/).
|
||||||
|
|
||||||
|
Tags:
|
||||||
|
|
||||||
|
- `vX.YZ.*`: based on `nektos/vX.Y.Z`, contains custom changes.
|
||||||
|
- `nektos/vX.Y.Z`: mirror for `vX.Y.Z` of [nektos/act](https://github.com/nektos/act/).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
# Overview [](https://github.com/nektos/act/actions) [](https://gitter.im/nektos/act?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://goreportcard.com/report/github.com/nektos/act) [](https://github.com/jonico/awesome-runners)
|
# Overview [](https://github.com/nektos/act/actions) [](https://gitter.im/nektos/act?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://goreportcard.com/report/github.com/nektos/act) [](https://github.com/jonico/awesome-runners)
|
||||||
|
@@ -25,3 +25,24 @@ func Logger(ctx context.Context) logrus.FieldLogger {
|
|||||||
func WithLogger(ctx context.Context, logger logrus.FieldLogger) context.Context {
|
func WithLogger(ctx context.Context, logger logrus.FieldLogger) context.Context {
|
||||||
return context.WithValue(ctx, loggerContextKeyVal, logger)
|
return context.WithValue(ctx, loggerContextKeyVal, logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type loggerHookKey string
|
||||||
|
|
||||||
|
const loggerHookKeyVal = loggerHookKey("logrus.Hook")
|
||||||
|
|
||||||
|
// LoggerHook returns the appropriate logger hook for current context
|
||||||
|
// the hook affects job logger, not global logger
|
||||||
|
func LoggerHook(ctx context.Context) logrus.Hook {
|
||||||
|
val := ctx.Value(loggerHookKeyVal)
|
||||||
|
if val != nil {
|
||||||
|
if hook, ok := val.(logrus.Hook); ok {
|
||||||
|
return hook
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLoggerHook adds a value to the context for the logger hook
|
||||||
|
func WithLoggerHook(ctx context.Context, hook logrus.Hook) context.Context {
|
||||||
|
return context.WithValue(ctx, loggerHookKeyVal, hook)
|
||||||
|
}
|
||||||
|
@@ -57,6 +57,8 @@ type NewContainerInput struct {
|
|||||||
UsernsMode string
|
UsernsMode string
|
||||||
Platform string
|
Platform string
|
||||||
Options string
|
Options string
|
||||||
|
|
||||||
|
AutoRemove bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileEntry is a file to copy to a container
|
// FileEntry is a file to copy to a container
|
||||||
@@ -475,6 +477,7 @@ func (cr *containerReference) create(capAdd []string, capDrop []string) common.E
|
|||||||
NetworkMode: container.NetworkMode(input.NetworkMode),
|
NetworkMode: container.NetworkMode(input.NetworkMode),
|
||||||
Privileged: input.Privileged,
|
Privileged: input.Privileged,
|
||||||
UsernsMode: container.UsernsMode(input.UsernsMode),
|
UsernsMode: container.UsernsMode(input.UsernsMode),
|
||||||
|
AutoRemove: input.AutoRemove,
|
||||||
}
|
}
|
||||||
logger.Debugf("Common container.HostConfig ==> %+v", hostConfig)
|
logger.Debugf("Common container.HostConfig ==> %+v", hostConfig)
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
@@ -396,6 +396,7 @@ type ContainerSpec struct {
|
|||||||
|
|
||||||
// Step is the structure of one step in a job
|
// Step is the structure of one step in a job
|
||||||
type Step struct {
|
type Step struct {
|
||||||
|
Number int `yaml:"-"`
|
||||||
ID string `yaml:"id"`
|
ID string `yaml:"id"`
|
||||||
If yaml.Node `yaml:"if"`
|
If yaml.Node `yaml:"if"`
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
|
@@ -366,6 +366,7 @@ func newStepContainer(ctx context.Context, step step, image string, cmd []string
|
|||||||
Privileged: rc.Config.Privileged,
|
Privileged: rc.Config.Privileged,
|
||||||
UsernsMode: rc.Config.UsernsMode,
|
UsernsMode: rc.Config.UsernsMode,
|
||||||
Platform: rc.Config.ContainerArchitecture,
|
Platform: rc.Config.ContainerArchitecture,
|
||||||
|
AutoRemove: rc.Config.AutoRemove,
|
||||||
})
|
})
|
||||||
return stepContainer
|
return stepContainer
|
||||||
}
|
}
|
||||||
|
@@ -120,6 +120,7 @@ func (rc *RunContext) compositeExecutor(action *model.Action) *compositeSteps {
|
|||||||
if step.ID == "" {
|
if step.ID == "" {
|
||||||
step.ID = fmt.Sprintf("%d", i)
|
step.ID = fmt.Sprintf("%d", i)
|
||||||
}
|
}
|
||||||
|
step.Number = i
|
||||||
|
|
||||||
// create a copy of the step, since this composite action could
|
// create a copy of the step, since this composite action could
|
||||||
// run multiple times and we might modify the instance
|
// run multiple times and we might modify the instance
|
||||||
|
@@ -48,6 +48,7 @@ func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executo
|
|||||||
if stepModel.ID == "" {
|
if stepModel.ID == "" {
|
||||||
stepModel.ID = fmt.Sprintf("%d", i)
|
stepModel.ID = fmt.Sprintf("%d", i)
|
||||||
}
|
}
|
||||||
|
stepModel.Number = i
|
||||||
|
|
||||||
step, err := sf.newStep(stepModel, rc)
|
step, err := sf.newStep(stepModel, rc)
|
||||||
|
|
||||||
@@ -119,7 +120,7 @@ func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executo
|
|||||||
|
|
||||||
func useStepLogger(rc *RunContext, stepModel *model.Step, stage stepStage, executor common.Executor) common.Executor {
|
func useStepLogger(rc *RunContext, stepModel *model.Step, stage stepStage, executor common.Executor) common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
ctx = withStepLogger(ctx, stepModel.ID, stepModel.String(), stage.String())
|
ctx = withStepLogger(ctx, stepModel.Number, stepModel.ID, stepModel.String(), stage.String())
|
||||||
|
|
||||||
rawLogger := common.Logger(ctx).WithField("raw_output", true)
|
rawLogger := common.Logger(ctx).WithField("raw_output", true)
|
||||||
logWriter := common.NewLineWriter(rc.commandHandler(ctx), func(s string) bool {
|
logWriter := common.NewLineWriter(rc.commandHandler(ctx), func(s string) bool {
|
||||||
|
@@ -79,9 +79,12 @@ func WithJobLogger(ctx context.Context, jobID string, jobName string, config *Co
|
|||||||
ctx = WithMasks(ctx, masks)
|
ctx = WithMasks(ctx, masks)
|
||||||
|
|
||||||
logger := logrus.New()
|
logger := logrus.New()
|
||||||
|
if hook := common.LoggerHook(ctx); hook != nil {
|
||||||
|
logger.AddHook(hook)
|
||||||
|
}
|
||||||
logger.SetFormatter(formatter)
|
logger.SetFormatter(formatter)
|
||||||
logger.SetOutput(os.Stdout)
|
logger.SetOutput(os.Stdout)
|
||||||
logger.SetLevel(logrus.GetLevel())
|
logger.SetLevel(logrus.TraceLevel) // to be aware of steps
|
||||||
rtn := logger.WithFields(logrus.Fields{
|
rtn := logger.WithFields(logrus.Fields{
|
||||||
"job": jobName,
|
"job": jobName,
|
||||||
"jobID": jobID,
|
"jobID": jobID,
|
||||||
@@ -114,11 +117,12 @@ func WithCompositeStepLogger(ctx context.Context, stepID string) context.Context
|
|||||||
}).WithContext(ctx))
|
}).WithContext(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
func withStepLogger(ctx context.Context, stepID string, stepName string, stageName string) context.Context {
|
func withStepLogger(ctx context.Context, stepNumber int, stepID, stepName, stageName string) context.Context {
|
||||||
rtn := common.Logger(ctx).WithFields(logrus.Fields{
|
rtn := common.Logger(ctx).WithFields(logrus.Fields{
|
||||||
"step": stepName,
|
"stepNumber": stepNumber,
|
||||||
"stepID": []string{stepID},
|
"step": stepName,
|
||||||
"stage": stageName,
|
"stepID": []string{stepID},
|
||||||
|
"stage": stageName,
|
||||||
})
|
})
|
||||||
return common.WithLogger(ctx, rtn)
|
return common.WithLogger(ctx, rtn)
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
"github.com/opencontainers/selinux/go-selinux"
|
"github.com/opencontainers/selinux/go-selinux"
|
||||||
@@ -66,7 +67,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
|
||||||
@@ -150,7 +151,7 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
|||||||
|
|
||||||
rc.JobContainer = container.NewContainer(&container.NewContainerInput{
|
rc.JobContainer = container.NewContainer(&container.NewContainerInput{
|
||||||
Cmd: nil,
|
Cmd: nil,
|
||||||
Entrypoint: []string{"/usr/bin/tail", "-f", "/dev/null"},
|
Entrypoint: []string{"/bin/sleep", fmt.Sprint(rc.Config.ContainerMaxLifetime.Round(time.Second).Seconds())},
|
||||||
WorkingDir: rc.Config.ContainerWorkdir(),
|
WorkingDir: rc.Config.ContainerWorkdir(),
|
||||||
Image: image,
|
Image: image,
|
||||||
Username: username,
|
Username: username,
|
||||||
@@ -158,7 +159,7 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
|||||||
Name: name,
|
Name: name,
|
||||||
Env: envList,
|
Env: envList,
|
||||||
Mounts: mounts,
|
Mounts: mounts,
|
||||||
NetworkMode: "host",
|
NetworkMode: rc.Config.ContainerNetworkMode,
|
||||||
Binds: binds,
|
Binds: binds,
|
||||||
Stdout: logWriter,
|
Stdout: logWriter,
|
||||||
Stderr: logWriter,
|
Stderr: logWriter,
|
||||||
@@ -166,6 +167,7 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
|||||||
UsernsMode: rc.Config.UsernsMode,
|
UsernsMode: rc.Config.UsernsMode,
|
||||||
Platform: rc.Config.ContainerArchitecture,
|
Platform: rc.Config.ContainerArchitecture,
|
||||||
Options: rc.options(ctx),
|
Options: rc.options(ctx),
|
||||||
|
AutoRemove: rc.Config.AutoRemove,
|
||||||
})
|
})
|
||||||
|
|
||||||
return common.NewPipelineExecutor(
|
return common.NewPipelineExecutor(
|
||||||
@@ -177,15 +179,15 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
|||||||
rc.JobContainer.UpdateFromEnv("/etc/environment", &rc.Env),
|
rc.JobContainer.UpdateFromEnv("/etc/environment", &rc.Env),
|
||||||
rc.JobContainer.Copy(ActPath+"/", &container.FileEntry{
|
rc.JobContainer.Copy(ActPath+"/", &container.FileEntry{
|
||||||
Name: "workflow/event.json",
|
Name: "workflow/event.json",
|
||||||
Mode: 0644,
|
Mode: 0o644,
|
||||||
Body: rc.EventJSON,
|
Body: rc.EventJSON,
|
||||||
}, &container.FileEntry{
|
}, &container.FileEntry{
|
||||||
Name: "workflow/envs.txt",
|
Name: "workflow/envs.txt",
|
||||||
Mode: 0666,
|
Mode: 0o666,
|
||||||
Body: "",
|
Body: "",
|
||||||
}, &container.FileEntry{
|
}, &container.FileEntry{
|
||||||
Name: "workflow/paths.txt",
|
Name: "workflow/paths.txt",
|
||||||
Mode: 0666,
|
Mode: 0o666,
|
||||||
Body: "",
|
Body: "",
|
||||||
}),
|
}),
|
||||||
)(ctx)
|
)(ctx)
|
||||||
@@ -297,9 +299,19 @@ func (rc *RunContext) platformImage(ctx context.Context) string {
|
|||||||
common.Logger(ctx).Errorf("'runs-on' key not defined in %s", rc.String())
|
common.Logger(ctx).Errorf("'runs-on' key not defined in %s", rc.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, runnerLabel := range job.RunsOn() {
|
runsOn := job.RunsOn()
|
||||||
platformName := rc.ExprEval.Interpolate(ctx, runnerLabel)
|
for i, v := range runsOn {
|
||||||
image := rc.Config.Platforms[strings.ToLower(platformName)]
|
runsOn[i] = rc.ExprEval.Interpolate(ctx, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pick := rc.Config.PlatformPicker; pick != nil {
|
||||||
|
if image := pick(runsOn); image != "" {
|
||||||
|
return image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, runnerLabel := range runsOn {
|
||||||
|
image := rc.Config.Platforms[strings.ToLower(runnerLabel)]
|
||||||
if image != "" {
|
if image != "" {
|
||||||
return image
|
return image
|
||||||
}
|
}
|
||||||
@@ -447,6 +459,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 {
|
||||||
@@ -569,9 +600,16 @@ func (rc *RunContext) withGithubEnv(ctx context.Context, github *model.GithubCon
|
|||||||
env["RUNNER_PERFLOG"] = github.RunnerPerflog
|
env["RUNNER_PERFLOG"] = github.RunnerPerflog
|
||||||
env["RUNNER_TRACKING_ID"] = github.RunnerTrackingID
|
env["RUNNER_TRACKING_ID"] = github.RunnerTrackingID
|
||||||
if rc.Config.GitHubInstance != "github.com" {
|
if rc.Config.GitHubInstance != "github.com" {
|
||||||
env["GITHUB_SERVER_URL"] = fmt.Sprintf("https://%s", rc.Config.GitHubInstance)
|
hasProtocol := strings.HasPrefix(rc.Config.GitHubInstance, "http://") || strings.HasPrefix(rc.Config.GitHubInstance, "https://")
|
||||||
env["GITHUB_API_URL"] = fmt.Sprintf("https://%s/api/v3", rc.Config.GitHubInstance)
|
if hasProtocol {
|
||||||
env["GITHUB_GRAPHQL_URL"] = fmt.Sprintf("https://%s/api/graphql", rc.Config.GitHubInstance)
|
env["GITHUB_SERVER_URL"] = rc.Config.GitHubInstance
|
||||||
|
env["GITHUB_API_URL"] = fmt.Sprintf("%s/api/v1", rc.Config.GitHubInstance)
|
||||||
|
env["GITHUB_GRAPHQL_URL"] = "" // disable graphql url because Gitea doesn't support that
|
||||||
|
} else {
|
||||||
|
env["GITHUB_SERVER_URL"] = fmt.Sprintf("https://%s", rc.Config.GitHubInstance)
|
||||||
|
env["GITHUB_API_URL"] = fmt.Sprintf("https://%s/api/v1", rc.Config.GitHubInstance)
|
||||||
|
env["GITHUB_GRAPHQL_URL"] = "" // disable graphql url because Gitea doesn't support that
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if rc.Config.ArtifactServerPath != "" {
|
if rc.Config.ArtifactServerPath != "" {
|
||||||
|
@@ -55,6 +55,14 @@ 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
|
||||||
|
ContainerMaxLifetime time.Duration // the max lifetime of job containers
|
||||||
|
ContainerNetworkMode string // the network mode of job containers
|
||||||
|
DefaultActionInstance string // the default actions web site
|
||||||
|
PlatformPicker func(labels []string) string // platform picker, it will take precedence over Platforms if isn't nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolves the equivalent host path inside the container
|
// Resolves the equivalent host path inside the container
|
||||||
@@ -109,7 +117,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 {
|
||||||
|
@@ -30,9 +30,7 @@ type stepActionRemote struct {
|
|||||||
remoteAction *remoteAction
|
remoteAction *remoteAction
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var stepActionRemoteNewCloneExecutor = git.NewGitCloneExecutor
|
||||||
stepActionRemoteNewCloneExecutor = git.NewGitCloneExecutor
|
|
||||||
)
|
|
||||||
|
|
||||||
func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
|
func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
@@ -46,7 +44,7 @@ func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
|
|||||||
return fmt.Errorf("Expected format {org}/{repo}[/path]@ref. Actual '%s' Input string was not in a correct format", sar.Step.Uses)
|
return fmt.Errorf("Expected format {org}/{repo}[/path]@ref. Actual '%s' Input string was not in a correct format", sar.Step.Uses)
|
||||||
}
|
}
|
||||||
|
|
||||||
sar.remoteAction.URL = sar.RunContext.Config.GitHubInstance
|
sar.remoteAction.URL = sar.RunContext.Config.DefaultActionInstance
|
||||||
|
|
||||||
github := sar.getGithubContext(ctx)
|
github := sar.getGithubContext(ctx)
|
||||||
if sar.remoteAction.IsCheckout() && isLocalCheckout(github, sar.Step) && !sar.RunContext.Config.NoSkipCheckout {
|
if sar.remoteAction.IsCheckout() && isLocalCheckout(github, sar.Step) && !sar.RunContext.Config.NoSkipCheckout {
|
||||||
@@ -54,7 +52,6 @@ func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sar.remoteAction.URL = sar.RunContext.Config.GitHubInstance
|
|
||||||
for _, action := range sar.RunContext.Config.ReplaceGheActionWithGithubCom {
|
for _, action := range sar.RunContext.Config.ReplaceGheActionWithGithubCom {
|
||||||
if strings.EqualFold(fmt.Sprintf("%s/%s", sar.remoteAction.Org, sar.remoteAction.Repo), action) {
|
if strings.EqualFold(fmt.Sprintf("%s/%s", sar.remoteAction.Org, sar.remoteAction.Repo), action) {
|
||||||
sar.remoteAction.URL = "github.com"
|
sar.remoteAction.URL = "github.com"
|
||||||
@@ -213,7 +210,11 @@ type remoteAction struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ra *remoteAction) CloneURL() string {
|
func (ra *remoteAction) CloneURL() string {
|
||||||
return fmt.Sprintf("https://%s/%s/%s", ra.URL, ra.Org, ra.Repo)
|
u := ra.URL
|
||||||
|
if !strings.HasPrefix(u, "http://") && !strings.HasPrefix(u, "https://") {
|
||||||
|
u = "https://" + u
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s/%s/%s", u, ra.Org, ra.Repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ra *remoteAction) IsCheckout() bool {
|
func (ra *remoteAction) IsCheckout() bool {
|
||||||
|
@@ -130,6 +130,7 @@ func (sd *stepDocker) newStepContainer(ctx context.Context, image string, cmd []
|
|||||||
Privileged: rc.Config.Privileged,
|
Privileged: rc.Config.Privileged,
|
||||||
UsernsMode: rc.Config.UsernsMode,
|
UsernsMode: rc.Config.UsernsMode,
|
||||||
Platform: rc.Config.ContainerArchitecture,
|
Platform: rc.Config.ContainerArchitecture,
|
||||||
|
AutoRemove: rc.Config.AutoRemove,
|
||||||
})
|
})
|
||||||
return stepContainer
|
return stepContainer
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user