Skip to content

Commit

Permalink
Merge branch 'issue-216' (fix #216)
Browse files Browse the repository at this point in the history
  • Loading branch information
rhysd committed Sep 22, 2022
2 parents c254394 + 90103e0 commit 0ffabf9
Show file tree
Hide file tree
Showing 19 changed files with 508 additions and 143 deletions.
6 changes: 4 additions & 2 deletions ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -762,9 +762,11 @@ type WorkflowCallSecret struct {
type WorkflowCall struct {
// Uses is a workflow specification to be called. This field is mandatory.
Uses *String
// Inputs is a map from input name to input value at 'with:'.
// Inputs is a map from input name to input value at 'with:'. Keys are in lower case since input names
// are case-insensitive.
Inputs map[string]*WorkflowCallInput
// Secrets is a map from secret name to secret value at 'secrets:'.
// Secrets is a map from secret name to secret value at 'secrets:'. Keys are in lower case since input
// names are case-insensitive.
Secrets map[string]*WorkflowCallSecret
// InheritSecrets is true when 'secrets: inherit' is specified. In this case, Secrets must be empty.
// https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onworkflow_callsecretsinherit
Expand Down
4 changes: 2 additions & 2 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -1159,7 +1159,7 @@ func (p *parser) parseJob(id *String, n *yaml.Node) *Job {
with := p.parseSectionMapping("with", v, false)
call.Inputs = make(map[string]*WorkflowCallInput, len(with))
for _, i := range with {
call.Inputs[i.key.Value] = &WorkflowCallInput{
call.Inputs[strings.ToLower(i.key.Value)] = &WorkflowCallInput{
Name: i.key,
Value: p.parseString(i.val, true),
}
Expand All @@ -1178,7 +1178,7 @@ func (p *parser) parseJob(id *String, n *yaml.Node) *Job {
secrets := p.parseSectionMapping("secrets", v, false)
call.Secrets = make(map[string]*WorkflowCallSecret, len(secrets))
for _, s := range secrets {
call.Secrets[s.key.Value] = &WorkflowCallSecret{
call.Secrets[strings.ToLower(s.key.Value)] = &WorkflowCallSecret{
Name: s.key,
Value: p.parseString(s.val, true),
}
Expand Down
137 changes: 117 additions & 20 deletions reusable_workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import (
"gopkg.in/yaml.v3"
)

// ReusableWorkflowMetadataInput is input metadata for validating local reusable workflow file.
// ReusableWorkflowMetadataInput is an input metadata for validating local reusable workflow file.
type ReusableWorkflowMetadataInput struct {
// Name is a name of the input defined in the reusable workflow.
Name string
// Required is true when 'required' field of the input is set to true and no default value is set.
Required bool
// Type is a type of the input. When the input type is unknown, 'any' type is set.
Expand Down Expand Up @@ -47,33 +49,122 @@ func (input *ReusableWorkflowMetadataInput) UnmarshalYAML(n *yaml.Node) error {
return nil
}

// ReusableWorkflowMetadataSecretRequired is metadata to indicate a secret of reusable workflow is
// required or not.
type ReusableWorkflowMetadataSecretRequired bool
// ReusableWorkflowMetadataInputs is a map from input name to reusable wokflow input metadata. The
// keys are in lower case since input names of workflow calls are case insensitive.
type ReusableWorkflowMetadataInputs map[string]*ReusableWorkflowMetadataInput

// UnmarshalYAML implements yaml.Unmarshaler.
func (required *ReusableWorkflowMetadataSecretRequired) UnmarshalYAML(n *yaml.Node) error {
type metadata struct {
Required bool `yaml:"required"`
func (inputs *ReusableWorkflowMetadataInputs) UnmarshalYAML(n *yaml.Node) error {
if n.Kind != yaml.MappingNode {
return fmt.Errorf(
"yaml: on.workflow_call.inputs must be mapping node but found %s node at line:%d, col:%d",
nodeKindName(n.Kind),
n.Line,
n.Column,
)
}

var md metadata
if err := n.Decode(&md); err != nil {
return err
md := make(ReusableWorkflowMetadataInputs, len(n.Content)/2)
for i := 0; i < len(n.Content); i += 2 {
k, v := n.Content[i], n.Content[i+1]

var m ReusableWorkflowMetadataInput
if err := v.Decode(&m); err != nil {
return err
}
m.Name = k.Value
if m.Type == nil {
m.Type = AnyType{} // Reach here when `v` is null node
}

md[strings.ToLower(k.Value)] = &m
}

*inputs = md
return nil
}

// ReusableWorkflowMetadataSecret is a secret metadata for validating local reusable workflow file.
type ReusableWorkflowMetadataSecret struct {
// Name is a name of the secret in the reusable workflow.
Name string
// Required indicates wether the secret is required by its reusable workflow. When this value is
// true, workflow calls must set this secret unless secrets are not inherited.
Required bool `yaml:"required"`
}

// ReusableWorkflowMetadataSecrets is a map from secret name to reusable wokflow secret metadata.
// The keys are in lower case since secret names of workflow calls are case insensitive.
type ReusableWorkflowMetadataSecrets map[string]*ReusableWorkflowMetadataSecret

// UnmarshalYAML implements yaml.Unmarshaler.
func (secrets *ReusableWorkflowMetadataSecrets) UnmarshalYAML(n *yaml.Node) error {
if n.Kind != yaml.MappingNode {
return fmt.Errorf(
"yaml: on.workflow_call.secrets must be mapping node but found %s node at line:%d, col:%d",
nodeKindName(n.Kind),
n.Line,
n.Column,
)
}

*required = ReusableWorkflowMetadataSecretRequired(md.Required)
md := make(ReusableWorkflowMetadataSecrets, len(n.Content)/2)
for i := 0; i < len(n.Content); i += 2 {
k, v := n.Content[i], n.Content[i+1]

var s ReusableWorkflowMetadataSecret
if err := v.Decode(&s); err != nil {
return err
}
s.Name = k.Value

md[strings.ToLower(k.Value)] = &s
}

*secrets = md
return nil
}

// ReusableWorkflowMetadataOutput is an output metadata for validating local reusable workflow file.
type ReusableWorkflowMetadataOutput struct {
// Name is a name of the output in the reusable workflow.
Name string
}

// ReusableWorkflowMetadataOutputs is a map from output name to reusable wokflow output metadata.
// The keys are in lower case since output names of workflow calls are case insensitive.
type ReusableWorkflowMetadataOutputs map[string]*ReusableWorkflowMetadataOutput

// UnmarshalYAML implements yaml.Unmarshaler.
func (outputs *ReusableWorkflowMetadataOutputs) UnmarshalYAML(n *yaml.Node) error {
if n.Kind != yaml.MappingNode {
return fmt.Errorf(
"yaml: on.workflow_call.outputs must be mapping node but found %s node at line:%d, col:%d",
nodeKindName(n.Kind),
n.Line,
n.Column,
)
}

md := make(ReusableWorkflowMetadataOutputs, len(n.Content))
for i := 0; i < len(n.Content); i += 2 {
k := n.Content[i]
md[strings.ToLower(k.Value)] = &ReusableWorkflowMetadataOutput{
Name: k.Value,
}
}

*outputs = md
return nil
}

// ReusableWorkflowMetadata is metadata to validate local reusable workflows. This struct does not
// contain all metadata from YAML file. It only contains metadata which is necessary to validate
// reusable workflow files by actionlint.
type ReusableWorkflowMetadata struct {
Inputs map[string]*ReusableWorkflowMetadataInput `yaml:"inputs"`
Outputs map[string]struct{} `yaml:"outputs"`
Secrets map[string]ReusableWorkflowMetadataSecretRequired `yaml:"secrets"`
Inputs ReusableWorkflowMetadataInputs `yaml:"inputs"`
Outputs ReusableWorkflowMetadataOutputs `yaml:"outputs"`
Secrets ReusableWorkflowMetadataSecrets `yaml:"secrets"`
}

// LocalReusableWorkflowCache is a cache for local reusable workflow metadata files. It avoids find/read/parse
Expand Down Expand Up @@ -191,9 +282,9 @@ func (c *LocalReusableWorkflowCache) WriteWorkflowCallEvent(wpath string, event
}

m := &ReusableWorkflowMetadata{
Inputs: map[string]*ReusableWorkflowMetadataInput{},
Outputs: map[string]struct{}{},
Secrets: map[string]ReusableWorkflowMetadataSecretRequired{},
Inputs: ReusableWorkflowMetadataInputs{},
Outputs: ReusableWorkflowMetadataOutputs{},
Secrets: ReusableWorkflowMetadataSecrets{},
}

for n, i := range event.Inputs {
Expand All @@ -206,19 +297,25 @@ func (c *LocalReusableWorkflowCache) WriteWorkflowCallEvent(wpath string, event
case WorkflowCallEventInputTypeString:
t = StringType{}
}
m.Inputs[n.Value] = &ReusableWorkflowMetadataInput{
m.Inputs[strings.ToLower(n.Value)] = &ReusableWorkflowMetadataInput{
Type: t,
Required: i.Required != nil && i.Required.Value && i.Default == nil,
Name: n.Value,
}
}

for n := range event.Outputs {
m.Outputs[n.Value] = struct{}{}
m.Outputs[strings.ToLower(n.Value)] = &ReusableWorkflowMetadataOutput{
Name: n.Value,
}
}

for n, s := range event.Secrets {
r := s.Required != nil && s.Required.Value
m.Secrets[n.Value] = ReusableWorkflowMetadataSecretRequired(r)
m.Secrets[strings.ToLower(n.Value)] = &ReusableWorkflowMetadataSecret{
Required: r,
Name: n.Value,
}
}

c.mu.Lock()
Expand Down
Loading

0 comments on commit 0ffabf9

Please sign in to comment.