v0.3.0 8f98fd3

actions_run.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package executor
4
5import (
6	"fmt"
7	"path/filepath"
8	"strings"
9
10	"bnema.dev/ermite-actions/action"
11)
12
13// stageName is for messages.
14func (s stage) String() string {
15	switch s {
16	case stagePre:
17		return "Pre"
18	case stagePost:
19		return "Post"
20	}
21	return "Main"
22}
23
24// definition is ActionManager.LoadAction for the step: the manifest of a local
25// action is read when the step runs; the other actions were loaded by the
26// pre-job phase.
27func (j *job) definition(s *stepRun) (*actionDef, error) {
28	n := s.n
29	switch n.uses.Kind {
30	case action.UsesDocker:
31		return &actionDef{kind: kindDocker, image: n.uses.Image}, nil
32	case action.UsesLocal:
33		dir := j.actionDir(n)
34		def, err := j.loadDef(s.num, dir)
35		if err == errNoAction {
36			return nil, ghErrorf("Can't find 'action.yml', 'action.yaml' or 'Dockerfile' under '%s'. Did you forget to run actions/checkout before running your local action?", filepath.Clean(dir))
37		}
38		if err != nil {
39			return nil, err
40		}
41		n.def = def
42		return def, nil
43	}
44	if n.def == nil {
45		return nil, ghErrorf("The action '%s' was not prepared", refString(n.uses))
46	}
47	return n.def, nil
48}
49
50// setActionRepository sets github.action_repository and action_ref
51// (ActionRunner.cs:149-165): the repository and ref of a repository action, empty
52// for anything else.
53func (j *job) setActionRepository(n *node) {
54	repo, ref := "", ""
55	if n != nil && n.uses.Kind == action.UsesRepository {
56		repo, ref = n.uses.Name, n.uses.Ref
57	}
58	j.mu.Lock()
59	j.state.SetGitHub("action_repository", repo)
60	j.state.SetGitHub("action_ref", ref)
61	j.mu.Unlock()
62}
63
64// registerPost registers the post stage of the action (ActionRunner.cs:99-126,
65// ExecutionContext.RegisterPostJobStep): a step "Post <name>" on the stack the
66// job runs after its steps, last registered first; inside a composite action
67// only the fact is recorded, the composite's own post runs the step.
68func (j *job) registerPost(s *stepRun, def *actionDef) {
69	n := s.n
70	name := s.name
71	if s.stage == stagePre && strings.HasPrefix(strings.ToLower(name), "pre ") {
72		name = name[len("Pre "):]
73	}
74	j.debug(s.num, "Register post job cleanup for action: "+registeredRef(n.uses))
75	if s.parent != nil {
76		n.postRegistered, n.postCond = true, def.postIf()
77		return
78	}
79	if n.postRegistered {
80		return // StepsWithPostRegistered
81	}
82	n.postRegistered = true
83	j.mu.Lock()
84	j.postStack = append(j.postStack, qstep{n: n, stage: stagePost, name: "Post " + name, cond: def.postIf()})
85	j.mu.Unlock()
86}
87
88// registeredRef is the text of the debug line of the registration.
89func registeredRef(u action.Uses) string {
90	path := ""
91	if u.Path != "" {
92		path = "/" + u.Path
93	}
94	if u.Ref == "" {
95		return u.Name + path
96	}
97	return u.Name + path + "@" + u.Ref
98}
99
100// handlerGroup is Handler.PrintActionDetails's group name.
101func handlerGroup(u action.Uses) string {
102	switch u.Kind {
103	case action.UsesDocker:
104		return "Run docker://" + u.Image
105	case action.UsesLocal:
106		return "Run " + u.Path
107	}
108	if u.Path == "" {
109		return fmt.Sprintf("Run %s@%s", u.Name, u.Ref)
110	}
111	return fmt.Sprintf("Run %s/%s@%s", u.Name, u.Path, u.Ref)
112}
113
114// printDetails is Handler.PrintActionDetails (Handler.cs:102-165): the group
115// of the step with its inputs that are not empty and its environment. The post
116// stage only prints "Post job cleanup.".
117func (j *job) printDetails(s *stepRun, in *inputSet, env *ordered) {
118	num := s.num
119	if s.stage == stagePost {
120		j.out(num, "Post job cleanup.")
121		return
122	}
123	j.out(num, "##[group]"+handlerGroup(s.n.uses))
124	if in.Len() > 0 {
125		j.out(num, "with:")
126		for _, k := range in.keys {
127			if v, _ := in.Get(k); v != "" {
128				j.out(num, fmt.Sprintf("  %s: %s", k, v))
129			}
130		}
131	}
132	if env.Len() > 0 {
133		j.out(num, "env:")
134		for _, k := range env.Keys() {
135			v, _ := env.Get(k)
136			j.out(num, fmt.Sprintf("  %s: %s", k, v))
137		}
138	}
139	j.out(num, "##[endgroup]")
140}
141
142// runAction is ActionRunner.RunAsync (ActionRunner.cs:62-247) for a uses step
143// in any stage: load the action, evaluate the inputs and the environment, print
144// the details and run the handler of the action.
145func (j *job) runAction(s *stepRun, cancel <-chan struct{}) stepResult {
146	num, n := s.num, s.n
147	fail := func(err error) stepResult { j.errorf(num, "%s", err.Error()); return resFailed }
148	def, err := j.definition(s)
149	if err != nil {
150		return fail(err)
151	}
152	s.def = def
153	local := n.uses.Kind == action.UsesLocal
154	// A local composite action is expanded when it runs (ActionRunner.cs:77-96).
155	if def.kind == kindComposite && s.stage == stageMain && local {
156		if err := j.prepareLocalComposite(s, def, cancel); err != nil {
157			if err == errSetupCancelled {
158				return resCanceled // logged by the command that was stopped
159			}
160			// What the cancellation or the timeout of the step broke (a resolution,
161			// a download, an extraction) is not a failure of the action: the step is
162			// cancelled, and StepsRunner turns it into the timeout message when its
163			// own timer fired. A genuine error keeps failing the step.
164			if done(cancel) {
165				s.logCanceled()
166				return resCanceled
167			}
168			return fail(err)
169		}
170	}
171	if local && n.hasPre() {
172		j.tagged(num, "##[warning]", fmt.Sprintf("`pre` execution is not supported for local action from '%s'", n.uses.Path))
173	}
174	if n.hasPostOf(def) && s.stage != stagePost {
175		j.registerPost(s, def)
176	}
177	j.setActionRepository(n)
178	// ActionRunner.cs:157-164: with a job container, the container must exist.
179	if err := j.containerReady(); err != nil {
180		return fail(err)
181	}
182	if err := j.prepareFileCommands(s); err != nil {
183		return fail(err)
184	}
185	defer s.fc.Process(s)
186
187	j.debug(num, "Loading inputs")
188	in, err := j.evalInputs(s.st, s.env)
189	if err != nil {
190		j.errorf(num, "%s", templateNotValid(err, true))
191		return resFailed
192	}
193	if err := j.mergeInputs(s, def, in); err != nil {
194		j.errorf(num, "%s", templateNotValid(err, true))
195		return resFailed
196	}
197	j.debug(num, "Loading env")
198	env := s.env.Clone()
199	// The intra-action state, applied last (ActionRunner.cs:255-258): names as
200	// first spelled, in the order they were saved.
201	for _, p := range j.intraActionState(n.stateKey) {
202		env.Set("STATE_"+p.Key, p.Value)
203	}
204	s.actEnv = env
205	if err := j.nodeVersionFor(s, def); err != nil {
206		return fail(err)
207	}
208	j.printDetails(s, in, env)
209	switch def.kind {
210	case kindNode:
211		return j.runNode(s, def, in, env, cancel)
212	case kindDocker:
213		return j.runDocker(s, def, in, env, cancel)
214	case kindComposite:
215		return j.runComposite(s, def, in, env)
216	}
217	return fail(ghErrorf("Unsupported action type: %v", def.kind))
218}
219
220// hasPostOf is HasPost of the loaded definition of the node.
221func (n *node) hasPostOf(def *actionDef) bool {
222	switch def.kind {
223	case kindComposite:
224		return len(n.postSteps) > 0
225	case kindScript:
226		return false
227	}
228	if r := def.runs(); r != nil {
229		return r.HasPost()
230	}
231	return false
232}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 4

Tags 11

↑↓ moveenter openesc close