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 <christopher.homberger@web.de>
Co-committed-by: Christopher Homberger <christopher.homberger@web.de>
This commit is contained in:
Christopher Homberger
2026-02-14 16:23:59 +00:00
committed by ChristopherHX
parent faa252c8e9
commit 933c4a5bd5
25 changed files with 265 additions and 253 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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 {