Compare commits
1 Commits
v0.261.6-1
...
v0.261.1-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac069843ad |
@@ -16,7 +16,6 @@ func NewInterpeter(
|
|||||||
gitCtx *model.GithubContext,
|
gitCtx *model.GithubContext,
|
||||||
results map[string]*JobResult,
|
results map[string]*JobResult,
|
||||||
vars map[string]string,
|
vars map[string]string,
|
||||||
inputs map[string]interface{},
|
|
||||||
) exprparser.Interpreter {
|
) exprparser.Interpreter {
|
||||||
strategy := make(map[string]interface{})
|
strategy := make(map[string]interface{})
|
||||||
if job.Strategy != nil {
|
if job.Strategy != nil {
|
||||||
@@ -63,7 +62,7 @@ func NewInterpeter(
|
|||||||
Strategy: strategy,
|
Strategy: strategy,
|
||||||
Matrix: matrix,
|
Matrix: matrix,
|
||||||
Needs: using,
|
Needs: using,
|
||||||
Inputs: inputs,
|
Inputs: nil, // not supported yet
|
||||||
Vars: vars,
|
Vars: vars,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"github.com/nektos/act/pkg/exprparser"
|
|
||||||
"github.com/nektos/act/pkg/model"
|
"github.com/nektos/act/pkg/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -41,10 +40,6 @@ func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid jobs: %w", err)
|
return nil, fmt.Errorf("invalid jobs: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluator := NewExpressionEvaluator(exprparser.NewInterpeter(&exprparser.EvaluationEnvironment{Github: pc.gitContext, Vars: pc.vars}, exprparser.Config{}))
|
|
||||||
workflow.RunName = evaluator.Interpolate(workflow.RunName)
|
|
||||||
|
|
||||||
for i, id := range ids {
|
for i, id := range ids {
|
||||||
job := jobs[i]
|
job := jobs[i]
|
||||||
matricxes, err := getMatrixes(origin.GetJob(id))
|
matricxes, err := getMatrixes(origin.GetJob(id))
|
||||||
@@ -57,7 +52,7 @@ func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) {
|
|||||||
job.Name = id
|
job.Name = id
|
||||||
}
|
}
|
||||||
job.Strategy.RawMatrix = encodeMatrix(matrix)
|
job.Strategy.RawMatrix = encodeMatrix(matrix)
|
||||||
evaluator := NewExpressionEvaluator(NewInterpeter(id, origin.GetJob(id), matrix, pc.gitContext, results, pc.vars, nil))
|
evaluator := NewExpressionEvaluator(NewInterpeter(id, origin.GetJob(id), matrix, pc.gitContext, results, pc.vars))
|
||||||
job.Name = nameWithMatrix(job.Name, matrix, evaluator)
|
job.Name = nameWithMatrix(job.Name, matrix, evaluator)
|
||||||
runsOn := origin.GetJob(id).RunsOn()
|
runsOn := origin.GetJob(id).RunsOn()
|
||||||
for i, v := range runsOn {
|
for i, v := range runsOn {
|
||||||
@@ -65,12 +60,10 @@ func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) {
|
|||||||
}
|
}
|
||||||
job.RawRunsOn = encodeRunsOn(runsOn)
|
job.RawRunsOn = encodeRunsOn(runsOn)
|
||||||
swf := &SingleWorkflow{
|
swf := &SingleWorkflow{
|
||||||
Name: workflow.Name,
|
Name: workflow.Name,
|
||||||
RawOn: workflow.RawOn,
|
RawOn: workflow.RawOn,
|
||||||
Env: workflow.Env,
|
Env: workflow.Env,
|
||||||
Defaults: workflow.Defaults,
|
Defaults: workflow.Defaults,
|
||||||
RawPermissions: workflow.RawPermissions,
|
|
||||||
RunName: workflow.RunName,
|
|
||||||
}
|
}
|
||||||
if err := swf.SetJob(id, job); err != nil {
|
if err := swf.SetJob(id, job); err != nil {
|
||||||
return nil, fmt.Errorf("SetJob: %w", err)
|
return nil, fmt.Errorf("SetJob: %w", err)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package jobparser
|
package jobparser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/nektos/act/pkg/model"
|
"github.com/nektos/act/pkg/model"
|
||||||
@@ -10,13 +9,11 @@ import (
|
|||||||
|
|
||||||
// SingleWorkflow is a workflow with single job and single matrix
|
// SingleWorkflow is a workflow with single job and single matrix
|
||||||
type SingleWorkflow struct {
|
type SingleWorkflow struct {
|
||||||
Name string `yaml:"name,omitempty"`
|
Name string `yaml:"name,omitempty"`
|
||||||
RawOn yaml.Node `yaml:"on,omitempty"`
|
RawOn yaml.Node `yaml:"on,omitempty"`
|
||||||
Env map[string]string `yaml:"env,omitempty"`
|
Env map[string]string `yaml:"env,omitempty"`
|
||||||
RawJobs yaml.Node `yaml:"jobs,omitempty"`
|
RawJobs yaml.Node `yaml:"jobs,omitempty"`
|
||||||
Defaults Defaults `yaml:"defaults,omitempty"`
|
Defaults Defaults `yaml:"defaults,omitempty"`
|
||||||
RawPermissions yaml.Node `yaml:"permissions,omitempty"`
|
|
||||||
RunName string `yaml:"run-name,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *SingleWorkflow) Job() (string, *Job) {
|
func (w *SingleWorkflow) Job() (string, *Job) {
|
||||||
@@ -85,8 +82,6 @@ type Job struct {
|
|||||||
Uses string `yaml:"uses,omitempty"`
|
Uses string `yaml:"uses,omitempty"`
|
||||||
With map[string]interface{} `yaml:"with,omitempty"`
|
With map[string]interface{} `yaml:"with,omitempty"`
|
||||||
RawSecrets yaml.Node `yaml:"secrets,omitempty"`
|
RawSecrets yaml.Node `yaml:"secrets,omitempty"`
|
||||||
RawConcurrency *model.RawConcurrency `yaml:"concurrency,omitempty"`
|
|
||||||
RawPermissions yaml.Node `yaml:"permissions,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *Job) Clone() *Job {
|
func (j *Job) Clone() *Job {
|
||||||
@@ -109,8 +104,6 @@ func (j *Job) Clone() *Job {
|
|||||||
Uses: j.Uses,
|
Uses: j.Uses,
|
||||||
With: j.With,
|
With: j.With,
|
||||||
RawSecrets: j.RawSecrets,
|
RawSecrets: j.RawSecrets,
|
||||||
RawConcurrency: j.RawConcurrency,
|
|
||||||
RawPermissions: j.RawPermissions,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,20 +172,10 @@ type RunDefaults struct {
|
|||||||
WorkingDirectory string `yaml:"working-directory,omitempty"`
|
WorkingDirectory string `yaml:"working-directory,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorkflowDispatchInput struct {
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
Description string `yaml:"description"`
|
|
||||||
Required bool `yaml:"required"`
|
|
||||||
Default string `yaml:"default"`
|
|
||||||
Type string `yaml:"type"`
|
|
||||||
Options []string `yaml:"options"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
Name string
|
Name string
|
||||||
acts map[string][]string
|
acts map[string][]string
|
||||||
schedules []map[string]string
|
schedules []map[string]string
|
||||||
inputs []WorkflowDispatchInput
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (evt *Event) IsSchedule() bool {
|
func (evt *Event) IsSchedule() bool {
|
||||||
@@ -207,114 +190,6 @@ func (evt *Event) Schedules() []map[string]string {
|
|||||||
return evt.schedules
|
return evt.schedules
|
||||||
}
|
}
|
||||||
|
|
||||||
func (evt *Event) Inputs() []WorkflowDispatchInput {
|
|
||||||
return evt.inputs
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseWorkflowDispatchInputs(inputs map[string]interface{}) ([]WorkflowDispatchInput, error) {
|
|
||||||
var results []WorkflowDispatchInput
|
|
||||||
for name, input := range inputs {
|
|
||||||
inputMap, ok := input.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("invalid input: %v", input)
|
|
||||||
}
|
|
||||||
input := WorkflowDispatchInput{
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
if desc, ok := inputMap["description"].(string); ok {
|
|
||||||
input.Description = desc
|
|
||||||
}
|
|
||||||
if required, ok := inputMap["required"].(bool); ok {
|
|
||||||
input.Required = required
|
|
||||||
}
|
|
||||||
if defaultVal, ok := inputMap["default"].(string); ok {
|
|
||||||
input.Default = defaultVal
|
|
||||||
}
|
|
||||||
if inputType, ok := inputMap["type"].(string); ok {
|
|
||||||
input.Type = inputType
|
|
||||||
}
|
|
||||||
if options, ok := inputMap["options"].([]string); ok {
|
|
||||||
input.Options = options
|
|
||||||
} else if options, ok := inputMap["options"].([]interface{}); ok {
|
|
||||||
for _, option := range options {
|
|
||||||
if opt, ok := option.(string); ok {
|
|
||||||
input.Options = append(input.Options, opt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
results = append(results, input)
|
|
||||||
}
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadWorkflowRawConcurrency(content []byte) (*model.RawConcurrency, error) {
|
|
||||||
w := new(model.Workflow)
|
|
||||||
err := yaml.NewDecoder(bytes.NewReader(content)).Decode(w)
|
|
||||||
return w.RawConcurrency, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func EvaluateConcurrency(rc *model.RawConcurrency, jobID string, job *Job, gitCtx map[string]any, results map[string]*JobResult, vars map[string]string, inputs map[string]any) (string, bool, error) {
|
|
||||||
actJob := &model.Job{}
|
|
||||||
if job != nil {
|
|
||||||
actJob.Strategy = &model.Strategy{
|
|
||||||
FailFastString: job.Strategy.FailFastString,
|
|
||||||
MaxParallelString: job.Strategy.MaxParallelString,
|
|
||||||
RawMatrix: job.Strategy.RawMatrix,
|
|
||||||
}
|
|
||||||
actJob.Strategy.FailFast = actJob.Strategy.GetFailFast()
|
|
||||||
actJob.Strategy.MaxParallel = actJob.Strategy.GetMaxParallel()
|
|
||||||
}
|
|
||||||
|
|
||||||
matrix := make(map[string]any)
|
|
||||||
matrixes, err := actJob.GetMatrixes()
|
|
||||||
if err != nil {
|
|
||||||
return "", false, err
|
|
||||||
}
|
|
||||||
if len(matrixes) > 0 {
|
|
||||||
matrix = matrixes[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
evaluator := NewExpressionEvaluator(NewInterpeter(jobID, actJob, matrix, toGitContext(gitCtx), results, vars, inputs))
|
|
||||||
group := evaluator.Interpolate(rc.Group)
|
|
||||||
cancelInProgress := evaluator.Interpolate(rc.CancelInProgress)
|
|
||||||
return group, cancelInProgress == "true", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func toGitContext(input map[string]any) *model.GithubContext {
|
|
||||||
gitContext := &model.GithubContext{
|
|
||||||
EventPath: asString(input["event_path"]),
|
|
||||||
Workflow: asString(input["workflow"]),
|
|
||||||
RunID: asString(input["run_id"]),
|
|
||||||
RunNumber: asString(input["run_number"]),
|
|
||||||
Actor: asString(input["actor"]),
|
|
||||||
Repository: asString(input["repository"]),
|
|
||||||
EventName: asString(input["event_name"]),
|
|
||||||
Sha: asString(input["sha"]),
|
|
||||||
Ref: asString(input["ref"]),
|
|
||||||
RefName: asString(input["ref_name"]),
|
|
||||||
RefType: asString(input["ref_type"]),
|
|
||||||
HeadRef: asString(input["head_ref"]),
|
|
||||||
BaseRef: asString(input["base_ref"]),
|
|
||||||
Token: asString(input["token"]),
|
|
||||||
Workspace: asString(input["workspace"]),
|
|
||||||
Action: asString(input["action"]),
|
|
||||||
ActionPath: asString(input["action_path"]),
|
|
||||||
ActionRef: asString(input["action_ref"]),
|
|
||||||
ActionRepository: asString(input["action_repository"]),
|
|
||||||
Job: asString(input["job"]),
|
|
||||||
RepositoryOwner: asString(input["repository_owner"]),
|
|
||||||
RetentionDays: asString(input["retention_days"]),
|
|
||||||
}
|
|
||||||
|
|
||||||
event, ok := input["event"].(map[string]any)
|
|
||||||
if ok {
|
|
||||||
gitContext.Event = event
|
|
||||||
}
|
|
||||||
|
|
||||||
return gitContext
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseRawOn(rawOn *yaml.Node) ([]*Event, error) {
|
func ParseRawOn(rawOn *yaml.Node) ([]*Event, error) {
|
||||||
switch rawOn.Kind {
|
switch rawOn.Kind {
|
||||||
case yaml.ScalarNode:
|
case yaml.ScalarNode:
|
||||||
@@ -343,126 +218,79 @@ func ParseRawOn(rawOn *yaml.Node) ([]*Event, error) {
|
|||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
case yaml.MappingNode:
|
case yaml.MappingNode:
|
||||||
events, triggers, err := parseMappingNode[yaml.Node](rawOn)
|
events, triggers, err := parseMappingNode[interface{}](rawOn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
res := make([]*Event, 0, len(events))
|
res := make([]*Event, 0, len(events))
|
||||||
for i, k := range events {
|
for i, k := range events {
|
||||||
v := triggers[i]
|
v := triggers[i]
|
||||||
switch v.Kind {
|
if v == nil {
|
||||||
case yaml.ScalarNode:
|
|
||||||
res = append(res, &Event{
|
res = append(res, &Event{
|
||||||
Name: k,
|
Name: k,
|
||||||
|
acts: map[string][]string{},
|
||||||
})
|
})
|
||||||
case yaml.SequenceNode:
|
continue
|
||||||
var t []interface{}
|
}
|
||||||
err := v.Decode(&t)
|
switch t := v.(type) {
|
||||||
if err != nil {
|
case string:
|
||||||
return nil, err
|
res = append(res, &Event{
|
||||||
}
|
Name: k,
|
||||||
schedules := make([]map[string]string, len(t))
|
acts: map[string][]string{},
|
||||||
if k == "schedule" {
|
})
|
||||||
for i, tt := range t {
|
case []string:
|
||||||
vv, ok := tt.(map[string]interface{})
|
res = append(res, &Event{
|
||||||
if !ok {
|
Name: k,
|
||||||
return nil, fmt.Errorf("unknown on type(schedule): %#v", v)
|
acts: map[string][]string{},
|
||||||
}
|
})
|
||||||
schedules[i] = make(map[string]string, len(vv))
|
case map[string]interface{}:
|
||||||
for k, vvv := range vv {
|
acts := make(map[string][]string, len(t))
|
||||||
|
for act, branches := range t {
|
||||||
|
switch b := branches.(type) {
|
||||||
|
case string:
|
||||||
|
acts[act] = []string{b}
|
||||||
|
case []string:
|
||||||
|
acts[act] = b
|
||||||
|
case []interface{}:
|
||||||
|
acts[act] = make([]string, len(b))
|
||||||
|
for i, v := range b {
|
||||||
var ok bool
|
var ok bool
|
||||||
if schedules[i][k], ok = vvv.(string); !ok {
|
if acts[act][i], ok = v.(string); !ok {
|
||||||
return nil, fmt.Errorf("unknown on type(schedule): %#v", v)
|
return nil, fmt.Errorf("unknown on type: %#v", branches)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown on type: %#v", branches)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
res = append(res, &Event{
|
||||||
if len(schedules) == 0 {
|
Name: k,
|
||||||
schedules = nil
|
acts: acts,
|
||||||
|
})
|
||||||
|
case []interface{}:
|
||||||
|
if k != "schedule" {
|
||||||
|
return nil, fmt.Errorf("unknown on type: %#v", v)
|
||||||
|
}
|
||||||
|
schedules := make([]map[string]string, len(t))
|
||||||
|
for i, tt := range t {
|
||||||
|
vv, ok := tt.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unknown on type: %#v", v)
|
||||||
|
}
|
||||||
|
schedules[i] = make(map[string]string, len(vv))
|
||||||
|
for k, vvv := range vv {
|
||||||
|
var ok bool
|
||||||
|
if schedules[i][k], ok = vvv.(string); !ok {
|
||||||
|
return nil, fmt.Errorf("unknown on type: %#v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
res = append(res, &Event{
|
res = append(res, &Event{
|
||||||
Name: k,
|
Name: k,
|
||||||
schedules: schedules,
|
schedules: schedules,
|
||||||
})
|
})
|
||||||
case yaml.MappingNode:
|
|
||||||
acts := make(map[string][]string, len(v.Content)/2)
|
|
||||||
var inputs []WorkflowDispatchInput
|
|
||||||
expectedKey := true
|
|
||||||
var act string
|
|
||||||
for _, content := range v.Content {
|
|
||||||
if expectedKey {
|
|
||||||
if content.Kind != yaml.ScalarNode {
|
|
||||||
return nil, fmt.Errorf("key type not string: %#v", content)
|
|
||||||
}
|
|
||||||
act = ""
|
|
||||||
err := content.Decode(&act)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch content.Kind {
|
|
||||||
case yaml.SequenceNode:
|
|
||||||
var t []string
|
|
||||||
err := content.Decode(&t)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
acts[act] = t
|
|
||||||
case yaml.ScalarNode:
|
|
||||||
var t string
|
|
||||||
err := content.Decode(&t)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
acts[act] = []string{t}
|
|
||||||
case yaml.MappingNode:
|
|
||||||
if k != "workflow_dispatch" || act != "inputs" {
|
|
||||||
return nil, fmt.Errorf("map should only for workflow_dispatch but %s: %#v", act, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
var key string
|
|
||||||
for i, vv := range content.Content {
|
|
||||||
if i%2 == 0 {
|
|
||||||
if vv.Kind != yaml.ScalarNode {
|
|
||||||
return nil, fmt.Errorf("key type not string: %#v", vv)
|
|
||||||
}
|
|
||||||
key = ""
|
|
||||||
if err := vv.Decode(&key); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if vv.Kind != yaml.MappingNode {
|
|
||||||
return nil, fmt.Errorf("key type not map(%s): %#v", key, vv)
|
|
||||||
}
|
|
||||||
|
|
||||||
input := WorkflowDispatchInput{}
|
|
||||||
if err := vv.Decode(&input); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
input.Name = key
|
|
||||||
inputs = append(inputs, input)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown on type: %#v", content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
expectedKey = !expectedKey
|
|
||||||
}
|
|
||||||
if len(inputs) == 0 {
|
|
||||||
inputs = nil
|
|
||||||
}
|
|
||||||
if len(acts) == 0 {
|
|
||||||
acts = nil
|
|
||||||
}
|
|
||||||
res = append(res, &Event{
|
|
||||||
Name: k,
|
|
||||||
acts: acts,
|
|
||||||
inputs: inputs,
|
|
||||||
})
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown on type: %v", v.Kind)
|
return nil, fmt.Errorf("unknown on type: %#v", v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
@@ -503,12 +331,3 @@ func parseMappingNode[T any](node *yaml.Node) ([]string, []T, error) {
|
|||||||
|
|
||||||
return scalars, datas, nil
|
return scalars, datas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func asString(v interface{}) string {
|
|
||||||
if v == nil {
|
|
||||||
return ""
|
|
||||||
} else if s, ok := v.(string); ok {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -58,19 +58,6 @@ func TestParseRawOn(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
input: "on:\n push:\n branches: main",
|
|
||||||
result: []*Event{
|
|
||||||
{
|
|
||||||
Name: "push",
|
|
||||||
acts: map[string][]string{
|
|
||||||
"branches": {
|
|
||||||
"main",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
input: "on:\n branch_protection_rule:\n types: [created, deleted]",
|
input: "on:\n branch_protection_rule:\n types: [created, deleted]",
|
||||||
result: []*Event{
|
result: []*Event{
|
||||||
@@ -199,60 +186,6 @@ func TestParseRawOn(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
input: `on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
logLevel:
|
|
||||||
description: 'Log level'
|
|
||||||
required: true
|
|
||||||
default: 'warning'
|
|
||||||
type: choice
|
|
||||||
options:
|
|
||||||
- info
|
|
||||||
- warning
|
|
||||||
- debug
|
|
||||||
tags:
|
|
||||||
description: 'Test scenario tags'
|
|
||||||
required: false
|
|
||||||
type: boolean
|
|
||||||
environment:
|
|
||||||
description: 'Environment to run tests against'
|
|
||||||
type: environment
|
|
||||||
required: true
|
|
||||||
push:
|
|
||||||
`,
|
|
||||||
result: []*Event{
|
|
||||||
{
|
|
||||||
Name: "workflow_dispatch",
|
|
||||||
inputs: []WorkflowDispatchInput{
|
|
||||||
{
|
|
||||||
Name: "logLevel",
|
|
||||||
Description: "Log level",
|
|
||||||
Required: true,
|
|
||||||
Default: "warning",
|
|
||||||
Type: "choice",
|
|
||||||
Options: []string{"info", "warning", "debug"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "tags",
|
|
||||||
Description: "Test scenario tags",
|
|
||||||
Required: false,
|
|
||||||
Type: "boolean",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "environment",
|
|
||||||
Description: "Environment to run tests against",
|
|
||||||
Type: "environment",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "push",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, kase := range kases {
|
for _, kase := range kases {
|
||||||
t.Run(kase.input, func(t *testing.T) {
|
t.Run(kase.input, func(t *testing.T) {
|
||||||
@@ -297,7 +230,8 @@ func TestParseMappingNode(t *testing.T) {
|
|||||||
{
|
{
|
||||||
input: "on:\n push:\n branches:\n - master",
|
input: "on:\n push:\n branches:\n - master",
|
||||||
scalars: []string{"push"},
|
scalars: []string{"push"},
|
||||||
datas: []interface{}{
|
datas: []interface {
|
||||||
|
}{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"branches": []interface{}{"master"},
|
"branches": []interface{}{"master"},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -9,22 +8,19 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nektos/act/pkg/common"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"github.com/nektos/act/pkg/common"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Workflow is the structure of the files in .github/workflows
|
// Workflow is the structure of the files in .github/workflows
|
||||||
type Workflow struct {
|
type Workflow struct {
|
||||||
File string
|
File string
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
RawOn yaml.Node `yaml:"on"`
|
RawOn yaml.Node `yaml:"on"`
|
||||||
Env map[string]string `yaml:"env"`
|
Env map[string]string `yaml:"env"`
|
||||||
Jobs map[string]*Job `yaml:"jobs"`
|
Jobs map[string]*Job `yaml:"jobs"`
|
||||||
Defaults Defaults `yaml:"defaults"`
|
Defaults Defaults `yaml:"defaults"`
|
||||||
RawConcurrency *RawConcurrency `yaml:"concurrency"`
|
|
||||||
RawPermissions yaml.Node `yaml:"permissions"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// On events for the workflow
|
// On events for the workflow
|
||||||
@@ -201,7 +197,6 @@ type Job struct {
|
|||||||
Uses string `yaml:"uses"`
|
Uses string `yaml:"uses"`
|
||||||
With map[string]interface{} `yaml:"with"`
|
With map[string]interface{} `yaml:"with"`
|
||||||
RawSecrets yaml.Node `yaml:"secrets"`
|
RawSecrets yaml.Node `yaml:"secrets"`
|
||||||
RawPermissions yaml.Node `yaml:"permissions"`
|
|
||||||
Result string
|
Result string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -721,12 +716,6 @@ func (s *Step) Type() StepType {
|
|||||||
return StepTypeUsesActionRemote
|
return StepTypeUsesActionRemote
|
||||||
}
|
}
|
||||||
|
|
||||||
// UsesHash returns a hash of the uses string.
|
|
||||||
// For Gitea.
|
|
||||||
func (s *Step) UsesHash() string {
|
|
||||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(s.Uses)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadWorkflow returns a list of jobs for a given workflow file reader
|
// ReadWorkflow returns a list of jobs for a given workflow file reader
|
||||||
func ReadWorkflow(in io.Reader) (*Workflow, error) {
|
func ReadWorkflow(in io.Reader) (*Workflow, error) {
|
||||||
w := new(Workflow)
|
w := new(Workflow)
|
||||||
@@ -772,10 +761,3 @@ func decodeNode(node yaml.Node, out interface{}) bool {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// For Gitea
|
|
||||||
// RawConcurrency represents a workflow concurrency or a job concurrency with uninterpolated options
|
|
||||||
type RawConcurrency struct {
|
|
||||||
Group string `yaml:"group,omitempty"`
|
|
||||||
CancelInProgress string `yaml:"cancel-in-progress,omitempty"`
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -603,37 +603,3 @@ func TestReadWorkflow_WorkflowDispatchConfig(t *testing.T) {
|
|||||||
Type: "choice",
|
Type: "choice",
|
||||||
}, workflowDispatch.Inputs["logLevel"])
|
}, workflowDispatch.Inputs["logLevel"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStep_UsesHash(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
Uses string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "regular",
|
|
||||||
fields: fields{
|
|
||||||
Uses: "https://gitea.com/testa/testb@v3",
|
|
||||||
},
|
|
||||||
want: "ae437878e9f285bd7518c58664f9fabbb12d05feddd7169c01702a2a14322aa8",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty",
|
|
||||||
fields: fields{
|
|
||||||
Uses: "",
|
|
||||||
},
|
|
||||||
want: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
s := &Step{
|
|
||||||
Uses: tt.fields.Uses,
|
|
||||||
}
|
|
||||||
assert.Equalf(t, tt.want, s.UsesHash(), "UsesHash()")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -535,7 +535,7 @@ func runPreStep(step actionStep) common.Executor {
|
|||||||
var actionPath string
|
var actionPath string
|
||||||
if _, ok := step.(*stepActionRemote); ok {
|
if _, ok := step.(*stepActionRemote); ok {
|
||||||
actionPath = newRemoteAction(stepModel.Uses).Path
|
actionPath = newRemoteAction(stepModel.Uses).Path
|
||||||
actionDir = fmt.Sprintf("%s/%s", rc.ActionCacheDir(), stepModel.UsesHash())
|
actionDir = fmt.Sprintf("%s/%s", rc.ActionCacheDir(), safeFilename(stepModel.Uses))
|
||||||
} else {
|
} else {
|
||||||
actionDir = filepath.Join(rc.Config.Workdir, stepModel.Uses)
|
actionDir = filepath.Join(rc.Config.Workdir, stepModel.Uses)
|
||||||
actionPath = ""
|
actionPath = ""
|
||||||
@@ -579,7 +579,7 @@ func runPreStep(step actionStep) common.Executor {
|
|||||||
var actionPath string
|
var actionPath string
|
||||||
if _, ok := step.(*stepActionRemote); ok {
|
if _, ok := step.(*stepActionRemote); ok {
|
||||||
actionPath = newRemoteAction(stepModel.Uses).Path
|
actionPath = newRemoteAction(stepModel.Uses).Path
|
||||||
actionDir = fmt.Sprintf("%s/%s", rc.ActionCacheDir(), stepModel.UsesHash())
|
actionDir = fmt.Sprintf("%s/%s", rc.ActionCacheDir(), safeFilename(stepModel.Uses))
|
||||||
} else {
|
} else {
|
||||||
actionDir = filepath.Join(rc.Config.Workdir, stepModel.Uses)
|
actionDir = filepath.Join(rc.Config.Workdir, stepModel.Uses)
|
||||||
actionPath = ""
|
actionPath = ""
|
||||||
@@ -665,7 +665,7 @@ func runPostStep(step actionStep) common.Executor {
|
|||||||
var actionPath string
|
var actionPath string
|
||||||
if _, ok := step.(*stepActionRemote); ok {
|
if _, ok := step.(*stepActionRemote); ok {
|
||||||
actionPath = newRemoteAction(stepModel.Uses).Path
|
actionPath = newRemoteAction(stepModel.Uses).Path
|
||||||
actionDir = fmt.Sprintf("%s/%s", rc.ActionCacheDir(), stepModel.UsesHash())
|
actionDir = fmt.Sprintf("%s/%s", rc.ActionCacheDir(), safeFilename(stepModel.Uses))
|
||||||
} else {
|
} else {
|
||||||
actionDir = filepath.Join(rc.Config.Workdir, stepModel.Uses)
|
actionDir = filepath.Join(rc.Config.Workdir, stepModel.Uses)
|
||||||
actionPath = ""
|
actionPath = ""
|
||||||
|
|||||||
@@ -185,8 +185,7 @@ func setJobResult(ctx context.Context, info jobInfo, rc *RunContext, success boo
|
|||||||
info.result(jobResult)
|
info.result(jobResult)
|
||||||
if rc.caller != nil {
|
if rc.caller != nil {
|
||||||
// set reusable workflow job result
|
// set reusable workflow job result
|
||||||
rc.caller.setReusedWorkflowJobResult(rc.JobName, jobResult) // For Gitea
|
rc.caller.runContext.result(jobResult)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jobResultMessage := "succeeded"
|
jobResultMessage := "succeeded"
|
||||||
|
|||||||
@@ -175,11 +175,7 @@ func newReusableWorkflowExecutor(rc *RunContext, directory string, workflow stri
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// return runner.NewPlanExecutor(plan)(ctx)
|
return runner.NewPlanExecutor(plan)(ctx)
|
||||||
return common.NewPipelineExecutor( // For Gitea
|
|
||||||
runner.NewPlanExecutor(plan),
|
|
||||||
setReusedWorkflowCallerResult(rc, runner),
|
|
||||||
)(ctx)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,8 +185,6 @@ func NewReusableWorkflowRunner(rc *RunContext) (Runner, error) {
|
|||||||
eventJSON: rc.EventJSON,
|
eventJSON: rc.EventJSON,
|
||||||
caller: &caller{
|
caller: &caller{
|
||||||
runContext: rc,
|
runContext: rc,
|
||||||
|
|
||||||
reusedWorkflowJobResults: map[string]string{}, // For Gitea
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,47 +269,3 @@ func newRemoteReusableWorkflow(uses string) *remoteReusableWorkflow {
|
|||||||
URL: "https://github.com",
|
URL: "https://github.com",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For Gitea
|
|
||||||
func setReusedWorkflowCallerResult(rc *RunContext, runner Runner) common.Executor {
|
|
||||||
return func(ctx context.Context) error {
|
|
||||||
logger := common.Logger(ctx)
|
|
||||||
|
|
||||||
runnerImpl, ok := runner.(*runnerImpl)
|
|
||||||
if !ok {
|
|
||||||
logger.Warn("Failed to get caller from runner")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
caller := runnerImpl.caller
|
|
||||||
|
|
||||||
allJobDone := true
|
|
||||||
hasFailure := false
|
|
||||||
for _, result := range caller.reusedWorkflowJobResults {
|
|
||||||
if result == "pending" {
|
|
||||||
allJobDone = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if result == "failure" {
|
|
||||||
hasFailure = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if allJobDone {
|
|
||||||
reusedWorkflowJobResult := "success"
|
|
||||||
reusedWorkflowJobResultMessage := "succeeded"
|
|
||||||
if hasFailure {
|
|
||||||
reusedWorkflowJobResult = "failure"
|
|
||||||
reusedWorkflowJobResultMessage = "failed"
|
|
||||||
}
|
|
||||||
|
|
||||||
if rc.caller != nil {
|
|
||||||
rc.caller.setReusedWorkflowJobResult(rc.JobName, reusedWorkflowJobResult)
|
|
||||||
} else {
|
|
||||||
rc.result(reusedWorkflowJobResult)
|
|
||||||
logger.WithField("jobResult", reusedWorkflowJobResult).Infof("\U0001F3C1 Job %s", reusedWorkflowJobResultMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -92,12 +92,7 @@ func (rc *RunContext) GetEnv() map[string]string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rc *RunContext) jobContainerName() string {
|
func (rc *RunContext) jobContainerName() string {
|
||||||
nameParts := []string{rc.Config.ContainerNamePrefix, "WORKFLOW-" + rc.Run.Workflow.Name, "JOB-" + rc.Name}
|
return createSimpleContainerName(rc.Config.ContainerNamePrefix, "WORKFLOW-"+rc.Run.Workflow.Name, "JOB-"+rc.Name)
|
||||||
if rc.caller != nil {
|
|
||||||
nameParts = append(nameParts, "CALLED-BY-"+rc.caller.runContext.JobName)
|
|
||||||
}
|
|
||||||
// return createSimpleContainerName(rc.Config.ContainerNamePrefix, "WORKFLOW-"+rc.Run.Workflow.Name, "JOB-"+rc.Name)
|
|
||||||
return createSimpleContainerName(nameParts...) // For Gitea
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: use `networkNameForGitea`
|
// Deprecated: use `networkNameForGitea`
|
||||||
@@ -658,7 +653,6 @@ func (rc *RunContext) Executor() (common.Executor, error) {
|
|||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
res, err := rc.isEnabled(ctx)
|
res, err := rc.isEnabled(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rc.caller.setReusedWorkflowJobResult(rc.JobName, "failure") // For Gitea
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if res {
|
if res {
|
||||||
@@ -754,10 +748,6 @@ func (rc *RunContext) isEnabled(ctx context.Context) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !runJob {
|
if !runJob {
|
||||||
if rc.caller != nil { // For Gitea
|
|
||||||
rc.caller.setReusedWorkflowJobResult(rc.JobName, "skipped")
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
l.WithField("jobResult", "skipped").Debugf("Skipping job '%s' due to '%s'", job.Name, job.If.Value)
|
l.WithField("jobResult", "skipped").Debugf("Skipping job '%s' due to '%s'", job.Name, job.If.Value)
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
docker_container "github.com/docker/docker/api/types/container"
|
docker_container "github.com/docker/docker/api/types/container"
|
||||||
@@ -87,9 +86,6 @@ func (c Config) GetToken() string {
|
|||||||
|
|
||||||
type caller struct {
|
type caller struct {
|
||||||
runContext *RunContext
|
runContext *RunContext
|
||||||
|
|
||||||
updateResultLock sync.Mutex // For Gitea
|
|
||||||
reusedWorkflowJobResults map[string]string // For Gitea
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type runnerImpl struct {
|
type runnerImpl struct {
|
||||||
@@ -210,9 +206,6 @@ func (runner *runnerImpl) NewPlanExecutor(plan *model.Plan) common.Executor {
|
|||||||
if len(rc.String()) > maxJobNameLen {
|
if len(rc.String()) > maxJobNameLen {
|
||||||
maxJobNameLen = len(rc.String())
|
maxJobNameLen = len(rc.String())
|
||||||
}
|
}
|
||||||
if rc.caller != nil { // For Gitea
|
|
||||||
rc.caller.setReusedWorkflowJobResult(rc.JobName, "pending")
|
|
||||||
}
|
|
||||||
stageExecutor = append(stageExecutor, func(ctx context.Context) error {
|
stageExecutor = append(stageExecutor, func(ctx context.Context) error {
|
||||||
jobName := fmt.Sprintf("%-*s", maxJobNameLen, rc.String())
|
jobName := fmt.Sprintf("%-*s", maxJobNameLen, rc.String())
|
||||||
executor, err := rc.Executor()
|
executor, err := rc.Executor()
|
||||||
@@ -284,10 +277,3 @@ func (runner *runnerImpl) newRunContext(ctx context.Context, run *model.Run, mat
|
|||||||
|
|
||||||
return rc
|
return rc
|
||||||
}
|
}
|
||||||
|
|
||||||
// For Gitea
|
|
||||||
func (c *caller) setReusedWorkflowJobResult(jobName string, result string) {
|
|
||||||
c.updateResultLock.Lock()
|
|
||||||
defer c.updateResultLock.Unlock()
|
|
||||||
c.reusedWorkflowJobResults[jobName] = result
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
actionDir := fmt.Sprintf("%s/%s", sar.RunContext.ActionCacheDir(), sar.Step.UsesHash())
|
actionDir := fmt.Sprintf("%s/%s", sar.RunContext.ActionCacheDir(), safeFilename(sar.Step.Uses))
|
||||||
gitClone := stepActionRemoteNewCloneExecutor(git.NewGitCloneExecutorInput{
|
gitClone := stepActionRemoteNewCloneExecutor(git.NewGitCloneExecutorInput{
|
||||||
URL: sar.remoteAction.CloneURL(sar.RunContext.Config.DefaultActionInstance),
|
URL: sar.remoteAction.CloneURL(sar.RunContext.Config.DefaultActionInstance),
|
||||||
Ref: sar.remoteAction.Ref,
|
Ref: sar.remoteAction.Ref,
|
||||||
@@ -177,7 +177,7 @@ func (sar *stepActionRemote) main() common.Executor {
|
|||||||
return sar.RunContext.JobContainer.CopyDir(copyToPath, sar.RunContext.Config.Workdir+string(filepath.Separator)+".", sar.RunContext.Config.UseGitIgnore)(ctx)
|
return sar.RunContext.JobContainer.CopyDir(copyToPath, sar.RunContext.Config.Workdir+string(filepath.Separator)+".", sar.RunContext.Config.UseGitIgnore)(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
actionDir := fmt.Sprintf("%s/%s", sar.RunContext.ActionCacheDir(), sar.Step.UsesHash())
|
actionDir := fmt.Sprintf("%s/%s", sar.RunContext.ActionCacheDir(), safeFilename(sar.Step.Uses))
|
||||||
|
|
||||||
return sar.runAction(sar, actionDir, sar.remoteAction)(ctx)
|
return sar.runAction(sar, actionDir, sar.remoteAction)(ctx)
|
||||||
}),
|
}),
|
||||||
@@ -236,7 +236,7 @@ func (sar *stepActionRemote) getActionModel() *model.Action {
|
|||||||
|
|
||||||
func (sar *stepActionRemote) getCompositeRunContext(ctx context.Context) *RunContext {
|
func (sar *stepActionRemote) getCompositeRunContext(ctx context.Context) *RunContext {
|
||||||
if sar.compositeRunContext == nil {
|
if sar.compositeRunContext == nil {
|
||||||
actionDir := fmt.Sprintf("%s/%s", sar.RunContext.ActionCacheDir(), sar.Step.UsesHash())
|
actionDir := fmt.Sprintf("%s/%s", sar.RunContext.ActionCacheDir(), safeFilename(sar.Step.Uses))
|
||||||
actionLocation := path.Join(actionDir, sar.remoteAction.Path)
|
actionLocation := path.Join(actionDir, sar.remoteAction.Path)
|
||||||
_, containerActionDir := getContainerActionPaths(sar.getStepModel(), actionLocation, sar.RunContext)
|
_, containerActionDir := getContainerActionPaths(sar.getStepModel(), actionLocation, sar.RunContext)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user