main 33a243c

registry_test.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package e2e
4
5import (
6	"bytes"
7	"encoding/json"
8	"net/http"
9	"strings"
10	"sync"
11	"testing"
12	"time"
13
14	"github.com/bnema/zerowrap"
15
16	"bnema.dev/ermite-runner/internal/cachesvc"
17	"bnema.dev/ermite-runner/internal/execstream/execpb"
18	"bnema.dev/ermite-runner/internal/registryproxy"
19)
20
21type registryLogs struct {
22	mu sync.Mutex
23	b  bytes.Buffer
24}
25
26func (b *registryLogs) Write(p []byte) (int, error) {
27	b.mu.Lock()
28	defer b.mu.Unlock()
29	return b.b.Write(p)
30}
31func (b *registryLogs) text() string { b.mu.Lock(); defer b.mu.Unlock(); return b.b.String() }
32func (b *registryLogs) reset()       { b.mu.Lock(); defer b.mu.Unlock(); b.b.Reset() }
33
34func TestDockerHubRegistryAcrossJobs(t *testing.T) {
35	image := needE2E(t)
36	cl := &http.Client{Timeout: 10 * time.Second}
37	resp, err := cl.Get("https://registry-1.docker.io/v2/")
38	if err != nil {
39		t.Skipf("Docker Hub unreachable: %v", err)
40	}
41	_ = resp.Body.Close()
42	if resp.StatusCode != http.StatusUnauthorized && resp.StatusCode != http.StatusOK {
43		t.Skipf("Docker Hub unavailable: HTTP %d", resp.StatusCode)
44	}
45	var logs registryLogs
46	p, err := registryproxy.New(registryproxy.Config{Dir: t.TempDir(), Max: 256 << 20, Log: zerowrap.New(zerowrap.Config{Level: "debug", Format: "json", Output: &logs})})
47	if err != nil {
48		t.Fatal(err)
49	}
50	svc, err := cachesvc.New(cachesvc.Config{Dir: t.TempDir(), Registry: p})
51	if err != nil {
52		t.Fatal(err)
53	}
54	defer func() { _ = svc.CloseAll() }()
55	d := newCacheDriver(t, image, svc)
56	probe := runJob(t, d, jobSpec(`on: push
57jobs:
58  j:
59    runs-on: x
60    steps:
61      - run: if docker info >/dev/null 2>&1; then echo nested-dockerd-ready; else echo nested-dockerd-unavailable; fi
62`), nil)
63	if !strings.Contains(strings.Join(probe.Logs, "\n"), "nested-dockerd-ready") {
64		t.Skip("nested dockerd unavailable in sandbox; rootless Docker harness cannot run job containers")
65	}
66	for i := 0; i < 2; i++ {
67		logs.reset()
68		spec := jobSpec(`on: push
69jobs:
70  j:
71    runs-on: x
72    container: alpine:3
73    steps:
74      - run: echo alpine-job
75`)
76		// A registry-only job: the host sends the port and no token.
77		spec.CachePort = cachesvc.DefaultPort
78		s := runJob(t, d, spec, nil)
79		if s.Result == nil || s.Result.Conclusion != execpb.StepConclusion_STEP_CONCLUSION_SUCCESS {
80			t.Fatalf("pull job %d: %v\n%s", i, s.Result, strings.Join(s.Logs, "\n"))
81		}
82		all := logs.text()
83		manifestHit := false
84		for _, line := range strings.Split(all, "\n") {
85			var event struct {
86				Message string `json:"message"`
87				Hit     bool   `json:"hit"`
88			}
89			if json.Unmarshal([]byte(line), &event) == nil && event.Message == "registry manifest" && event.Hit {
90				manifestHit = true
91			}
92		}
93		if i == 1 && !manifestHit {
94			t.Fatalf("second job missing manifest hit:\n%s", all)
95		}
96		t.Logf("pull job %d proxy logs:\n%s", i+1, all)
97		if i == 1 && strings.Contains(all, `"message":"registry blob"`) {
98			t.Fatalf("second job fetched upstream blobs:\n%s", all)
99		}
100	}
101	// The mirror argv is unconditional: a job without the runtime channel
102	// must still pull directly from Docker Hub.
103	off := runJob(t, d, jobSpec(`on: push
104jobs:
105  j:
106    runs-on: x
107    container: alpine:3
108    steps:
109      - run: echo direct-pull
110`), nil)
111	if off.Result == nil || off.Result.Conclusion != execpb.StepConclusion_STEP_CONCLUSION_SUCCESS {
112		t.Fatalf("proxy-off pull: %v\n%s", off.Result, strings.Join(off.Logs, "\n"))
113	}
114}

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