v3.3.0 b164fe9

resolver_test.go RawHistoryFolder

1package imageref
2
3import (
4	"context"
5	"errors"
6	"testing"
7
8	"github.com/stretchr/testify/assert"
9	"github.com/stretchr/testify/require"
10
11	"github.com/bnema/gordon/internal/domain"
12)
13
14type stubManifests struct {
15	raw map[string][]byte
16	err error
17}
18
19func (f *stubManifests) GetManifest(name, reference string) ([]byte, string, error) {
20	if f.err != nil {
21		return nil, "", f.err
22	}
23	raw, ok := f.raw[name+":"+reference]
24	if !ok {
25		return nil, "", errors.New("missing")
26	}
27	return raw, "application/vnd.oci.image.manifest.v1+json", nil
28}
29
30func TestResolver_PinnedPassesThrough(t *testing.T) {
31	r := NewResolver("reg.example.com", &stubManifests{}, nil)
32	valid := "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
33	got, err := r.ResolveDigest(context.Background(), "img@"+valid)
34	require.NoError(t, err)
35	assert.Equal(t, valid, got)
36
37	_, err = r.ResolveDigest(context.Background(), "img@sha256:short")
38	require.ErrorIs(t, err, domain.ErrAppImageUnresolvable)
39}
40
41func TestResolver_LocalManifestHashes(t *testing.T) {
42	r := NewResolver("reg.example.com", &stubManifests{raw: map[string][]byte{"blog/web:1.0": []byte("{}")}}, nil)
43	got, err := r.ResolveDigest(context.Background(), "reg.example.com/blog/web:1.0")
44	require.NoError(t, err)
45	assert.True(t, len(got) == 7+64)
46
47	_, err = r.ResolveDigest(context.Background(), "reg.example.com/blog/missing:1.0")
48	require.ErrorIs(t, err, domain.ErrAppImageUnresolvable)
49}
50
51func TestResolver_ExternalNeedsRemote(t *testing.T) {
52	r := NewResolver("reg.example.com", &stubManifests{}, nil)
53	_, err := r.ResolveDigest(context.Background(), "docker.io/library/nginx:1.0")
54	require.ErrorIs(t, err, domain.ErrAppImageUnresolvable)
55}
56
57func TestResolver_RejectsMalformedRemoteDigest(t *testing.T) {
58	r := NewResolver("reg.example.com", &stubManifests{}, &recordingRemote{digest: "sha256:not-a-digest"})
59	_, err := r.ResolveDigest(context.Background(), "docker.io/library/nginx:1.0")
60	require.ErrorIs(t, err, domain.ErrAppImageUnresolvable)
61}
62
63// recordingRemote records whether the connector was consulted.
64type recordingRemote struct {
65	called bool
66	digest string
67}
68
69func (r *recordingRemote) ResolveDigest(context.Context, string) (string, error) {
70	r.called = true
71	if r.digest != "" {
72		return r.digest, nil
73	}
74	return "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", nil
75}
76
77// TestResolver_RejectsDisallowedRefsWithoutConnecting proves policy is
78// enforced before the remote connector is consulted, including for
79// already-pinned digest references.
80func TestResolver_RejectsDisallowedRefsWithoutConnecting(t *testing.T) {
81	const digest = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
82	remote := &recordingRemote{}
83	r := NewResolver("reg.example.com", &stubManifests{}, remote).WithPolicy(domain.ImageSourcePolicy{
84		InstallationRegistry: "reg.example.com",
85		AllowedRegistries:    []string{"registry.example.com"},
86	})
87
88	_, err := r.ResolveDigest(context.Background(), "127.0.0.1:12345/private@"+digest)
89	require.ErrorIs(t, err, domain.ErrAppImageNotAllowed)
90
91	_, err = r.ResolveDigest(context.Background(), "other.example.com/team/app@"+digest)
92	require.ErrorIs(t, err, domain.ErrAppImageNotAllowed)
93
94	_, err = r.ResolveDigest(context.Background(), "other.example.com/team/app:1.0")
95	require.ErrorIs(t, err, domain.ErrAppImageNotAllowed)
96
97	assert.False(t, remote.called, "policy rejection must happen before connecting")
98}
99
100func TestResolver_AllowsInstallationAndAllowlistedRefs(t *testing.T) {
101	const digest = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
102	manifests := &stubManifests{raw: map[string][]byte{"blog/web:1.0": []byte("{}")}}
103	remote := &recordingRemote{}
104	r := NewResolver("reg.example.com", manifests, remote).WithPolicy(domain.ImageSourcePolicy{
105		InstallationRegistry: "reg.example.com",
106		AllowedRegistries:    []string{"registry.example.com"},
107	})
108
109	got, err := r.ResolveDigest(context.Background(), "reg.example.com/blog/web:1.0")
110	require.NoError(t, err)
111	assert.True(t, len(got) == 7+64)
112
113	got, err = r.ResolveDigest(context.Background(), "registry.example.com/team/app@"+digest)
114	require.NoError(t, err)
115	assert.Equal(t, digest, got)
116	assert.False(t, remote.called, "digest refs resolve without contacting the connector")
117}
118
119// TestResolver_RequireDigestRejectsTags proves the require-digest policy is
120// enforced on the manifest reference before it is resolved.
121func TestResolver_RequireDigestRejectsTags(t *testing.T) {
122	const digest = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
123	r := NewResolver("reg.example.com", &stubManifests{}, &recordingRemote{}).WithPolicy(domain.ImageSourcePolicy{
124		AllowedRegistries:    []string{"registry.example.com"},
125		RequireDigest:        true,
126		InstallationRegistry: "reg.example.com",
127	})
128
129	_, err := r.ResolveDigest(context.Background(), "registry.example.com/team/app:1.0")
130	require.ErrorIs(t, err, domain.ErrAppImageNotAllowed)
131
132	got, err := r.ResolveDigest(context.Background(), "registry.example.com/team/app@"+digest)
133	require.NoError(t, err)
134	assert.Equal(t, digest, got)
135}

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 7

Tags 106

↑↓ moveenter openesc close