fix/exec-umask 2cd66dc

guestforge_test.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package host
4
5import (
6	"encoding/json"
7	"strings"
8	"testing"
9
10	ermite "bnema.dev/ermite-api/gen/ermite"
11
12	"bnema.dev/ermite-runner/internal/execstream"
13	"bnema.dev/ermite-runner/internal/execstream/execpb"
14)
15
16const publicContexts = `{"github":{"job":"","ref":"refs/heads/main","repository":"octo/hello","repositoryUrl":"git://ermite.bnema.dev/octo/hello.git",` +
17	`"server_url":"https://ermite.bnema.dev","api_url":"https://ermite.bnema.dev/-/github/api","graphql_url":"https://ermite.bnema.dev/-/github/api/graphql",` +
18	`"event":{"compare":"https://ermite.bnema.dev/octo/hello/compare/a...b","repository":{"html_url":"https://ermite.bnema.dev/octo/hello"}},` +
19	`"note":"a & <b>"},"needs":{},"vars":{"U":"https://ermite.bnema.dev/x"}}`
20
21func TestRewriteServerURLs(t *testing.T) {
22	got, err := RewriteServerURLs(publicContexts, "http://ermite.bnema.dev:8088")
23	if err != nil {
24		t.Fatal(err)
25	}
26	want := strings.NewReplacer(
27		`"server_url":"https://ermite.bnema.dev"`, `"server_url":"http://ermite.bnema.dev:8088"`,
28		`"api_url":"https://ermite.bnema.dev/-/github/api"`, `"api_url":"http://ermite.bnema.dev:8088/-/github/api"`,
29		`"graphql_url":"https://ermite.bnema.dev/-/github/api/graphql"`, `"graphql_url":"http://ermite.bnema.dev:8088/-/github/api/graphql"`,
30	).Replace(publicContexts)
31	// Only the three fields change: same bytes everywhere else (key order, the
32	// event payload as recorded, other URLs, escaping).
33	if got != want {
34		t.Fatalf("got\n%s\nwant\n%s", got, want)
35	}
36	if !json.Valid([]byte(got)) {
37		t.Fatal("invalid JSON")
38	}
39}
40
41func TestRewriteServerURLsMatchesOriginsOnly(t *testing.T) {
42	const target = "http://forge.internal:8088"
43	cases := map[string]struct {
44		in   string
45		same bool
46	}{
47		"other origin is untouched":   {`{"github":{"server_url":"https://ermite.bnema.dev","api_url":"https://api.example.org/x","graphql_url":"https://ermite.bnema.dev:8443/g"}}`, false},
48		"host prefix is not a match":  {`{"github":{"server_url":"https://ermite.bnema.dev","api_url":"https://ermite.bnema.dev.evil.test/x","graphql_url":"https://xermite.bnema.dev/g"}}`, false},
49		"no server_url":               {`{"github":{"api_url":"https://ermite.bnema.dev/-/github/api"}}`, true},
50		"no github":                   {`{"vars":{"a":"b"}}`, true},
51		"empty server_url":            {`{"github":{"server_url":"","api_url":"https://ermite.bnema.dev/a"}}`, true},
52		"non-string values":           {`{"github":{"server_url":"https://ermite.bnema.dev","api_url":null,"graphql_url":3}}`, false},
53		"already the target":          {`{"github":{"server_url":"http://forge.internal:8088","api_url":"http://forge.internal:8088/a"}}`, true},
54		"credentials are not origins": {`{"github":{"server_url":"https://ermite.bnema.dev","api_url":"https://u:p@ermite.bnema.dev/a"}}`, false},
55	}
56	for name, c := range cases {
57		got, err := RewriteServerURLs(c.in, target)
58		if err != nil {
59			t.Fatalf("%s: %v", name, err)
60		}
61		if c.same && got != c.in {
62			t.Errorf("%s: changed:\n%s", name, got)
63		}
64		var v struct {
65			GitHub map[string]any `json:"github"`
66		}
67		if err := json.Unmarshal([]byte(got), &v); err != nil {
68			t.Fatalf("%s: %v", name, err)
69		}
70		switch name {
71		case "other origin is untouched", "host prefix is not a match", "credentials are not origins":
72			if v.GitHub["server_url"] != target {
73				t.Errorf("%s: server_url %v", name, v.GitHub["server_url"])
74			}
75			if u, _ := v.GitHub["api_url"].(string); strings.HasPrefix(u, target) {
76				t.Errorf("%s: api_url rewritten: %s", name, u)
77			}
78			if u, _ := v.GitHub["graphql_url"].(string); strings.HasPrefix(u, target) {
79				t.Errorf("%s: graphql_url rewritten: %s", name, u)
80			}
81		case "non-string values":
82			if v.GitHub["server_url"] != target || v.GitHub["api_url"] != nil || v.GitHub["graphql_url"] != float64(3) {
83				t.Errorf("%s: %v", name, v.GitHub)
84			}
85		}
86	}
87}
88
89func TestRewriteServerURLsSpellings(t *testing.T) {
90	// Case of scheme and host and a default port do not change the origin.
91	in := `{"github":{"server_url":"HTTPS://Ermite.BNEMA.dev:443/","api_url":"https://ermite.bnema.dev/-/github/api?x=1#f"}}`
92	got, err := RewriteServerURLs(in, "https://forge.test")
93	if err != nil {
94		t.Fatal(err)
95	}
96	if got != `{"github":{"server_url":"https://forge.test/","api_url":"https://forge.test/-/github/api?x=1#f"}}` {
97		t.Fatal(got)
98	}
99	// Off: the contexts are returned as received, valid or not.
100	if got, err := RewriteServerURLs("not json", ""); err != nil || got != "not json" {
101		t.Fatalf("%q %v", got, err)
102	}
103	for _, bad := range []string{"not json", `[1]`, `{"github":[1]}`, `{"github":{}} x`} {
104		if _, err := RewriteServerURLs(bad, "http://a.test"); err == nil {
105			t.Errorf("%q accepted", bad)
106		}
107	}
108}
109
110func TestOriginOf(t *testing.T) {
111	for in, want := range map[string]string{
112		"https://Ermite.example/x": "https://ermite.example", "http://a.test:80": "http://a.test",
113		"http://a.test:8088/p": "http://a.test:8088", "https://a.test:443": "https://a.test",
114		"ftp://a.test": "", "a.test": "", "https://u@a.test": "", "": "",
115		// GO-012: an IPv6 host stays in brackets, and the host "::1:8443" is not the
116		// host "::1" on port 8443.
117		"https://[::1]:8443/x": "https://[::1]:8443", "https://[::1:8443]/x": "https://[::1:8443]", "https://[::1]:443/": "https://[::1]",
118		"http://[2001:DB8::1]:80": "http://[2001:db8::1]",
119	} {
120		if got := originOf(in); got != want {
121			t.Errorf("originOf(%q) = %q, want %q", in, got, want)
122		}
123	}
124}
125
126// The executor receives the rewritten contexts when the access is on, and the
127// recorded ones when it is off.
128func TestJobContextsAreRewrittenOnlyWithGuestForge(t *testing.T) {
129	for _, on := range []bool{true, false} {
130		h := newHarness(t, simpleScript("x"))
131		job := testJob("j1")
132		job.Contexts = publicContexts
133		h.srv.jobs = []*ermite.Job{job}
134		if on {
135			h.runner.Cfg.GuestForgeOrigin = "http://ermite.bnema.dev:8088"
136		}
137		var seen string
138		inner := h.driver.script
139		h.driver.script = func(t *testing.T, c *execstream.Conn, run *execpb.Run, cn <-chan *execpb.Cancel) {
140			seen = run.Job.Contexts
141			inner(t, c, run, cn)
142		}
143		h.run(t)
144		eventually(t, "CompleteJob", func() bool { return h.srv.completeCount() == 1 })
145		if got := strings.Contains(seen, `"server_url":"http://ermite.bnema.dev:8088"`); got != on {
146			t.Errorf("guest forge %v: contexts %s", on, seen)
147		}
148		if !on && seen != publicContexts {
149			t.Errorf("contexts changed while off: %s", seen)
150		}
151	}
152}
153
154// GO-012: guest forge access on an IPv6 address. The URLs of the contexts are
155// rewritten to the runner's origin, the brackets and the port kept, and the
156// origin of the other host with the look-alike spelling is not touched.
157func TestRewriteServerURLsIPv6(t *testing.T) {
158	in := `{"github":{"server_url":"https://[::1]:8443","api_url":"https://[::1]:8443/-/github/api","graphql_url":"https://[::1:8443]/x"}}`
159	got, err := RewriteServerURLs(in, "http://[fd00::2]:8088")
160	if err != nil {
161		t.Fatal(err)
162	}
163	want := `{"github":{"server_url":"http://[fd00::2]:8088","api_url":"http://[fd00::2]:8088/-/github/api","graphql_url":"https://[::1:8443]/x"}}`
164	if got != want {
165		t.Errorf("got  %s\nwant %s", got, want)
166	}
167}

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