Compare commits

...

2 Commits

2 changed files with 200 additions and 56 deletions

View File

@@ -172,10 +172,20 @@ 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 {
@@ -190,6 +200,47 @@ 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 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:
@@ -218,79 +269,119 @@ func ParseRawOn(rawOn *yaml.Node) ([]*Event, error) {
} }
return res, nil return res, nil
case yaml.MappingNode: case yaml.MappingNode:
events, triggers, err := parseMappingNode[interface{}](rawOn) events, triggers, err := parseMappingNode[yaml.Node](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]
if v == nil { switch v.Kind {
case yaml.ScalarNode:
res = append(res, &Event{ res = append(res, &Event{
Name: k, Name: k,
acts: map[string][]string{},
}) })
continue case yaml.SequenceNode:
} var t []interface{}
switch t := v.(type) { err := v.Decode(&t)
case string: if err != nil {
res = append(res, &Event{ return nil, err
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)
}
}
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)) schedules := make([]map[string]string, len(t))
if k == "schedule" {
for i, tt := range t { for i, tt := range t {
vv, ok := tt.(map[string]interface{}) vv, ok := tt.(map[string]interface{})
if !ok { if !ok {
return nil, fmt.Errorf("unknown on type: %#v", v) return nil, fmt.Errorf("unknown on type(schedule): %#v", v)
} }
schedules[i] = make(map[string]string, len(vv)) schedules[i] = make(map[string]string, len(vv))
for k, vvv := range vv { for k, vvv := range vv {
var ok bool var ok bool
if schedules[i][k], ok = vvv.(string); !ok { if schedules[i][k], ok = vvv.(string); !ok {
return nil, fmt.Errorf("unknown on type: %#v", v) return nil, fmt.Errorf("unknown on type(schedule): %#v", v)
} }
} }
} }
}
if len(schedules) == 0 {
schedules = nil
}
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.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: default:
return nil, fmt.Errorf("unknown on type: %#v", v) 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:
return nil, fmt.Errorf("unknown on type: %v", v.Kind)
} }
} }
return res, nil return res, nil

View File

@@ -186,6 +186,60 @@ 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) {
@@ -230,8 +284,7 @@ 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"},
}, },