fix/exec-umask 2cd66dc

actions_load.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package executor
4
5import (
6	"fmt"
7	"os"
8	"path/filepath"
9	"strings"
10
11	"bnema.dev/ermite-actions/action"
12)
13
14// loadManifest reads action.yml or action.yaml (the first is preferred) from
15// dir, like ActionManager.LoadAction (ActionManager.cs:722-731) and
16// ActionManifestManagerLegacy.Load (:53-160): every error of the manifest is
17// logged as an error of the step, then the load fails with "Failed to load
18// <path>". shown is the path the messages use: relative to the actions
19// directory for downloaded actions, the whole path for local ones
20// (ActionManifestManagerLegacy.cs:61-66). It returns (nil, "", nil) without a
21// manifest.
22func (j *job) loadManifest(num int, dir string) (*action.Action, string, error) {
23	var file string
24	for _, name := range []string{"action.yml", "action.yaml"} {
25		if _, err := os.Stat(pathCombine(dir, name)); err == nil {
26			file = pathCombine(dir, name)
27			break
28		}
29	}
30	if file == "" {
31		return nil, "", nil
32	}
33	shown := file
34	if base := j.state.Layout().Actions; strings.HasPrefix(file, base+string(filepath.Separator)) {
35		shown = file[len(base)+1:]
36	}
37	data, err := os.ReadFile(file)
38	if err != nil {
39		return nil, "", err
40	}
41	act, errs := action.Parse(shown, data)
42	if len(errs) > 0 {
43		for _, e := range errs {
44			j.errorf(num, "%s", e.Error())
45		}
46		return nil, "", ghErrorf("%s", action.LoadFailed(shown))
47	}
48	return act, shown, nil
49}
50
51func kindOfRuns(r *action.Runs) kind {
52	switch r.Kind {
53	case action.RunsNode:
54		return kindNode
55	case action.RunsDocker:
56		return kindDocker
57	case action.RunsComposite:
58		return kindComposite
59	}
60	return kindScript
61}
62
63// loadDef loads the action in dir (ActionManager.LoadAction): its manifest, or
64// a Dockerfile when there is none (Dockerfile before dockerfile). what names
65// the action in the error for a directory without any.
66func (j *job) loadDef(num int, dir string) (*actionDef, error) {
67	act, _, err := j.loadManifest(num, dir)
68	if err != nil {
69		return nil, err
70	}
71	if act != nil {
72		d := &actionDef{kind: kindOfRuns(act.Runs), dir: dir, act: act}
73		switch act.Runs.Kind {
74		case action.RunsDocker:
75			d.image = act.Runs.Image
76		case action.RunsPlugin:
77			return nil, ghErrorf("Unsupported action type: Plugin")
78		}
79		return d, nil
80	}
81	for _, name := range []string{"Dockerfile", "dockerfile"} {
82		if _, err := os.Stat(pathCombine(dir, name)); err == nil {
83			return &actionDef{kind: kindDocker, dir: dir, dockerfile: pathCombine(dir, name)}, nil
84		}
85	}
86	return nil, errNoAction
87}
88
89// errNoAction is the sentinel of a directory without manifest and Dockerfile.
90var errNoAction = fmt.Errorf("no action")
91
92// actionDir is where the action of a node lives: the workspace for a local
93// action, the download directory for a repository one (ActionManager.cs:665-694).
94func (j *job) actionDir(n *node) string {
95	switch n.uses.Kind {
96	case action.UsesLocal:
97		return pathCombine(j.state.GitHub("workspace"), filepath.FromSlash(strings.ReplaceAll(n.uses.Path, `\`, "/")))
98	case action.UsesRepository:
99		d := pathCombine(j.state.Layout().Actions, filepath.FromSlash(n.uses.Name), n.uses.Ref)
100		if n.uses.Path != "" {
101			d = pathCombine(d, filepath.FromSlash(n.uses.Path))
102		}
103		return d
104	}
105	return ""
106}
107
108// refString is "Name[/Path]@Ref" (ActionRunner.cs:119-121, Handler.cs:130-148).
109func refString(u action.Uses) string {
110	switch u.Kind {
111	case action.UsesRepository:
112		p := u.Name
113		if u.Path != "" {
114			p += "/" + u.Path
115		}
116		return p + "@" + u.Ref
117	case action.UsesLocal:
118		return u.Path
119	case action.UsesDocker:
120		return "docker://" + u.Image
121	}
122	return ""
123}
124
125// pathCombine is .NET's Path.Combine: the parts are joined with a separator and
126// nothing is cleaned, so a "./" in the path of a local action stays in the
127// messages and in github.action_path (recorded: ".../work/repo/repo/./.github/
128// actions/basic"); a rooted part replaces what precedes it (an absolute `main`
129// is that file). Paths are not confined to the action directory, as in the
130// runner: ../ and symlinks may leave it.
131func pathCombine(parts ...string) string {
132	out := ""
133	for _, p := range parts {
134		switch {
135		case p == "":
136		case out == "" || filepath.IsAbs(p):
137			out = p
138		case strings.HasSuffix(out, string(filepath.Separator)):
139			out += p
140		default:
141			out += string(filepath.Separator) + p
142		}
143	}
144	return out
145}

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