1// SPDX-License-Identifier: MIT
2
3package executor
4
5import (
6 "context"
7 "fmt"
8 "strings"
9
10 "bnema.dev/ermite-actions/action"
11 "bnema.dev/ermite-actions/expr"
12 "bnema.dev/ermite-actions/template"
13 "bnema.dev/ermite-actions/workflow"
14)
15
16// inputSet is the inputs of a step: a dictionary with OrdinalIgnoreCase keys
17// that keeps insertion order (Dictionary<string,string>, which only keeps it
18// while nothing is removed).
19type inputSet struct {
20 keys []string
21 vals map[string]string
22}
23
24func newInputSet() *inputSet { return &inputSet{vals: map[string]string{}} }
25
26func (in *inputSet) lower(k string) string { return strings.ToLower(k) }
27
28func (in *inputSet) Set(k, v string) {
29 l := in.lower(k)
30 if _, ok := in.vals[l]; !ok {
31 in.keys = append(in.keys, k)
32 }
33 in.vals[l] = v
34}
35
36func (in *inputSet) Get(k string) (string, bool) { v, ok := in.vals[in.lower(k)]; return v, ok }
37func (in *inputSet) Has(k string) bool { _, ok := in.vals[in.lower(k)]; return ok }
38func (in *inputSet) Len() int { return len(in.keys) }
39
40// evalInputs is EvaluateStepInputs: the "with" of the step (a mapping, or the
41// expression of a composite action's step that stands for one).
42func (j *job) evalInputs(st *workflow.Step, env *ordered) (*inputSet, error) {
43 in := newInputSet()
44 var tok template.Token
45 switch {
46 case st.With != nil:
47 tok = st.With
48 case st.WithExpression != nil:
49 tok = st.WithExpression
50 default:
51 return in, nil
52 }
53 v, err := template.Evaluate(context.Background(), tok, j.evalEnv(env))
54 if err != nil {
55 return nil, err
56 }
57 d, ok := v.(*expr.Dictionary)
58 if !ok {
59 return in, nil
60 }
61 for _, p := range d.Pairs() {
62 if k := p.Value.Kind(); k == expr.KindArray || k == expr.KindDictionary {
63 return nil, ghErrorf("Unexpected type '%s' encountered while reading 'inputs value'. The type 'StringToken' was expected.", k)
64 }
65 in.Set(p.Key, expr.CoerceString(p.Value))
66 }
67 return in, nil
68}
69
70// mergeInputs is the second half of ActionRunner.RunAsync's input handling
71// (ActionRunner.cs:121-186): deprecation warnings for the inputs the step
72// gives, the defaults of the others (evaluated in the context of the step), and
73// the warning about inputs the action does not declare. The missing required
74// inputs are not checked: the runner has no such check.
75func (j *job) mergeInputs(s *stepRun, def *actionDef, in *inputSet) error {
76 var act *action.Action
77 if def != nil {
78 act = def.act
79 }
80 user := append([]string(nil), in.keys...)
81 if act != nil {
82 for _, k := range user {
83 for i := range act.Inputs {
84 if act.Inputs[i].Name == k && act.Inputs[i].Deprecated() {
85 j.tagged(s.num, "##[warning]", fmt.Sprintf("Input '%s' has been deprecated with message: %s", k, act.Inputs[i].DeprecationMessage))
86 }
87 }
88 }
89 }
90 valid := newInputSet()
91 if def != nil && def.kind == kindDocker {
92 // A container action always accepts 'entryPoint' and 'args'.
93 valid.Set("entryPoint", "")
94 valid.Set("args", "")
95 }
96 if act != nil {
97 for i := range act.Inputs {
98 name := act.Inputs[i].Name
99 valid.Set(name, "")
100 if in.Has(name) {
101 continue
102 }
103 val, err := j.evalDefault(act.Inputs[i].Default, s.env)
104 if err != nil {
105 return err
106 }
107 in.Set(name, val)
108 }
109 }
110 // Only for actions with a manifest or a repository reference (not docker://).
111 if s.n.uses.Kind == action.UsesDocker {
112 return nil
113 }
114 var unexpected []string
115 for _, k := range user {
116 if !valid.Has(k) {
117 unexpected = append(unexpected, k)
118 }
119 }
120 if len(unexpected) > 0 {
121 j.tagged(s.num, "##[warning]", fmt.Sprintf("Unexpected input(s) '%s', valid inputs are ['%s']",
122 strings.Join(unexpected, "', '"), strings.Join(valid.keys, "', '")))
123 }
124 return nil
125}
126
127// evalDefault is EvaluateDefaultInput: the default is a string or an expression
128// over github, strategy, matrix, job, runner and hashFiles.
129func (j *job) evalDefault(tok template.Token, env *ordered) (string, error) {
130 if tok == nil {
131 return "", nil
132 }
133 return j.evalString(tok, env)
134}
135
136// inputEnvName is the variable of an input (Handler.AddInputsToEnvironment).
137func inputEnvName(key string) string {
138 return "INPUT_" + strings.ToUpper(strings.ReplaceAll(key, " ", "_"))
139}