From 933c4a5bd50bdb7712b296b759b9f8926c91a609 Mon Sep 17 00:00:00 2001 From: Christopher Homberger Date: Sat, 14 Feb 2026 16:23:59 +0000 Subject: [PATCH] feat: allow configuring gitea schema mode (#23) * config entries for schema change * remove broken/unused syntetic nodejs action * specify desired schema in model struct that is read by unmarshal * replace params by config * allows gitea context + env names * act --gitea now parses a subset of gitea specific workflows Reviewed-on: https://gitea.com/actions-oss/act-cli/pulls/23 Co-authored-by: Christopher Homberger Co-committed-by: Christopher Homberger --- cmd/input.go | 1 + cmd/root.go | 22 ++++++++- internal/eval/v2/evaluation_result.go | 14 +++++- pkg/artifacts/server_test.go | 2 +- pkg/exprparser/interpreter.go | 38 +++++++++----- pkg/model/action.go | 29 +++++++++-- pkg/model/planner.go | 15 ++++-- pkg/model/planner_test.go | 4 +- pkg/model/workflow.go | 71 +++++++++++++++------------ pkg/model/workflow_test.go | 44 ++++++++--------- pkg/runner/action.go | 51 ++----------------- pkg/runner/action_composite.go | 4 +- pkg/runner/action_test.go | 35 +------------ pkg/runner/expression.go | 14 +++--- pkg/runner/res/trampoline.js | 14 ------ pkg/runner/reusable_workflow.go | 12 ++--- pkg/runner/run_context.go | 63 ++++++++++++++---------- pkg/runner/runner.go | 9 ++-- pkg/runner/runner_test.go | 14 +++--- pkg/runner/step.go | 10 ++-- pkg/runner/step_action_local.go | 15 +----- pkg/runner/step_action_local_test.go | 7 ++- pkg/runner/step_action_remote.go | 2 +- pkg/runner/step_action_remote_test.go | 15 +++--- pkg/schema/gitea_schema.go | 13 +++++ 25 files changed, 265 insertions(+), 253 deletions(-) delete mode 100644 pkg/runner/res/trampoline.js diff --git a/cmd/input.go b/cmd/input.go index e1142a9b..1d1b44f5 100644 --- a/cmd/input.go +++ b/cmd/input.go @@ -66,6 +66,7 @@ type Input struct { validate bool strict bool parallel int + gitea bool } func (i *Input) resolve(path string) string { diff --git a/cmd/root.go b/cmd/root.go index 16320367..320ae9b6 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -32,6 +32,7 @@ import ( "github.com/actions-oss/act-cli/pkg/gh" "github.com/actions-oss/act-cli/pkg/model" "github.com/actions-oss/act-cli/pkg/runner" + "github.com/actions-oss/act-cli/pkg/schema" ) type Flag struct { @@ -131,6 +132,7 @@ func createRootCommand(ctx context.Context, input *Input, version string) *cobra rootCmd.PersistentFlags().StringVarP(&input.networkName, "network", "", "host", "Sets a docker network name. Defaults to host.") rootCmd.PersistentFlags().StringArrayVarP(&input.localRepository, "local-repository", "", []string{}, "Replaces the specified repository and ref with a local folder (e.g. https://github.com/test/test@v0=/home/act/test or test/test@v0=/home/act/test, the latter matches any hosts or protocols)") rootCmd.PersistentFlags().BoolVar(&input.listOptions, "list-options", false, "Print a json structure of compatible options") + rootCmd.PersistentFlags().BoolVar(&input.gitea, "gitea", false, "Use Gitea instead of GitHub") rootCmd.SetArgs(args()) return rootCmd } @@ -453,7 +455,18 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str matrixes := parseMatrix(input.matrix) log.Debugf("Evaluated matrix inclusions: %v", matrixes) - planner, err := model.NewWorkflowPlanner(input.WorkflowsPath(), !input.workflowRecurse, input.strict) + // TODO switch to Gitea Schema when supported + plannerConfig := model.PlannerConfig{ + Recursive: input.workflowRecurse, + Workflow: model.WorkflowConfig{ + Strict: input.strict, + }, + } + if input.gitea { + plannerConfig.Workflow.Schema = schema.GetGiteaWorkflowSchema() + } + + planner, err := model.NewWorkflowPlanner(input.WorkflowsPath(), plannerConfig) if err != nil { return err } @@ -653,6 +666,13 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str Matrix: matrixes, ContainerNetworkMode: docker_container.NetworkMode(input.networkName), Parallel: input.parallel, + Planner: plannerConfig, + Action: model.ActionConfig{}, // TODO Gitea Action Schema + MainContextNames: []string{"github"}, + } + if input.gitea { + config.Action.Schema = schema.GetGiteaActionSchema() + config.MainContextNames = append(config.MainContextNames, "gitea") } actionCache := runner.GoGitActionCache{ Path: config.ActionCacheDir, diff --git a/internal/eval/v2/evaluation_result.go b/internal/eval/v2/evaluation_result.go index 1af5bd07..54509626 100644 --- a/internal/eval/v2/evaluation_result.go +++ b/internal/eval/v2/evaluation_result.go @@ -34,6 +34,7 @@ type ReadOnlyArray[T any] interface { type ReadOnlyObject[T any] interface { Get(key string) T + GetKv(key string) (string, T) // Returns the actual key used (for case-insensitive objects) GetEnumerator() map[string]T } @@ -54,13 +55,18 @@ func (a BasicArray[T]) GetEnumerator() []T { type CaseInsensitiveObject[T any] map[string]T func (o CaseInsensitiveObject[T]) Get(key string) T { + _, v := o.GetKv(key) + return v +} + +func (o CaseInsensitiveObject[T]) GetKv(key string) (k string, v T) { for k, v := range o { if strings.EqualFold(k, key) { - return v + return k, v } } var zero T - return zero + return key, zero } func (o CaseInsensitiveObject[T]) GetEnumerator() map[string]T { @@ -73,6 +79,10 @@ func (o CaseSensitiveObject[T]) Get(key string) T { return o[key] } +func (o CaseSensitiveObject[T]) GetKv(key string) (string, T) { + return key, o[key] +} + func (o CaseSensitiveObject[T]) GetEnumerator() map[string]T { return o } diff --git a/pkg/artifacts/server_test.go b/pkg/artifacts/server_test.go index 7658ef1c..6bfd35df 100644 --- a/pkg/artifacts/server_test.go +++ b/pkg/artifacts/server_test.go @@ -301,7 +301,7 @@ func runTestJobFile(ctx context.Context, t *testing.T, tjfi TestJobFileInfo) { runner, err := runner.New(runnerConfig) assert.Nil(t, err, tjfi.workflowPath) - planner, err := model.NewWorkflowPlanner(fullWorkflowPath, true, false) + planner, err := model.NewWorkflowPlanner(fullWorkflowPath, model.PlannerConfig{}) assert.Nil(t, err, fullWorkflowPath) plan, err := planner.PlanEvent(tjfi.eventName) diff --git a/pkg/exprparser/interpreter.go b/pkg/exprparser/interpreter.go index e12c48f7..362fb176 100644 --- a/pkg/exprparser/interpreter.go +++ b/pkg/exprparser/interpreter.go @@ -38,9 +38,10 @@ type Needs struct { } type Config struct { - Run *model.Run - WorkingDir string - Context string + Run *model.Run + WorkingDir string + Context string + MainContextNames []string // e.g. "github", "gitea", "forgejo" } type DefaultStatusCheck int @@ -253,7 +254,8 @@ func (impl *interperterImpl) GetFunctions() eval.CaseInsensitiveObject[eval.Func } func (impl *interperterImpl) GetVariables() eval.ReadOnlyObject[any] { - githubCtx := toRawObj(reflect.ValueOf(impl.env.Github)) + mainCtx := eval.CaseInsensitiveObject[any](toRawObj(reflect.ValueOf(impl.env.Github))) + var env any if impl.env.EnvCS { env = eval.CaseSensitiveObject[any](toRawObj(reflect.ValueOf(impl.env.Env))) @@ -261,7 +263,6 @@ func (impl *interperterImpl) GetVariables() eval.ReadOnlyObject[any] { env = eval.CaseInsensitiveObject[any](toRawObj(reflect.ValueOf(impl.env.Env))) } vars := eval.CaseInsensitiveObject[any]{ - "github": githubCtx, "env": env, "vars": toRawObj(reflect.ValueOf(impl.env.Vars)), "steps": toRawObj(reflect.ValueOf(impl.env.Steps)), @@ -274,17 +275,28 @@ func (impl *interperterImpl) GetVariables() eval.ReadOnlyObject[any] { "jobs": toRawObj(reflect.ValueOf(impl.env.Jobs)), "inputs": toRawObj(reflect.ValueOf(impl.env.Inputs)), } + ctxNames := impl.config.MainContextNames + if len(ctxNames) == 0 { + ctxNames = []string{"github"} + } + for _, cn := range ctxNames { + vars[cn] = mainCtx + } for name, cd := range impl.env.CtxData { - lowerName := strings.ToLower(name) - if serverPayload, ok := cd.(map[string]interface{}); ok { - if lowerName == "github" { - for k, v := range serverPayload { - // skip empty values, because github.workspace was set by Gitea Actions to an empty string - if _, ok := githubCtx[k]; !ok || v != "" && v != nil { - githubCtx[k] = v + if rawOtherCtx := vars.Get(name); rawOtherCtx != nil { + res := eval.CreateIntermediateResult(eval.NewEvaluationContext(), rawOtherCtx) + if rOtherCd, ok := res.TryGetCollectionInterface(); ok { + if otherCd, ok := rOtherCd.(eval.ReadOnlyObject[any]); ok { + if rawPayload, ok := cd.(map[string]interface{}); ok { + for k, v := range rawPayload { + // skip empty values, because github.workspace was set by Gitea Actions to an empty string + if mk, _ := otherCd.GetKv(k); v != "" && v != nil { + otherCd.GetEnumerator()[mk] = v + } + } + continue } } - continue } } vars[name] = cd diff --git a/pkg/model/action.go b/pkg/model/action.go index 2df5d052..1c7b231c 100644 --- a/pkg/model/action.go +++ b/pkg/model/action.go @@ -83,13 +83,29 @@ type Action struct { Color string `yaml:"color"` Icon string `yaml:"icon"` } `yaml:"branding"` + Definition string `yaml:"-"` + Schema *schema.Schema `yaml:"-"` +} + +func (a *Action) GetDefinition() string { + if a.Definition == "" { + return "action-root" + } + return a.Definition +} + +func (a *Action) GetSchema() *schema.Schema { + if a.Schema == nil { + return schema.GetActionSchema() + } + return a.Schema } func (a *Action) UnmarshalYAML(node *yaml.Node) error { // Validate the schema before deserializing it into our model if err := (&schema.Node{ - Definition: "action-root", - Schema: schema.GetActionSchema(), + Definition: a.GetDefinition(), + Schema: a.GetSchema(), }).UnmarshalYAML(node); err != nil { return err } @@ -110,9 +126,16 @@ type Output struct { Value string `yaml:"value"` } +type ActionConfig struct { + Definition string + Schema *schema.Schema +} + // ReadAction reads an action from a reader -func ReadAction(in io.Reader) (*Action, error) { +func ReadAction(in io.Reader, config ActionConfig) (*Action, error) { a := new(Action) + a.Schema = config.Schema + a.Definition = config.Definition err := yaml.NewDecoder(in).Decode(a) if err != nil { return nil, err diff --git a/pkg/model/planner.go b/pkg/model/planner.go index 06b808dc..66db08ea 100644 --- a/pkg/model/planner.go +++ b/pkg/model/planner.go @@ -55,8 +55,13 @@ type WorkflowFiles struct { dirPath string } +type PlannerConfig struct { + Recursive bool + Workflow WorkflowConfig +} + // NewWorkflowPlanner will load a specific workflow, all workflows from a directory or all workflows from a directory and its subdirectories -func NewWorkflowPlanner(path string, noWorkflowRecurse, strict bool) (WorkflowPlanner, error) { +func NewWorkflowPlanner(path string, config PlannerConfig) (WorkflowPlanner, error) { path, err := filepath.Abs(path) if err != nil { return nil, err @@ -71,7 +76,7 @@ func NewWorkflowPlanner(path string, noWorkflowRecurse, strict bool) (WorkflowPl if fi.IsDir() { log.Debugf("Loading workflows from '%s'", path) - if noWorkflowRecurse { + if !config.Recursive { files, err := os.ReadDir(path) if err != nil { return nil, err @@ -124,7 +129,7 @@ func NewWorkflowPlanner(path string, noWorkflowRecurse, strict bool) (WorkflowPl } log.Debugf("Reading workflow '%s'", f.Name()) - workflow, err := ReadWorkflow(f, strict) + workflow, err := ReadWorkflow(f, config.Workflow) if err != nil { _ = f.Close() if err == io.EOF { @@ -157,11 +162,11 @@ func NewWorkflowPlanner(path string, noWorkflowRecurse, strict bool) (WorkflowPl return wp, nil } -func NewSingleWorkflowPlanner(name string, f io.Reader) (WorkflowPlanner, error) { +func NewSingleWorkflowPlanner(name string, f io.Reader, config PlannerConfig) (WorkflowPlanner, error) { wp := new(workflowPlanner) log.Debugf("Reading workflow %s", name) - workflow, err := ReadWorkflow(f, false) + workflow, err := ReadWorkflow(f, config.Workflow) if err != nil { if err == io.EOF { return nil, fmt.Errorf("unable to read workflow '%s': file is empty: %w", name, err) diff --git a/pkg/model/planner_test.go b/pkg/model/planner_test.go index 57e7443f..aab5c045 100644 --- a/pkg/model/planner_test.go +++ b/pkg/model/planner_test.go @@ -31,7 +31,9 @@ func TestPlanner(t *testing.T) { assert.NoError(t, err, workdir) for _, table := range tables { fullWorkflowPath := filepath.Join(workdir, table.workflowPath) - _, err = NewWorkflowPlanner(fullWorkflowPath, table.noWorkflowRecurse, false) + _, err = NewWorkflowPlanner(fullWorkflowPath, PlannerConfig{ + Recursive: !table.noWorkflowRecurse, + }) if table.errorMessage == "" { assert.NoError(t, err, "WorkflowPlanner should exit without any error") } else { diff --git a/pkg/model/workflow.go b/pkg/model/workflow.go index db1c0a2f..8d9221b9 100644 --- a/pkg/model/workflow.go +++ b/pkg/model/workflow.go @@ -17,12 +17,30 @@ import ( // Workflow is the structure of the files in .github/workflows type Workflow struct { - File string - Name string `yaml:"name"` - RawOn yaml.Node `yaml:"on"` - Env map[string]string `yaml:"env"` - Jobs map[string]*Job `yaml:"jobs"` - Defaults Defaults `yaml:"defaults"` + File string + Name string `yaml:"name"` + RawOn yaml.Node `yaml:"on"` + Env map[string]string `yaml:"env"` + Jobs map[string]*Job `yaml:"jobs"` + Defaults Defaults `yaml:"defaults"` + Schema *schema.Schema `yaml:"-"` + Definition string `yaml:"-"` +} + +// GetDefinition gets the schema definition name for the workflow +func (w *Workflow) GetDefinition() string { + if w.Definition == "" { + return "workflow-root" + } + return w.Definition +} + +// GetSchema gets the schema for the workflow +func (w *Workflow) GetSchema() *schema.Schema { + if w.Schema == nil { + return schema.GetWorkflowSchema() + } + return w.Schema } // On events for the workflow @@ -74,27 +92,10 @@ func (w *Workflow) UnmarshalYAML(node *yaml.Node) error { } // Validate the schema before deserializing it into our model if err := (&schema.Node{ - Definition: "workflow-root", - Schema: schema.GetWorkflowSchema(), + Definition: w.GetDefinition(), + Schema: w.GetSchema(), }).UnmarshalYAML(node); err != nil { - return errors.Join(err, fmt.Errorf("actions YAML Schema Validation Error detected:\nFor more information, see: https://actions-oss.github.io/act-docs/usage/schema.html")) - } - type WorkflowDefault Workflow - return node.Decode((*WorkflowDefault)(w)) -} - -type WorkflowStrict Workflow - -func (w *WorkflowStrict) UnmarshalYAML(node *yaml.Node) error { - if err := resolveAliases(node); err != nil { - return err - } - // Validate the schema before deserializing it into our model - if err := (&schema.Node{ - Definition: "workflow-root-strict", - Schema: schema.GetWorkflowSchema(), - }).UnmarshalYAML(node); err != nil { - return errors.Join(err, fmt.Errorf("actions YAML Strict Schema Validation Error detected:\nFor more information, see: https://nektosact.com/usage/schema.html")) + return errors.Join(err, fmt.Errorf("actions YAML Schema Validation Error of definition '%s' detected:\nFor more information, see: https://actions-oss.github.io/act-docs/usage/schema.html", w.GetDefinition())) } type WorkflowDefault Workflow return node.Decode((*WorkflowDefault)(w)) @@ -708,14 +709,20 @@ func (s *Step) Type() StepType { return StepTypeUsesActionRemote } +type WorkflowConfig struct { + Definition string + Schema *schema.Schema + Strict bool +} + // ReadWorkflow returns a list of jobs for a given workflow file reader -func ReadWorkflow(in io.Reader, strict bool) (*Workflow, error) { - if strict { - w := new(WorkflowStrict) - err := yaml.NewDecoder(in).Decode(w) - return (*Workflow)(w), err - } +func ReadWorkflow(in io.Reader, config WorkflowConfig) (*Workflow, error) { w := new(Workflow) + w.Schema = config.Schema + w.Definition = config.Definition + if config.Strict { + w.Definition = "workflow-root-strict" + } err := yaml.NewDecoder(in).Decode(w) return w, err } diff --git a/pkg/model/workflow_test.go b/pkg/model/workflow_test.go index 47a9dd82..339a9e87 100644 --- a/pkg/model/workflow_test.go +++ b/pkg/model/workflow_test.go @@ -21,7 +21,7 @@ jobs: - uses: ./actions/docker-url ` - workflow, err := ReadWorkflow(strings.NewReader(yaml), false) + workflow, err := ReadWorkflow(strings.NewReader(yaml), WorkflowConfig{}) assert.NoError(t, err, "read workflow should succeed") assert.Len(t, workflow.On(), 1) @@ -40,7 +40,7 @@ jobs: - uses: ./actions/docker-url ` - workflow, err := ReadWorkflow(strings.NewReader(yaml), false) + workflow, err := ReadWorkflow(strings.NewReader(yaml), WorkflowConfig{}) assert.NoError(t, err, "read workflow should succeed") assert.Len(t, workflow.On(), 2) @@ -66,7 +66,7 @@ jobs: - uses: ./actions/docker-url ` - workflow, err := ReadWorkflow(strings.NewReader(yaml), false) + workflow, err := ReadWorkflow(strings.NewReader(yaml), WorkflowConfig{}) assert.NoError(t, err, "read workflow should succeed") assert.Len(t, workflow.On(), 2) assert.Contains(t, workflow.On(), "push") @@ -85,7 +85,7 @@ jobs: steps: - uses: ./actions/docker-url` - workflow, err := ReadWorkflow(strings.NewReader(yaml), false) + workflow, err := ReadWorkflow(strings.NewReader(yaml), WorkflowConfig{}) assert.NoError(t, err, "read workflow should succeed") assert.Equal(t, workflow.Jobs["test"].RunsOn(), []string{"ubuntu-latest"}) } @@ -103,7 +103,7 @@ jobs: steps: - uses: ./actions/docker-url` - workflow, err := ReadWorkflow(strings.NewReader(yaml), false) + workflow, err := ReadWorkflow(strings.NewReader(yaml), WorkflowConfig{}) assert.NoError(t, err, "read workflow should succeed") assert.Equal(t, workflow.Jobs["test"].RunsOn(), []string{"ubuntu-latest", "linux"}) } @@ -128,7 +128,7 @@ jobs: - uses: ./actions/docker-url ` - workflow, err := ReadWorkflow(strings.NewReader(yaml), false) + workflow, err := ReadWorkflow(strings.NewReader(yaml), WorkflowConfig{}) assert.NoError(t, err, "read workflow should succeed") assert.Len(t, workflow.Jobs, 2) assert.Contains(t, workflow.Jobs["test"].Container().Image, "nginx:latest") @@ -158,7 +158,7 @@ jobs: - uses: ./actions/docker-url ` - workflow, err := ReadWorkflow(strings.NewReader(yaml), false) + workflow, err := ReadWorkflow(strings.NewReader(yaml), WorkflowConfig{}) assert.NoError(t, err, "read workflow should succeed") assert.Len(t, workflow.Jobs, 1) @@ -196,7 +196,7 @@ jobs: uses: ./some/path/to/workflow.yaml ` - workflow, err := ReadWorkflow(strings.NewReader(yaml), false) + workflow, err := ReadWorkflow(strings.NewReader(yaml), WorkflowConfig{}) assert.NoError(t, err, "read workflow should succeed") assert.Len(t, workflow.Jobs, 6) @@ -240,7 +240,7 @@ jobs: uses: some/path/to/workflow.yaml ` - workflow, err := ReadWorkflow(strings.NewReader(yaml), false) + workflow, err := ReadWorkflow(strings.NewReader(yaml), WorkflowConfig{}) assert.NoError(t, err, "read workflow should succeed") assert.Len(t, workflow.Jobs, 4) @@ -282,7 +282,7 @@ jobs: uses: ./local-action ` - _, err := ReadWorkflow(strings.NewReader(yaml), false) + _, err := ReadWorkflow(strings.NewReader(yaml), WorkflowConfig{}) assert.Error(t, err, "read workflow should fail") } @@ -314,7 +314,7 @@ jobs: echo "${{ needs.test1.outputs.some-b-key }}" ` - workflow, err := ReadWorkflow(strings.NewReader(yaml), false) + workflow, err := ReadWorkflow(strings.NewReader(yaml), WorkflowConfig{}) assert.NoError(t, err, "read workflow should succeed") assert.Len(t, workflow.Jobs, 2) @@ -329,7 +329,7 @@ jobs: } func TestReadWorkflow_Strategy(t *testing.T) { - w, err := NewWorkflowPlanner("testdata/strategy/push.yml", true, false) + w, err := NewWorkflowPlanner("testdata/strategy/push.yml", PlannerConfig{}) assert.NoError(t, err) p, err := w.PlanJob("strategy-only-max-parallel") @@ -451,7 +451,7 @@ func TestReadWorkflow_WorkflowDispatchConfig(t *testing.T) { steps: - run: echo Test ` - workflow, err := ReadWorkflow(strings.NewReader(yaml), false) + workflow, err := ReadWorkflow(strings.NewReader(yaml), WorkflowConfig{}) assert.NoError(t, err, "read workflow should succeed") workflowDispatch := workflow.WorkflowDispatchConfig() assert.Nil(t, workflowDispatch) @@ -465,7 +465,7 @@ func TestReadWorkflow_WorkflowDispatchConfig(t *testing.T) { steps: - run: echo Test ` - workflow, err = ReadWorkflow(strings.NewReader(yaml), false) + workflow, err = ReadWorkflow(strings.NewReader(yaml), WorkflowConfig{}) assert.NoError(t, err, "read workflow should succeed") workflowDispatch = workflow.WorkflowDispatchConfig() assert.NotNil(t, workflowDispatch) @@ -480,7 +480,7 @@ func TestReadWorkflow_WorkflowDispatchConfig(t *testing.T) { steps: - run: echo Test ` - workflow, err = ReadWorkflow(strings.NewReader(yaml), false) + workflow, err = ReadWorkflow(strings.NewReader(yaml), WorkflowConfig{}) assert.NoError(t, err, "read workflow should succeed") workflowDispatch = workflow.WorkflowDispatchConfig() assert.Nil(t, workflowDispatch) @@ -494,7 +494,7 @@ func TestReadWorkflow_WorkflowDispatchConfig(t *testing.T) { steps: - run: echo Test ` - workflow, err = ReadWorkflow(strings.NewReader(yaml), false) + workflow, err = ReadWorkflow(strings.NewReader(yaml), WorkflowConfig{}) assert.NoError(t, err, "read workflow should succeed") workflowDispatch = workflow.WorkflowDispatchConfig() assert.NotNil(t, workflowDispatch) @@ -511,7 +511,7 @@ func TestReadWorkflow_WorkflowDispatchConfig(t *testing.T) { steps: - run: echo Test ` - workflow, err = ReadWorkflow(strings.NewReader(yaml), false) + workflow, err = ReadWorkflow(strings.NewReader(yaml), WorkflowConfig{}) assert.NoError(t, err, "read workflow should succeed") workflowDispatch = workflow.WorkflowDispatchConfig() assert.NotNil(t, workflowDispatch) @@ -528,7 +528,7 @@ func TestReadWorkflow_WorkflowDispatchConfig(t *testing.T) { steps: - run: echo Test ` - workflow, err = ReadWorkflow(strings.NewReader(yaml), false) + workflow, err = ReadWorkflow(strings.NewReader(yaml), WorkflowConfig{}) assert.NoError(t, err, "read workflow should succeed") workflowDispatch = workflow.WorkflowDispatchConfig() assert.Nil(t, workflowDispatch) @@ -555,7 +555,7 @@ func TestReadWorkflow_WorkflowDispatchConfig(t *testing.T) { steps: - run: echo Test ` - workflow, err = ReadWorkflow(strings.NewReader(yaml), false) + workflow, err = ReadWorkflow(strings.NewReader(yaml), WorkflowConfig{}) assert.NoError(t, err, "read workflow should succeed") workflowDispatch = workflow.WorkflowDispatchConfig() assert.NotNil(t, workflowDispatch) @@ -584,7 +584,7 @@ jobs: - uses: ./actions/docker-url ` - _, err := ReadWorkflow(strings.NewReader(yaml), true) + _, err := ReadWorkflow(strings.NewReader(yaml), WorkflowConfig{Strict: true}) assert.Error(t, err, "read workflow should succeed") } @@ -603,7 +603,7 @@ jobs: - uses: *checkout ` - w, err := ReadWorkflow(strings.NewReader(yaml), true) + w, err := ReadWorkflow(strings.NewReader(yaml), WorkflowConfig{Strict: true}) assert.NoError(t, err, "read workflow should succeed") for _, job := range w.Jobs { @@ -631,7 +631,7 @@ jobs: on: push #*trigger ` - w, err := ReadWorkflow(strings.NewReader(yaml), false) + w, err := ReadWorkflow(strings.NewReader(yaml), WorkflowConfig{}) assert.NoError(t, err, "read workflow should succeed") for _, job := range w.Jobs { diff --git a/pkg/runner/action.go b/pkg/runner/action.go index 0fed7694..19bd989d 100644 --- a/pkg/runner/action.go +++ b/pkg/runner/action.go @@ -2,14 +2,11 @@ package runner import ( "context" - "embed" "errors" "fmt" "io" - "io/fs" "os" "path" - "path/filepath" "regexp" "runtime" "strings" @@ -33,23 +30,18 @@ type actionStep interface { maybeCopyToActionDir(ctx context.Context) error } -type readAction func(ctx context.Context, step *model.Step, actionDir string, actionPath string, readFile actionYamlReader, writeFile fileWriter) (*model.Action, error) +type readAction func(ctx context.Context, step *model.Step, readFile actionYamlReader, config model.ActionConfig) (*model.Action, error) type actionYamlReader func(filename string) (io.Reader, io.Closer, error) -type fileWriter func(filename string, data []byte, perm fs.FileMode) error - type runAction func(step actionStep) common.Executor -//go:embed res/trampoline.js -var trampoline embed.FS - -func readActionImpl(ctx context.Context, step *model.Step, actionDir string, actionPath string, readFile actionYamlReader, writeFile fileWriter) (*model.Action, error) { +func readActionImpl(ctx context.Context, step *model.Step, readFile actionYamlReader, config model.ActionConfig) (*model.Action, error) { logger := common.Logger(ctx) allErrors := []error{} addError := func(fileName string, err error) { if err != nil { - allErrors = append(allErrors, fmt.Errorf("failed to read '%s' from action '%s' with path '%s' of step %w", fileName, step.String(), actionPath, err)) + allErrors = append(allErrors, fmt.Errorf("failed to read '%s' from action '%s': %w", fileName, step.String(), err)) } else { // One successful read, clear error state allErrors = nil @@ -75,39 +67,6 @@ func readActionImpl(ctx context.Context, step *model.Step, actionDir string, act logger.Debugf("Using synthetic action %v for Dockerfile", action) return action, nil } - if step.With != nil { - if val, ok := step.With["args"]; ok { - var b []byte - if b, err = trampoline.ReadFile("res/trampoline.js"); err != nil { - return nil, err - } - err2 := writeFile(filepath.Join(actionDir, actionPath, "trampoline.js"), b, 0o400) - if err2 != nil { - return nil, err2 - } - action := &model.Action{ - Name: "(Synthetic)", - Inputs: map[string]model.Input{ - "cwd": { - Description: "(Actual working directory)", - Required: false, - Default: filepath.Join(actionDir, actionPath), - }, - "command": { - Description: "(Actual program)", - Required: false, - Default: val, - }, - }, - Runs: model.ActionRuns{ - Using: "node12", - Main: "trampoline.js", - }, - } - logger.Debugf("Using synthetic action %v", action) - return action, nil - } - } } } if allErrors != nil { @@ -115,8 +74,8 @@ func readActionImpl(ctx context.Context, step *model.Step, actionDir string, act } defer closer.Close() - action, err := model.ReadAction(reader) - logger.Debugf("Read action %v from '%s'", action, "Unknown") + action, err := model.ReadAction(reader, config) + logger.Debugf("Read action %v", action) return action, err } diff --git a/pkg/runner/action_composite.go b/pkg/runner/action_composite.go index fb2ab2eb..f0efa70a 100644 --- a/pkg/runner/action_composite.go +++ b/pkg/runner/action_composite.go @@ -38,8 +38,8 @@ func evaluateCompositeInputAndEnv(ctx context.Context, parent *RunContext, step } } gh := step.getGithubContext(ctx) - env["GITHUB_ACTION_REPOSITORY"] = gh.ActionRepository - env["GITHUB_ACTION_REF"] = gh.ActionRef + step.getRunContext().setMainCtxVars(env, "ACTION_REPOSITORY", gh.ActionRepository) + step.getRunContext().setMainCtxVars(env, "ACTION_REF", gh.ActionRef) return env } diff --git a/pkg/runner/action_test.go b/pkg/runner/action_test.go index b19df463..964a6575 100644 --- a/pkg/runner/action_test.go +++ b/pkg/runner/action_test.go @@ -79,33 +79,6 @@ runs: }, }, }, - { - name: "readWithArgs", - step: &model.Step{ - With: map[string]string{ - "args": "cmd", - }, - }, - expected: &model.Action{ - Name: "(Synthetic)", - Inputs: map[string]model.Input{ - "cwd": { - Description: "(Actual working directory)", - Required: false, - Default: "actionDir/actionPath", - }, - "command": { - Description: "(Actual program)", - Required: false, - Default: "cmd", - }, - }, - Runs: model.ActionRuns{ - Using: "node12", - Main: "trampoline.js", - }, - }, - }, } for _, tt := range table { @@ -120,17 +93,11 @@ runs: return strings.NewReader(tt.fileContent), closerMock, nil } - writeFile := func(filename string, _ []byte, perm fs.FileMode) error { - assert.Equal(t, "actionDir/actionPath/trampoline.js", filename) - assert.Equal(t, fs.FileMode(0400), perm) - return nil - } - if tt.filename != "" { closerMock.On("Close") } - action, err := readActionImpl(context.Background(), tt.step, "actionDir", "actionPath", readFile, writeFile) + action, err := readActionImpl(context.Background(), tt.step, readFile, model.ActionConfig{}) assert.Nil(t, err) assert.Equal(t, tt.expected, action) diff --git a/pkg/runner/expression.go b/pkg/runner/expression.go index 5214371f..1203ba90 100644 --- a/pkg/runner/expression.go +++ b/pkg/runner/expression.go @@ -98,9 +98,10 @@ func (rc *RunContext) NewExpressionEvaluatorWithEnv(ctx context.Context, env map } return expressionEvaluator{ interpreter: exprparser.NewInterpeter(ee, exprparser.Config{ - Run: rc.Run, - WorkingDir: rc.Config.Workdir, - Context: "job", + Run: rc.Run, + WorkingDir: rc.Config.Workdir, + Context: "job", + MainContextNames: rc.Config.MainContextNames, }), } } @@ -164,9 +165,10 @@ func (rc *RunContext) newStepExpressionEvaluator(ctx context.Context, step step, } return expressionEvaluator{ interpreter: exprparser.NewInterpeter(ee, exprparser.Config{ - Run: rc.Run, - WorkingDir: rc.Config.Workdir, - Context: "step", + Run: rc.Run, + WorkingDir: rc.Config.Workdir, + Context: "step", + MainContextNames: rc.Config.MainContextNames, }), } } diff --git a/pkg/runner/res/trampoline.js b/pkg/runner/res/trampoline.js deleted file mode 100644 index e9a87322..00000000 --- a/pkg/runner/res/trampoline.js +++ /dev/null @@ -1,14 +0,0 @@ -const { spawnSync } = require('child_process') -const spawnArguments = { - cwd: process.env.INPUT_CWD, - stdio: [ - process.stdin, - process.stdout, - process.stderr - ] -} -const child = spawnSync( - '/bin/sh', - ['-c'].concat(process.env.INPUT_COMMAND), - spawnArguments) -process.exit(child.status) diff --git a/pkg/runner/reusable_workflow.go b/pkg/runner/reusable_workflow.go index 94543de1..85511c6e 100644 --- a/pkg/runner/reusable_workflow.go +++ b/pkg/runner/reusable_workflow.go @@ -12,7 +12,7 @@ import ( ) func newLocalReusableWorkflowExecutor(rc *RunContext) common.Executor { - return newReusableWorkflowExecutor(rc, rc.Config.Workdir, rc.Run.Job().Uses) + return newReusableWorkflowExecutor(rc, rc.Config.Workdir, rc.Run.Job().Uses, rc.Config.Planner) } func newRemoteReusableWorkflowExecutor(rc *RunContext) common.Executor { @@ -28,10 +28,10 @@ func newRemoteReusableWorkflowExecutor(rc *RunContext) common.Executor { // multiple reusable workflows from the same repository and ref since for each workflow we won't have to clone it again filename := fmt.Sprintf("%s/%s@%s", remoteReusableWorkflow.Org, remoteReusableWorkflow.Repo, remoteReusableWorkflow.Ref) - return newActionCacheReusableWorkflowExecutor(rc, filename, remoteReusableWorkflow) + return newActionCacheReusableWorkflowExecutor(rc, filename, remoteReusableWorkflow, rc.Config.Planner) } -func newActionCacheReusableWorkflowExecutor(rc *RunContext, filename string, remoteReusableWorkflow *remoteReusableWorkflow) common.Executor { +func newActionCacheReusableWorkflowExecutor(rc *RunContext, filename string, remoteReusableWorkflow *remoteReusableWorkflow, config model.PlannerConfig) common.Executor { return func(ctx context.Context) error { ghctx := rc.getGithubContext(ctx) remoteReusableWorkflow.URL = ghctx.ServerURL @@ -49,7 +49,7 @@ func newActionCacheReusableWorkflowExecutor(rc *RunContext, filename string, rem if _, err = treader.Next(); err != nil { return err } - planner, err := model.NewSingleWorkflowPlanner(remoteReusableWorkflow.Filename, treader) + planner, err := model.NewSingleWorkflowPlanner(remoteReusableWorkflow.Filename, treader, config) if err != nil { return err } @@ -67,9 +67,9 @@ func newActionCacheReusableWorkflowExecutor(rc *RunContext, filename string, rem } } -func newReusableWorkflowExecutor(rc *RunContext, directory string, workflow string) common.Executor { +func newReusableWorkflowExecutor(rc *RunContext, directory string, workflow string, config model.PlannerConfig) common.Executor { return func(ctx context.Context) error { - planner, err := model.NewWorkflowPlanner(path.Join(directory, workflow), true, false) + planner, err := model.NewWorkflowPlanner(path.Join(directory, workflow), config) if err != nil { return err } diff --git a/pkg/runner/run_context.go b/pkg/runner/run_context.go index 9deaef3c..5a4a5e25 100644 --- a/pkg/runner/run_context.go +++ b/pkg/runner/run_context.go @@ -1109,36 +1109,47 @@ func nestedMapLookup(m map[string]interface{}, ks ...string) (rval interface{}) return nestedMapLookup(m, ks[1:]...) } +func (rc *RunContext) setMainCtxVars(env map[string]string, name string, value string) { + // Use this to set GITHUB_ and any additional main context names in env + ctxNames := rc.Config.MainContextNames + if len(ctxNames) == 0 { + ctxNames = []string{"github"} + } + for _, ctxName := range ctxNames { + env[strings.ToUpper(ctxName)+"_"+name] = value + } +} + func (rc *RunContext) withGithubEnv(ctx context.Context, github *model.GithubContext, env map[string]string) map[string]string { env["CI"] = "true" - env["GITHUB_WORKFLOW"] = github.Workflow - env["GITHUB_RUN_ATTEMPT"] = github.RunAttempt - env["GITHUB_RUN_ID"] = github.RunID - env["GITHUB_RUN_NUMBER"] = github.RunNumber - env["GITHUB_ACTION"] = github.Action - env["GITHUB_ACTION_PATH"] = github.ActionPath - env["GITHUB_ACTION_REPOSITORY"] = github.ActionRepository - env["GITHUB_ACTION_REF"] = github.ActionRef - env["GITHUB_ACTIONS"] = "true" - env["GITHUB_ACTOR"] = github.Actor - env["GITHUB_REPOSITORY"] = github.Repository - env["GITHUB_EVENT_NAME"] = github.EventName - env["GITHUB_EVENT_PATH"] = github.EventPath - env["GITHUB_WORKSPACE"] = github.Workspace - env["GITHUB_SHA"] = github.Sha - env["GITHUB_REF"] = github.Ref - env["GITHUB_REF_NAME"] = github.RefName - env["GITHUB_REF_TYPE"] = github.RefType - env["GITHUB_JOB"] = github.Job - env["GITHUB_REPOSITORY_OWNER"] = github.RepositoryOwner - env["GITHUB_RETENTION_DAYS"] = github.RetentionDays + rc.setMainCtxVars(env, "WORKFLOW", github.Workflow) + rc.setMainCtxVars(env, "RUN_ATTEMPT", github.RunAttempt) + rc.setMainCtxVars(env, "RUN_ID", github.RunID) + rc.setMainCtxVars(env, "RUN_NUMBER", github.RunNumber) + rc.setMainCtxVars(env, "ACTION", github.Action) + rc.setMainCtxVars(env, "ACTION_PATH", github.ActionPath) + rc.setMainCtxVars(env, "ACTION_REPOSITORY", github.ActionRepository) + rc.setMainCtxVars(env, "ACTION_REF", github.ActionRef) + rc.setMainCtxVars(env, "ACTIONS", "true") + rc.setMainCtxVars(env, "ACTOR", github.Actor) + rc.setMainCtxVars(env, "REPOSITORY", github.Repository) + rc.setMainCtxVars(env, "EVENT_NAME", github.EventName) + rc.setMainCtxVars(env, "EVENT_PATH", github.EventPath) + rc.setMainCtxVars(env, "WORKSPACE", github.Workspace) + rc.setMainCtxVars(env, "SHA", github.Sha) + rc.setMainCtxVars(env, "REF", github.Ref) + rc.setMainCtxVars(env, "REF_NAME", github.RefName) + rc.setMainCtxVars(env, "REF_TYPE", github.RefType) + rc.setMainCtxVars(env, "JOB", github.Job) + rc.setMainCtxVars(env, "REPOSITORY_OWNER", github.RepositoryOwner) + rc.setMainCtxVars(env, "RETENTION_DAYS", github.RetentionDays) env["RUNNER_PERFLOG"] = github.RunnerPerflog env["RUNNER_TRACKING_ID"] = github.RunnerTrackingID - env["GITHUB_BASE_REF"] = github.BaseRef - env["GITHUB_HEAD_REF"] = github.HeadRef - env["GITHUB_SERVER_URL"] = github.ServerURL - env["GITHUB_API_URL"] = github.APIURL - env["GITHUB_GRAPHQL_URL"] = github.GraphQLURL + rc.setMainCtxVars(env, "BASE_REF", github.BaseRef) + rc.setMainCtxVars(env, "HEAD_REF", github.HeadRef) + rc.setMainCtxVars(env, "SERVER_URL", github.ServerURL) + rc.setMainCtxVars(env, "API_URL", github.APIURL) + rc.setMainCtxVars(env, "GRAPHQL_URL", github.GraphQLURL) if rc.Config.ArtifactServerPath != "" { setActionRuntimeVars(rc, env) diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index 4a998f5a..430f0928 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -68,9 +68,12 @@ type Config struct { ActionCache ActionCache // Use a custom ActionCache Implementation HostEnvironmentDir string // Custom folder for host environment, parallel jobs must be 1 - CustomExecutor map[model.JobType]func(*RunContext) common.Executor // Custom executor to run jobs - semaphore *semaphore.Weighted - Parallel int // Number of parallel jobs to run + CustomExecutor map[model.JobType]func(*RunContext) common.Executor // Custom executor to run jobs + semaphore *semaphore.Weighted + Parallel int // Number of parallel jobs to run + Planner model.PlannerConfig // Configuration for the workflow planner + Action model.ActionConfig // Configuration for action reading + MainContextNames []string // e.g. "github", "gitea", "forgejo" } func (runnerConfig *Config) GetGitHubServerURL() string { diff --git a/pkg/runner/runner_test.go b/pkg/runner/runner_test.go index 501130aa..f0714061 100644 --- a/pkg/runner/runner_test.go +++ b/pkg/runner/runner_test.go @@ -56,7 +56,7 @@ func init() { } func TestNoWorkflowsFoundByPlanner(t *testing.T) { - planner, err := model.NewWorkflowPlanner("res", true, false) + planner, err := model.NewWorkflowPlanner("hashfiles", model.PlannerConfig{}) assert.NoError(t, err) out := log.StandardLogger().Out @@ -76,7 +76,7 @@ func TestNoWorkflowsFoundByPlanner(t *testing.T) { } func TestGraphMissingEvent(t *testing.T) { - planner, err := model.NewWorkflowPlanner("testdata/issue-1595/no-event.yml", true, false) + planner, err := model.NewWorkflowPlanner("testdata/issue-1595/no-event.yml", model.PlannerConfig{}) assert.NoError(t, err) out := log.StandardLogger().Out @@ -94,7 +94,7 @@ func TestGraphMissingEvent(t *testing.T) { } func TestGraphMissingFirst(t *testing.T) { - planner, err := model.NewWorkflowPlanner("testdata/issue-1595/no-first.yml", true, false) + planner, err := model.NewWorkflowPlanner("testdata/issue-1595/no-first.yml", model.PlannerConfig{}) assert.NoError(t, err) plan, err := planner.PlanEvent("push") @@ -104,7 +104,7 @@ func TestGraphMissingFirst(t *testing.T) { } func TestGraphWithMissing(t *testing.T) { - planner, err := model.NewWorkflowPlanner("testdata/issue-1595/missing.yml", true, false) + planner, err := model.NewWorkflowPlanner("testdata/issue-1595/missing.yml", model.PlannerConfig{}) assert.NoError(t, err) out := log.StandardLogger().Out @@ -123,7 +123,7 @@ func TestGraphWithMissing(t *testing.T) { func TestGraphWithSomeMissing(t *testing.T) { log.SetLevel(log.DebugLevel) - planner, err := model.NewWorkflowPlanner("testdata/issue-1595/", true, false) + planner, err := model.NewWorkflowPlanner("testdata/issue-1595/", model.PlannerConfig{}) assert.NoError(t, err) out := log.StandardLogger().Out @@ -141,7 +141,7 @@ func TestGraphWithSomeMissing(t *testing.T) { } func TestGraphEvent(t *testing.T) { - planner, err := model.NewWorkflowPlanner("testdata/basic", true, false) + planner, err := model.NewWorkflowPlanner("testdata/basic", model.PlannerConfig{}) assert.NoError(t, err) plan, err := planner.PlanEvent("push") @@ -200,7 +200,7 @@ func (j *TestJobFileInfo) runTest(ctx context.Context, t *testing.T, cfg *Config runner, err := New(runnerConfig) assert.Nil(t, err, j.workflowPath) - planner, err := model.NewWorkflowPlanner(fullWorkflowPath, true, false) + planner, err := model.NewWorkflowPlanner(fullWorkflowPath, model.PlannerConfig{}) if j.errorMessage != "" && err != nil { assert.Error(t, err, j.errorMessage) } else if assert.Nil(t, err, fullWorkflowPath) { diff --git a/pkg/runner/step.go b/pkg/runner/step.go index 8749cfd0..06365230 100644 --- a/pkg/runner/step.go +++ b/pkg/runner/step.go @@ -141,19 +141,19 @@ func runStepExecutor(step step, stage stepStage, executor common.Executor) commo actPath := rc.JobContainer.GetActPath() outputFileCommand := path.Join("workflow", "outputcmd.txt") - (*step.getEnv())["GITHUB_OUTPUT"] = path.Join(actPath, outputFileCommand) + rc.setMainCtxVars(*step.getEnv(), "OUTPUT", path.Join(actPath, outputFileCommand)) stateFileCommand := path.Join("workflow", "statecmd.txt") - (*step.getEnv())["GITHUB_STATE"] = path.Join(actPath, stateFileCommand) + rc.setMainCtxVars(*step.getEnv(), "STATE", path.Join(actPath, stateFileCommand)) pathFileCommand := path.Join("workflow", "pathcmd.txt") - (*step.getEnv())["GITHUB_PATH"] = path.Join(actPath, pathFileCommand) + rc.setMainCtxVars(*step.getEnv(), "PATH", path.Join(actPath, pathFileCommand)) envFileCommand := path.Join("workflow", "envs.txt") - (*step.getEnv())["GITHUB_ENV"] = path.Join(actPath, envFileCommand) + rc.setMainCtxVars(*step.getEnv(), "ENV", path.Join(actPath, envFileCommand)) summaryFileCommand := path.Join("workflow", "SUMMARY.md") - (*step.getEnv())["GITHUB_STEP_SUMMARY"] = path.Join(actPath, summaryFileCommand) + rc.setMainCtxVars(*step.getEnv(), "STEP_SUMMARY", path.Join(actPath, summaryFileCommand)) _ = rc.JobContainer.Copy(actPath, &container.FileEntry{ Name: outputFileCommand, diff --git a/pkg/runner/step_action_local.go b/pkg/runner/step_action_local.go index 89206a10..580b60bb 100644 --- a/pkg/runner/step_action_local.go +++ b/pkg/runner/step_action_local.go @@ -9,7 +9,6 @@ import ( "io/fs" "os" "path" - "path/filepath" "github.com/actions-oss/act-cli/pkg/common" "github.com/actions-oss/act-cli/pkg/model" @@ -40,17 +39,7 @@ func (sal *stepActionLocal) main() common.Executor { return nil } - workdir := sal.getRunContext().Config.Workdir - actionDir := filepath.Join(workdir, sal.Step.Uses) - localReader := func(ctx context.Context) actionYamlReader { - // In case we want to limit resolving symlinks, folders are resolved by archive function - // _, cpath = sal.getContainerActionPathsExt(".") - roots := []string{ - ".", // Allow everything, other code permits it already - // path.Dir(cpath), // Allow RUNNER_WORKSPACE e.g. GITHUB_WORKSPACE/../ - // sal.RunContext.JobContainer.GetActPath(), // Allow remote action folders - } _, cpath := sal.getContainerActionPaths() return func(filename string) (io.Reader, io.Closer, error) { spath := path.Join(cpath, filename) @@ -69,7 +58,7 @@ func (sal *stepActionLocal) main() common.Executor { return nil, nil, err } if header.FileInfo().Mode()&os.ModeSymlink == os.ModeSymlink { - spath, err = symlinkJoin(spath, header.Linkname, roots...) + spath, err = symlinkJoin(spath, header.Linkname, ".") if err != nil { return nil, nil, err } @@ -81,7 +70,7 @@ func (sal *stepActionLocal) main() common.Executor { } } - actionModel, err := sal.readAction(ctx, sal.Step, actionDir, "", localReader(ctx), os.WriteFile) + actionModel, err := sal.readAction(ctx, sal.Step, localReader(ctx), sal.RunContext.Config.Action) if err != nil { return err } diff --git a/pkg/runner/step_action_local_test.go b/pkg/runner/step_action_local_test.go index 6d0688fa..44f7d2d7 100644 --- a/pkg/runner/step_action_local_test.go +++ b/pkg/runner/step_action_local_test.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "io" - "path/filepath" "strings" "testing" @@ -24,8 +23,8 @@ func (salm *stepActionLocalMocks) runAction(step actionStep) common.Executor { return args.Get(0).(func(context.Context) error) } -func (salm *stepActionLocalMocks) readAction(_ context.Context, step *model.Step, actionDir string, actionPath string, readFile actionYamlReader, writeFile fileWriter) (*model.Action, error) { - args := salm.Called(step, actionDir, actionPath, readFile, writeFile) +func (salm *stepActionLocalMocks) readAction(_ context.Context, step *model.Step, readFile actionYamlReader, config model.ActionConfig) (*model.Action, error) { + args := salm.Called(step, readFile, config) return args.Get(0).(*model.Action), args.Error(1) } @@ -66,7 +65,7 @@ func TestStepActionLocalTest(t *testing.T) { }, } - salm.On("readAction", sal.Step, filepath.Clean("/tmp/path/to/action"), "", mock.Anything, mock.Anything). + salm.On("readAction", sal.Step, mock.Anything, sal.RunContext.Config.Action). Return(&model.Action{}, nil) cm.On("Copy", "/var/run/act", mock.AnythingOfType("[]*container.FileEntry")).Return(func(_ context.Context) error { diff --git a/pkg/runner/step_action_remote.go b/pkg/runner/step_action_remote.go index 742e576e..20d462d3 100644 --- a/pkg/runner/step_action_remote.go +++ b/pkg/runner/step_action_remote.go @@ -92,7 +92,7 @@ func (sar *stepActionRemote) prepareActionExecutor() common.Executor { } } - actionModel, err := sar.readAction(ctx, sar.Step, sar.resolvedSha, sar.remoteAction.Path, remoteReader(ctx), os.WriteFile) + actionModel, err := sar.readAction(ctx, sar.Step, remoteReader(ctx), sar.RunContext.Config.Action) sar.action = actionModel return err } diff --git a/pkg/runner/step_action_remote_test.go b/pkg/runner/step_action_remote_test.go index ffedd2dc..630163c5 100644 --- a/pkg/runner/step_action_remote_test.go +++ b/pkg/runner/step_action_remote_test.go @@ -19,8 +19,8 @@ type stepActionRemoteMocks struct { mock.Mock } -func (sarm *stepActionRemoteMocks) readAction(_ context.Context, step *model.Step, actionDir string, actionPath string, readFile actionYamlReader, writeFile fileWriter) (*model.Action, error) { - args := sarm.Called(step, actionDir, actionPath, readFile, writeFile) +func (sarm *stepActionRemoteMocks) readAction(_ context.Context, step *model.Step, readFile actionYamlReader, config model.ActionConfig) (*model.Action, error) { + args := sarm.Called(step, readFile, config) return args.Get(0).(*model.Action), args.Error(1) } @@ -175,6 +175,9 @@ func TestStepActionRemote(t *testing.T) { GitHubServerURL: tt.gitHubServerURL, GitHubAPIServerURL: tt.gitHubAPIServerURL, GitHubGraphQlAPIServerURL: tt.gitHubGraphQlAPIServerURL, + Action: model.ActionConfig{ + Definition: "action-root", + }, }, Run: &model.Run{ JobID: "1", @@ -201,7 +204,7 @@ func TestStepActionRemote(t *testing.T) { cacheMock.Mock.On("Fetch", ctx, mock.AnythingOfType("string"), serverURL+"/remote/action", "v1", "").Return("someval") if tt.mocks.read { - sarm.Mock.On("readAction", sar.Step, "someval", "", mock.Anything, mock.Anything).Return(&model.Action{}, nil) + sarm.Mock.On("readAction", sar.Step, mock.Anything, sar.RunContext.Config.Action).Return(&model.Action{}, nil) } if tt.mocks.run { remoteAction := newRemoteAction(sar.Step.Uses) @@ -282,7 +285,7 @@ func TestStepActionRemotePre(t *testing.T) { readAction: sarm.readAction, } - sarm.Mock.On("readAction", sar.Step, "someval", "path", mock.Anything, mock.Anything).Return(&model.Action{}, nil) + sarm.Mock.On("readAction", sar.Step, mock.Anything, sar.RunContext.Config.Action).Return(&model.Action{}, nil) cacheMock.Mock.On("Fetch", ctx, mock.AnythingOfType("string"), "https://github.com/org/repo", "ref", "").Return("someval") err := sar.pre()(ctx) @@ -335,7 +338,7 @@ func TestStepActionRemotePreThroughAction(t *testing.T) { readAction: sarm.readAction, } - sarm.Mock.On("readAction", sar.Step, mock.AnythingOfType("string"), "path", mock.Anything, mock.Anything).Return(&model.Action{}, nil) + sarm.Mock.On("readAction", sar.Step, mock.Anything, sar.RunContext.Config.Action).Return(&model.Action{}, nil) cacheMock.Mock.On("Fetch", ctx, mock.AnythingOfType("string"), "https://github.com/org/repo", "ref", "").Return("someval") err := sar.pre()(ctx) @@ -389,7 +392,7 @@ func TestStepActionRemotePreThroughActionToken(t *testing.T) { readAction: sarm.readAction, } - sarm.Mock.On("readAction", sar.Step, mock.AnythingOfType("string"), "path", mock.Anything, mock.Anything).Return(&model.Action{}, nil) + sarm.Mock.On("readAction", sar.Step, mock.Anything, sar.RunContext.Config.Action).Return(&model.Action{}, nil) cacheMock.Mock.On("Fetch", ctx, mock.AnythingOfType("string"), "https://github.com/org/repo", "ref", "PRIVATE_ACTIONS_TOKEN_ON_GITHUB").Return("someval") err := sar.pre()(ctx) diff --git a/pkg/schema/gitea_schema.go b/pkg/schema/gitea_schema.go index 01ae8607..fe7881b3 100644 --- a/pkg/schema/gitea_schema.go +++ b/pkg/schema/gitea_schema.go @@ -29,3 +29,16 @@ func updateUses(mapping *MappingDefinition) { uses.Type = "string-strategy-context" mapping.Properties["uses"] = uses } + +func GetGiteaActionSchema() *Schema { + schema := GetActionSchema() + in := schema.Definitions + schema.Definitions = map[string]Definition{} + for k, v := range in { + if v.Context != nil && slices.Contains(v.Context, "github") { + v.Context = append(v.Context, "gitea", "env") + } + schema.Definitions[k] = v + } + return schema +}