v0.4.4 edcdd4c

actions_artifacts.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package executor
4
5import (
6	"bytes"
7	"io"
8	"os"
9	"strings"
10
11	"bnema.dev/ermite-actions/action"
12)
13
14// @actions/artifact v2 (upload-artifact and download-artifact v4 and later)
15// refuses to run when it believes the server is GitHub Enterprise Server:
16// isGhes() (toolkit packages/artifact/src/internal/shared/config.ts) reads
17// GITHUB_SERVER_URL only, and any host that is not github.com, *.ghe.com or
18// *.localhost is GHES. ermite's server URL is its own, so the node steps that
19// run that client get a server URL the client takes for GitHub, in the
20// environment of their process only. It is the one place where ermite changes
21// the environment of an action.
22const (
23	// artifactsServerURL is the GITHUB_SERVER_URL those steps get. isGhes()
24	// treats *.localhost as GitHub; the host is never resolved nor connected to
25	// (the client talks to ACTIONS_RESULTS_URL only).
26	artifactsServerURL = "https://ermite.ghe.localhost"
27	// ghesMessage is the error @actions/artifact v2 throws on GHES
28	// (packages/artifact/src/internal/shared/errors.ts:32): a script that holds
29	// it bundles the client.
30	ghesMessage = "@actions/artifact v2.0.0+, upload-artifact@v4+ and download-artifact@v4+ are not currently supported on GHES."
31	// sniffChunk is the size of the reads of the sniff.
32	sniffChunk = 64 << 10
33)
34
35// artifactRepos are the repository actions that use @actions/artifact v2, any
36// path inside them included (actions/upload-artifact/merge).
37var artifactRepos = []string{"actions/upload-artifact", "actions/download-artifact"}
38
39// setArtifactsServerURL decides the GITHUB_SERVER_URL of the process of a node
40// action step that runs script (main, pre or post): artifactsServerURL when the
41// job has the results service and the step is an artifact action or its script
42// bundles @actions/artifact v2, else the job's own value.
43func (j *job) setArtifactsServerURL(s *stepRun, script string) {
44	s.serverURL = ""
45	if !j.cfg.Results {
46		return
47	}
48	if !isArtifactAction(s.n) && !j.bundlesArtifactClient(script) {
49		return
50	}
51	s.serverURL = artifactsServerURL
52	j.debug(s.num, "GITHUB_SERVER_URL set to "+artifactsServerURL+" for the artifacts service")
53}
54
55// isArtifactAction reports whether the step uses one of artifactRepos, in any
56// case.
57func isArtifactAction(n *node) bool {
58	if n == nil || n.uses.Kind != action.UsesRepository {
59		return false
60	}
61	name := n.uses.Owner + "/" + n.uses.Repo
62	for _, r := range artifactRepos {
63		if strings.EqualFold(name, r) {
64			return true
65		}
66	}
67	return false
68}
69
70// bundlesArtifactClient reports whether the file holds ghesMessage. The answer
71// is kept per path for the job: the pre, main and post stages and other uses of
72// the action read it once. A file that cannot be read does not hold it.
73func (j *job) bundlesArtifactClient(script string) bool {
74	j.mu.Lock()
75	hit, ok := j.ghesSniff[script]
76	j.mu.Unlock()
77	if ok {
78		return hit
79	}
80	hit = fileContains(script, []byte(ghesMessage))
81	j.mu.Lock()
82	if j.ghesSniff == nil {
83		j.ghesSniff = map[string]bool{}
84	}
85	j.ghesSniff[script] = hit
86	j.mu.Unlock()
87	return hit
88}
89
90// fileContains reads the file in chunks and reports whether it holds needle;
91// len(needle)-1 bytes of a chunk are kept for the next, so a match across two
92// reads is found. A read error is a miss.
93func fileContains(name string, needle []byte) bool {
94	if len(needle) == 0 {
95		return true
96	}
97	f, err := os.Open(name)
98	if err != nil {
99		return false
100	}
101	defer func() { _ = f.Close() }()
102	keep := len(needle) - 1
103	buf := make([]byte, keep+sniffChunk)
104	have := 0
105	for {
106		n, err := f.Read(buf[have:])
107		have += n
108		if bytes.Contains(buf[:have], needle) {
109			return true
110		}
111		if err == io.EOF {
112			return false
113		}
114		if err != nil {
115			return false
116		}
117		if have > keep {
118			have = copy(buf, buf[have-keep:have])
119		}
120	}
121}

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