diff --git a/go.mod b/go.mod index dc80d2ca2b..57c9a7fd62 100644 --- a/go.mod +++ b/go.mod @@ -102,6 +102,7 @@ require ( github.com/prometheus/client_golang v1.20.5 github.com/quasoft/websspi v1.1.2 github.com/redis/go-redis/v9 v9.7.0 + github.com/rhysd/actionlint v1.7.3 github.com/robfig/cron/v3 v3.0.1 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/sassoftware/go-rpmutils v0.4.0 @@ -271,7 +272,6 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.60.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/rhysd/actionlint v1.7.3 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/rs/xid v1.6.0 // indirect diff --git a/modules/actions/jobparser/interpeter.go b/modules/actions/jobparser/interpeter.go index 1a88b21504..672a998f3b 100644 --- a/modules/actions/jobparser/interpeter.go +++ b/modules/actions/jobparser/interpeter.go @@ -9,10 +9,10 @@ import ( "gopkg.in/yaml.v3" ) -// NewInterpeter returns an interpeter used in the server, +// NewInterpreter returns an interpreter 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( +func NewInterpreter( jobID string, job *model.Job, matrix map[string]any, @@ -78,7 +78,7 @@ func NewInterpeter( return exprparser.NewInterpeter(ee, config) } -// JobResult is the minimum requirement of job results for Interpeter +// JobResult is the minimum requirement of job results for Interpreter type JobResult struct { Needs []string Result string diff --git a/modules/actions/jobparser/jobparser.go b/modules/actions/jobparser/jobparser.go index 876cfdc981..1f9500ffba 100644 --- a/modules/actions/jobparser/jobparser.go +++ b/modules/actions/jobparser/jobparser.go @@ -54,7 +54,7 @@ func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) { job.Name = id } job.Strategy.RawMatrix = encodeMatrix(matrix) - evaluator := NewExpressionEvaluator(NewInterpeter(id, origin.GetJob(id), matrix, pc.gitContext, results, pc.vars)) + evaluator := NewExpressionEvaluator(NewInterpreter(id, origin.GetJob(id), matrix, pc.gitContext, results, pc.vars)) job.Name = nameWithMatrix(job.Name, matrix, evaluator) runsOn := origin.GetJob(id).RunsOn() for i, v := range runsOn { diff --git a/modules/actions/jobparser/model.go b/modules/actions/jobparser/model.go index 3aa9399709..4383bba53e 100644 --- a/modules/actions/jobparser/model.go +++ b/modules/actions/jobparser/model.go @@ -204,16 +204,19 @@ func (evt *Event) Acts() map[string][]string { // Helper to convert actionlint errors func acErrToError(acErrs []*actionlint.Error) []error { errs := make([]error, len(acErrs)) - for _, err := range acErrs { - errs = append(errs, err) + for i, err := range acErrs { + errs[i] = err } return errs } func acStringToString(strs []*actionlint.String) []string { + if len(strs) == 0 { + return nil + } strings := make([]string, len(strs)) - for _, v := range strs { - strings = append(strings, v.Value) + for i, v := range strs { + strings[i] = v.Value } return strings } @@ -246,29 +249,39 @@ func GetEventsFromContent(content []byte) ([]*Event, error) { for _, acEvent := range wf.On { event := &Event{ Name: acEvent.EventName(), - acts: map[string][]string{}, } switch e := acEvent.(type) { case *actionlint.ScheduledEvent: schedules := make([]map[string]string, len(e.Cron)) - for _, c := range e.Cron { - schedules = append(schedules, map[string]string{"cron": c.Value}) + for i, c := range e.Cron { + schedules[i] = map[string]string{"cron": c.Value} } event.schedules = schedules case *actionlint.WorkflowDispatchEvent: inputs := make([]WorkflowDispatchInput, len(e.Inputs)) + i := 0 for keyword, v := range e.Inputs { - inputs = append(inputs, WorkflowDispatchInput{ - Name: keyword, - Required: v.Required.Value, - Description: v.Description.Value, - Default: v.Default.Value, - Options: acStringToString(v.Options), - Type: typeToString(v.Type), - }) + wdi := WorkflowDispatchInput{ + Name: keyword, + + Options: acStringToString(v.Options), + Type: typeToString(v.Type), + } + if v.Required != nil { + wdi.Required = v.Required.Value + } + if v.Description != nil { + wdi.Description = v.Description.Value + } + if v.Default != nil { + wdi.Default = v.Default.Value + } + inputs[i] = wdi + i++ } event.inputs = inputs case *actionlint.WebhookEvent: + event.acts = map[string][]string{} if e.Branches != nil { event.acts[e.Branches.Name.Value] = acStringToString(e.Branches.Values) } @@ -290,7 +303,6 @@ func GetEventsFromContent(content []byte) ([]*Event, error) { if e.Types != nil { event.acts["types"] = acStringToString(e.Types) } - // if e. } events = append(events, event) } diff --git a/modules/actions/jobparser/model_test.go b/modules/actions/jobparser/model_test.go index f23748bd01..c0babd7ec5 100644 --- a/modules/actions/jobparser/model_test.go +++ b/modules/actions/jobparser/model_test.go @@ -9,270 +9,85 @@ import ( "github.com/nektos/act/pkg/model" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" ) -// func TestParseRawOn(t *testing.T) { -// kases := []struct { -// input string -// result []*Event -// }{ -// { -// input: "on: issue_comment", -// result: []*Event{ -// { -// Name: "issue_comment", -// }, -// }, -// }, -// { -// input: "on:\n push", -// result: []*Event{ -// { -// Name: "push", -// }, -// }, -// }, +func TestGetEvents(t *testing.T) { + content := ` +name: My Workflow -// { -// input: "on:\n - push\n - pull_request", -// result: []*Event{ -// { -// Name: "push", -// }, -// { -// Name: "pull_request", -// }, -// }, -// }, -// { -// input: "on:\n push:\n branches:\n - master", -// result: []*Event{ -// { -// Name: "push", -// acts: map[string][]string{ -// "branches": { -// "master", -// }, -// }, -// }, -// }, -// }, -// { -// input: "on:\n branch_protection_rule:\n types: [created, deleted]", -// result: []*Event{ -// { -// Name: "branch_protection_rule", -// acts: map[string][]string{ -// "types": { -// "created", -// "deleted", -// }, -// }, -// }, -// }, -// }, -// { -// input: "on:\n project:\n types: [created, deleted]\n milestone:\n types: [opened, deleted]", -// result: []*Event{ -// { -// Name: "project", -// acts: map[string][]string{ -// "types": { -// "created", -// "deleted", -// }, -// }, -// }, -// { -// Name: "milestone", -// acts: map[string][]string{ -// "types": { -// "opened", -// "deleted", -// }, -// }, -// }, -// }, -// }, -// { -// input: "on:\n pull_request:\n types:\n - opened\n branches:\n - 'releases/**'", -// result: []*Event{ -// { -// Name: "pull_request", -// acts: map[string][]string{ -// "types": { -// "opened", -// }, -// "branches": { -// "releases/**", -// }, -// }, -// }, -// }, -// }, -// { -// input: "on:\n push:\n branches:\n - main\n pull_request:\n types:\n - opened\n branches:\n - '**'", -// result: []*Event{ -// { -// Name: "push", -// acts: map[string][]string{ -// "branches": { -// "main", -// }, -// }, -// }, -// { -// Name: "pull_request", -// acts: map[string][]string{ -// "types": { -// "opened", -// }, -// "branches": { -// "**", -// }, -// }, -// }, -// }, -// }, -// { -// input: "on:\n push:\n branches:\n - 'main'\n - 'releases/**'", -// result: []*Event{ -// { -// Name: "push", -// acts: map[string][]string{ -// "branches": { -// "main", -// "releases/**", -// }, -// }, -// }, -// }, -// }, -// { -// input: "on:\n push:\n tags:\n - v1.**", -// result: []*Event{ -// { -// Name: "push", -// acts: map[string][]string{ -// "tags": { -// "v1.**", -// }, -// }, -// }, -// }, -// }, -// { -// input: "on: [pull_request, workflow_dispatch]", -// result: []*Event{ -// { -// Name: "pull_request", -// }, -// { -// Name: "workflow_dispatch", -// }, -// }, -// }, -// { -// input: "on:\n schedule:\n - cron: '20 6 * * *'", -// result: []*Event{ -// { -// Name: "schedule", -// schedules: []map[string]string{ -// { -// "cron": "20 6 * * *", -// }, -// }, -// }, -// }, -// }, -// { -// 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 { -// t.Run(kase.input, func(t *testing.T) { -// origin, err := model.ReadWorkflow(strings.NewReader(kase.input)) -// assert.NoError(t, err) +on: + push: + branches: [main] + schedule: + - cron: '0 0 * * *' + workflow_dispatch: + inputs: + my_variable1: + description: 'first variable' + required: true + type: string + my_variable2: + description: 'second variable' + required: false + type: number + default: 4 -// events, err := ParseRawOn(&origin.RawOn) -// assert.NoError(t, err) -// assert.EqualValues(t, kase.result, events, fmt.Sprintf("%#v", events)) -// }) -// } -// } +jobs: + example: + runs-on: ubuntu-latest + steps: + - run: exit 0 +` + expected := make([]*Event, 3) + expected[0] = &Event{acts: map[string][]string{"branches": {"main"}}, Name: "push"} + expected[1] = &Event{Name: "schedule", schedules: []map[string]string{{"cron": "0 0 * * *"}}} + expected[2] = &Event{ + Name: "workflow_dispatch", + inputs: []WorkflowDispatchInput{ + { + Name: "my_variable1", + Description: "first variable", + Required: true, + Type: "string", + }, + { + Name: "my_variable2", + Type: "number", + Description: "second variable", + Default: "4", + }, + }, + } + actual, err := GetEventsFromContent([]byte(content)) + require.NoError(t, err) + assert.Len(t, actual, 3) + assert.Equal(t, expected, actual) + //Toggler +} -// func TestSingleWorkflow_SetJob(t *testing.T) { -// t.Run("erase needs", func(t *testing.T) { -// content := ReadTestdata(t, "erase_needs.in.yaml") -// want := ReadTestdata(t, "erase_needs.out.yaml") -// swf, err := Parse(content) -// require.NoError(t, err) -// builder := &strings.Builder{} -// for _, v := range swf { -// id, job := v.Job() -// require.NoError(t, v.SetJob(id, job.EraseNeeds())) +func TestSingleWorkflow_SetJob(t *testing.T) { + t.Run("erase needs", func(t *testing.T) { + content := ReadTestdata(t, "erase_needs.in.yaml") + want := ReadTestdata(t, "erase_needs.out.yaml") + swf, err := Parse(content) + require.NoError(t, err) + builder := &strings.Builder{} + for _, v := range swf { + id, job := v.Job() + require.NoError(t, v.SetJob(id, job.EraseNeeds())) -// if builder.Len() > 0 { -// builder.WriteString("---\n") -// } -// encoder := yaml.NewEncoder(builder) -// encoder.SetIndent(2) -// require.NoError(t, encoder.Encode(v)) -// } -// assert.Equal(t, string(want), builder.String()) -// }) -// } + if builder.Len() > 0 { + builder.WriteString("---\n") + } + encoder := yaml.NewEncoder(builder) + encoder.SetIndent(2) + require.NoError(t, encoder.Encode(v)) + } + assert.Equal(t, string(want), builder.String()) + }) +} func TestParseMappingNode(t *testing.T) { tests := []struct {