summaryrefslogtreecommitdiffstats
path: root/pkg/jobparser/model.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/jobparser/model.go')
-rw-r--r--pkg/jobparser/model.go338
1 files changed, 338 insertions, 0 deletions
diff --git a/pkg/jobparser/model.go b/pkg/jobparser/model.go
new file mode 100644
index 0000000..f63a045
--- /dev/null
+++ b/pkg/jobparser/model.go
@@ -0,0 +1,338 @@
+package jobparser
+
+import (
+ "fmt"
+
+ "github.com/nektos/act/pkg/model"
+ "gopkg.in/yaml.v3"
+)
+
+// SingleWorkflow is a workflow with single job and single matrix
+type SingleWorkflow struct {
+ Name string `yaml:"name,omitempty"`
+ RawOn yaml.Node `yaml:"on,omitempty"`
+ Env map[string]string `yaml:"env,omitempty"`
+ RawJobs yaml.Node `yaml:"jobs,omitempty"`
+ Defaults Defaults `yaml:"defaults,omitempty"`
+}
+
+func (w *SingleWorkflow) Job() (string, *Job) {
+ ids, jobs, _ := w.jobs()
+ if len(ids) >= 1 {
+ return ids[0], jobs[0]
+ }
+ return "", nil
+}
+
+func (w *SingleWorkflow) jobs() ([]string, []*Job, error) {
+ ids, jobs, err := parseMappingNode[*Job](&w.RawJobs)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ for _, job := range jobs {
+ steps := make([]*Step, 0, len(job.Steps))
+ for _, s := range job.Steps {
+ if s != nil {
+ steps = append(steps, s)
+ }
+ }
+ job.Steps = steps
+ }
+
+ return ids, jobs, nil
+}
+
+func (w *SingleWorkflow) SetJob(id string, job *Job) error {
+ m := map[string]*Job{
+ id: job,
+ }
+ out, err := yaml.Marshal(m)
+ if err != nil {
+ return err
+ }
+ node := yaml.Node{}
+ if err := yaml.Unmarshal(out, &node); err != nil {
+ return err
+ }
+ if len(node.Content) != 1 || node.Content[0].Kind != yaml.MappingNode {
+ return fmt.Errorf("can not set job: %q", out)
+ }
+ w.RawJobs = *node.Content[0]
+ return nil
+}
+
+func (w *SingleWorkflow) Marshal() ([]byte, error) {
+ return yaml.Marshal(w)
+}
+
+type Job struct {
+ Name string `yaml:"name,omitempty"`
+ RawNeeds yaml.Node `yaml:"needs,omitempty"`
+ RawRunsOn yaml.Node `yaml:"runs-on,omitempty"`
+ Env yaml.Node `yaml:"env,omitempty"`
+ If yaml.Node `yaml:"if,omitempty"`
+ Steps []*Step `yaml:"steps,omitempty"`
+ TimeoutMinutes string `yaml:"timeout-minutes,omitempty"`
+ Services map[string]*ContainerSpec `yaml:"services,omitempty"`
+ Strategy Strategy `yaml:"strategy,omitempty"`
+ RawContainer yaml.Node `yaml:"container,omitempty"`
+ Defaults Defaults `yaml:"defaults,omitempty"`
+ Outputs map[string]string `yaml:"outputs,omitempty"`
+ Uses string `yaml:"uses,omitempty"`
+ With map[string]interface{} `yaml:"with,omitempty"`
+ RawSecrets yaml.Node `yaml:"secrets,omitempty"`
+}
+
+func (j *Job) Clone() *Job {
+ if j == nil {
+ return nil
+ }
+ return &Job{
+ Name: j.Name,
+ RawNeeds: j.RawNeeds,
+ RawRunsOn: j.RawRunsOn,
+ Env: j.Env,
+ If: j.If,
+ Steps: j.Steps,
+ TimeoutMinutes: j.TimeoutMinutes,
+ Services: j.Services,
+ Strategy: j.Strategy,
+ RawContainer: j.RawContainer,
+ Defaults: j.Defaults,
+ Outputs: j.Outputs,
+ Uses: j.Uses,
+ With: j.With,
+ RawSecrets: j.RawSecrets,
+ }
+}
+
+func (j *Job) Needs() []string {
+ return (&model.Job{RawNeeds: j.RawNeeds}).Needs()
+}
+
+func (j *Job) EraseNeeds() *Job {
+ j.RawNeeds = yaml.Node{}
+ return j
+}
+
+func (j *Job) RunsOn() []string {
+ return (&model.Job{RawRunsOn: j.RawRunsOn}).RunsOn()
+}
+
+type Step struct {
+ ID string `yaml:"id,omitempty"`
+ If yaml.Node `yaml:"if,omitempty"`
+ Name string `yaml:"name,omitempty"`
+ Uses string `yaml:"uses,omitempty"`
+ Run string `yaml:"run,omitempty"`
+ WorkingDirectory string `yaml:"working-directory,omitempty"`
+ Shell string `yaml:"shell,omitempty"`
+ Env yaml.Node `yaml:"env,omitempty"`
+ With map[string]string `yaml:"with,omitempty"`
+ ContinueOnError bool `yaml:"continue-on-error,omitempty"`
+ TimeoutMinutes string `yaml:"timeout-minutes,omitempty"`
+}
+
+// String gets the name of step
+func (s *Step) String() string {
+ if s == nil {
+ return ""
+ }
+ return (&model.Step{
+ ID: s.ID,
+ Name: s.Name,
+ Uses: s.Uses,
+ Run: s.Run,
+ }).String()
+}
+
+type ContainerSpec struct {
+ Image string `yaml:"image,omitempty"`
+ Env map[string]string `yaml:"env,omitempty"`
+ Ports []string `yaml:"ports,omitempty"`
+ Volumes []string `yaml:"volumes,omitempty"`
+ Options string `yaml:"options,omitempty"`
+ Credentials map[string]string `yaml:"credentials,omitempty"`
+ Cmd []string `yaml:"cmd,omitempty"`
+}
+
+type Strategy struct {
+ FailFastString string `yaml:"fail-fast,omitempty"`
+ MaxParallelString string `yaml:"max-parallel,omitempty"`
+ RawMatrix yaml.Node `yaml:"matrix,omitempty"`
+}
+
+type Defaults struct {
+ Run RunDefaults `yaml:"run,omitempty"`
+}
+
+type RunDefaults struct {
+ Shell string `yaml:"shell,omitempty"`
+ WorkingDirectory string `yaml:"working-directory,omitempty"`
+}
+
+type Event struct {
+ Name string
+ acts map[string][]string
+ schedules []map[string]string
+}
+
+func (evt *Event) IsSchedule() bool {
+ return evt.schedules != nil
+}
+
+func (evt *Event) Acts() map[string][]string {
+ return evt.acts
+}
+
+func (evt *Event) Schedules() []map[string]string {
+ return evt.schedules
+}
+
+func ParseRawOn(rawOn *yaml.Node) ([]*Event, error) {
+ switch rawOn.Kind {
+ case yaml.ScalarNode:
+ var val string
+ err := rawOn.Decode(&val)
+ if err != nil {
+ return nil, err
+ }
+ return []*Event{
+ {Name: val},
+ }, nil
+ case yaml.SequenceNode:
+ var val []interface{}
+ err := rawOn.Decode(&val)
+ if err != nil {
+ return nil, err
+ }
+ res := make([]*Event, 0, len(val))
+ for _, v := range val {
+ switch t := v.(type) {
+ case string:
+ res = append(res, &Event{Name: t})
+ default:
+ return nil, fmt.Errorf("invalid type %T", t)
+ }
+ }
+ return res, nil
+ case yaml.MappingNode:
+ events, triggers, err := parseMappingNode[interface{}](rawOn)
+ if err != nil {
+ return nil, err
+ }
+ res := make([]*Event, 0, len(events))
+ for i, k := range events {
+ v := triggers[i]
+ if v == nil {
+ res = append(res, &Event{
+ Name: k,
+ acts: map[string][]string{},
+ })
+ continue
+ }
+ switch t := v.(type) {
+ case string:
+ res = append(res, &Event{
+ Name: k,
+ acts: map[string][]string{},
+ })
+ case []string:
+ res = append(res, &Event{
+ Name: k,
+ acts: map[string][]string{},
+ })
+ case map[string]interface{}:
+ 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
+ if acts[act][i], ok = v.(string); !ok {
+ return nil, fmt.Errorf("unknown on type: %#v", branches)
+ }
+ }
+ case map[string]interface{}:
+ if k != "workflow_dispatch" || act != "inputs" {
+ return nil, fmt.Errorf("unknown on type: %#v", v)
+ }
+ acts = nil
+ default:
+ return nil, fmt.Errorf("unknown on type: %#v", branches)
+ }
+ }
+ res = append(res, &Event{
+ Name: k,
+ 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{
+ Name: k,
+ schedules: schedules,
+ })
+ default:
+ return nil, fmt.Errorf("unknown on type: %#v", v)
+ }
+ }
+ return res, nil
+ default:
+ return nil, fmt.Errorf("unknown on type: %v", rawOn.Kind)
+ }
+}
+
+// parseMappingNode parse a mapping node and preserve order.
+func parseMappingNode[T any](node *yaml.Node) ([]string, []T, error) {
+ if node.Kind != yaml.MappingNode {
+ return nil, nil, fmt.Errorf("input node is not a mapping node")
+ }
+
+ var scalars []string
+ var datas []T
+ expectKey := true
+ for _, item := range node.Content {
+ if expectKey {
+ if item.Kind != yaml.ScalarNode {
+ return nil, nil, fmt.Errorf("not a valid scalar node: %v", item.Value)
+ }
+ scalars = append(scalars, item.Value)
+ expectKey = false
+ } else {
+ var val T
+ if err := item.Decode(&val); err != nil {
+ return nil, nil, err
+ }
+ datas = append(datas, val)
+ expectKey = true
+ }
+ }
+
+ if len(scalars) != len(datas) {
+ return nil, nil, fmt.Errorf("invalid definition of on: %v", node.Value)
+ }
+
+ return scalars, datas, nil
+}