Skip to content

Commit cd70e2a

Browse files
committed
use real OCI layout testdata instead of generated fixtures
Signed-off-by: Jeff Rescignano <jeffr@defenseunicorns.com>
1 parent 2dd5fd4 commit cd70e2a

File tree

11 files changed

+99
-155
lines changed

11 files changed

+99
-155
lines changed

pkg/attestation/crafter/materials/oci_image_test.go

Lines changed: 77 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ import (
2121

2222
contractAPI "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
2323
"github.com/chainloop-dev/chainloop/pkg/attestation/crafter/materials"
24-
"github.com/google/go-containerregistry/pkg/v1/empty"
25-
"github.com/google/go-containerregistry/pkg/v1/layout"
26-
"github.com/google/go-containerregistry/pkg/v1/random"
2724
"github.com/rs/zerolog"
2825
"github.com/stretchr/testify/assert"
2926
"github.com/stretchr/testify/require"
@@ -65,50 +62,40 @@ func TestNewOCIImageCrafter(t *testing.T) {
6562

6663
func TestOCIImageCraft_Layout(t *testing.T) {
6764
testCases := []struct {
68-
name string
69-
setupLayout func(t *testing.T) string
70-
wantErr string
71-
wantDigest string
72-
wantName string
73-
wantTag string
65+
name string
66+
layoutPath string
67+
wantErr string
68+
wantDigest string
69+
wantName string
70+
wantTag string
7471
}{
7572
{
76-
name: "valid OCI layout",
77-
setupLayout: func(t *testing.T) string {
78-
return createTestOCILayout(t, "test-image", "v1.0.0")
79-
},
80-
wantName: "test-image",
81-
wantTag: "v1.0.0",
73+
name: "crane - single image with annotations",
74+
layoutPath: "testdata/oci-layouts/crane",
75+
wantName: "oci-layout",
76+
wantDigest: "sha256:fa6d9058c3d65a33ff565c0e35172f2d99e76fbf8358d91ffaa2208eff2be400",
8277
},
8378
{
84-
name: "OCI layout without annotations",
85-
setupLayout: func(t *testing.T) string {
86-
return createTestOCILayout(t, "", "")
87-
},
88-
wantName: "oci-layout",
89-
wantTag: "",
79+
name: "skopeo - single image with tag annotation",
80+
layoutPath: "testdata/oci-layouts/skopeo",
81+
wantName: "v1.51.0",
82+
wantDigest: "sha256:fa6d9058c3d65a33ff565c0e35172f2d99e76fbf8358d91ffaa2208eff2be400",
9083
},
9184
{
92-
name: "non-existent path",
93-
setupLayout: func(_ *testing.T) string {
94-
return "/non/existent/path"
95-
},
96-
wantErr: "UNAUTHORIZED",
85+
name: "skopeo-alt - alternative format",
86+
layoutPath: "testdata/oci-layouts/skopeo-alt",
87+
wantName: "v1.51.0",
88+
wantDigest: "sha256:a5303ef28a4bd9b6e06aa92c07831dd151ac64172695971226bdba4a11fc1b88",
9789
},
9890
{
99-
name: "empty directory",
100-
setupLayout: func(t *testing.T) string {
101-
dir := t.TempDir()
102-
return dir
103-
},
104-
wantErr: "could not parse reference",
91+
name: "non-existent path",
92+
layoutPath: "/non/existent/path",
93+
wantErr: "UNAUTHORIZED",
10594
},
10695
}
10796

10897
for _, tc := range testCases {
10998
t.Run(tc.name, func(t *testing.T) {
110-
layoutPath := tc.setupLayout(t)
111-
11299
schema := &contractAPI.CraftingSchema_Material{
113100
Name: "test",
114101
Type: contractAPI.CraftingSchema_Material_CONTAINER_IMAGE,
@@ -117,7 +104,7 @@ func TestOCIImageCraft_Layout(t *testing.T) {
117104
crafter, err := materials.NewOCIImageCrafter(schema, nil, &l)
118105
require.NoError(t, err)
119106

120-
got, err := crafter.Craft(context.TODO(), layoutPath)
107+
got, err := crafter.Craft(context.TODO(), tc.layoutPath)
121108
if tc.wantErr != "" {
122109
assert.ErrorContains(t, err, tc.wantErr)
123110
return
@@ -130,63 +117,71 @@ func TestOCIImageCraft_Layout(t *testing.T) {
130117
containerImage := got.GetContainerImage()
131118
require.NotNil(t, containerImage)
132119
assert.Equal(t, tc.wantName, containerImage.Name)
133-
assert.Equal(t, tc.wantTag, containerImage.Tag)
134-
assert.NotEmpty(t, containerImage.Digest)
135-
assert.True(t, len(containerImage.Digest) > 0, "digest should not be empty")
120+
if tc.wantTag != "" {
121+
assert.Equal(t, tc.wantTag, containerImage.Tag)
122+
}
123+
if tc.wantDigest != "" {
124+
assert.Equal(t, tc.wantDigest, containerImage.Digest)
125+
} else {
126+
assert.NotEmpty(t, containerImage.Digest)
127+
}
136128
})
137129
}
138130
}
139131

140132
func TestOCIImageCraft_LayoutWithDigestSelector(t *testing.T) {
141133
testCases := []struct {
142-
name string
143-
setupLayout func(t *testing.T) (string, string) // returns (layoutPath, digestToSelect)
144-
wantErr string
145-
wantName string
146-
wantTag string
134+
name string
135+
layoutPath string
136+
digestSelector string
137+
wantErr string
138+
wantName string
139+
wantDigest string
147140
}{
148141
{
149-
name: "select second image by digest",
150-
setupLayout: func(t *testing.T) (string, string) {
151-
layoutPath, digests := createTestOCILayoutMultiple(t, []imageSpec{
152-
{name: "first-image", tag: "v1.0.0"},
153-
{name: "second-image", tag: "v2.0.0"},
154-
})
155-
return layoutPath, digests[1] // Select second image
156-
},
157-
wantName: "second-image",
158-
wantTag: "v2.0.0",
142+
name: "oras - select first image by digest",
143+
layoutPath: "testdata/oci-layouts/oras",
144+
digestSelector: "sha256:b1747c197a0ab3cb89e109f60a3c5d4ede6946e447fd468fa82d85fa94c6c6e5",
145+
wantName: "oci-layout",
146+
wantDigest: "sha256:b1747c197a0ab3cb89e109f60a3c5d4ede6946e447fd468fa82d85fa94c6c6e5",
159147
},
160148
{
161-
name: "digest not found",
162-
setupLayout: func(t *testing.T) (string, string) {
163-
layoutPath, _ := createTestOCILayoutMultiple(t, []imageSpec{
164-
{name: "test-image", tag: "v1.0.0"},
165-
})
166-
return layoutPath, "sha256:nonexistent"
167-
},
168-
wantErr: "not found in OCI layout",
149+
name: "oras - select second image by digest",
150+
layoutPath: "testdata/oci-layouts/oras",
151+
digestSelector: "sha256:f333056ac987169b2a121c16d06112d88ec3d7cb50b098bb17b0f14b0c52f6f3",
152+
wantName: "oci-layout",
153+
wantDigest: "sha256:f333056ac987169b2a121c16d06112d88ec3d7cb50b098bb17b0f14b0c52f6f3",
169154
},
170155
{
171-
name: "multiple images without digest selector",
172-
setupLayout: func(t *testing.T) (string, string) {
173-
layoutPath, _ := createTestOCILayoutMultiple(t, []imageSpec{
174-
{name: "first-image", tag: "v1.0.0"},
175-
{name: "second-image", tag: "v2.0.0"},
176-
{name: "third-image", tag: "v3.0.0"},
177-
})
178-
return layoutPath, "" // No digest selector
179-
},
180-
wantErr: "contains 3 images, please specify which one",
156+
name: "zarf - select specific image from bundle",
157+
layoutPath: "testdata/oci-layouts/zarf",
158+
digestSelector: "sha256:e8ac056f7b9b44b07935fe23b8383e5e550d479dc5c6261941e76449a8f7e926",
159+
wantName: "ghcr.io/chainloop-dev/chainloop/artifact-cas:v1.51.0",
160+
wantDigest: "sha256:e8ac056f7b9b44b07935fe23b8383e5e550d479dc5c6261941e76449a8f7e926",
161+
},
162+
{
163+
name: "digest not found",
164+
layoutPath: "testdata/oci-layouts/oras",
165+
digestSelector: "sha256:nonexistent",
166+
wantErr: "not found in OCI layout",
167+
},
168+
{
169+
name: "oras - multiple images without digest selector",
170+
layoutPath: "testdata/oci-layouts/oras",
171+
wantErr: "contains 3 images, please specify which one",
172+
},
173+
{
174+
name: "zarf - multiple images without digest selector",
175+
layoutPath: "testdata/oci-layouts/zarf",
176+
wantErr: "contains 3 images, please specify which one",
181177
},
182178
}
183179

184180
for _, tc := range testCases {
185181
t.Run(tc.name, func(t *testing.T) {
186-
layoutPath, digest := tc.setupLayout(t)
187-
imageRef := layoutPath
188-
if digest != "" {
189-
imageRef = layoutPath + "@" + digest
182+
imageRef := tc.layoutPath
183+
if tc.digestSelector != "" {
184+
imageRef = tc.layoutPath + "@" + tc.digestSelector
190185
}
191186

192187
schema := &contractAPI.CraftingSchema_Material{
@@ -209,87 +204,14 @@ func TestOCIImageCraft_LayoutWithDigestSelector(t *testing.T) {
209204
// Check container image fields
210205
containerImage := got.GetContainerImage()
211206
require.NotNil(t, containerImage)
212-
assert.Equal(t, tc.wantName, containerImage.Name)
213-
assert.Equal(t, tc.wantTag, containerImage.Tag)
214-
assert.NotEmpty(t, containerImage.Digest)
215-
})
216-
}
217-
}
218-
219-
type imageSpec struct {
220-
name string
221-
tag string
222-
}
223-
224-
// createTestOCILayoutMultiple creates an OCI layout with multiple images for testing
225-
func createTestOCILayoutMultiple(t *testing.T, specs []imageSpec) (string, []string) {
226-
t.Helper()
227-
228-
layoutPath := t.TempDir()
229-
path, err := layout.Write(layoutPath, empty.Index)
230-
require.NoError(t, err)
231-
232-
digests := make([]string, 0, len(specs))
233-
for _, spec := range specs {
234-
img, err := random.Image(1024, 1)
235-
require.NoError(t, err)
236-
237-
var opts []layout.Option
238-
if spec.name != "" || spec.tag != "" {
239-
annotations := make(map[string]string)
240-
if spec.name != "" {
241-
annotations["org.opencontainers.image.ref.name"] = spec.name
207+
if tc.wantName != "" {
208+
assert.Equal(t, tc.wantName, containerImage.Name)
242209
}
243-
if spec.tag != "" {
244-
annotations["io.containerd.image.name"] = spec.name + ":" + spec.tag
210+
if tc.wantDigest != "" {
211+
assert.Equal(t, tc.wantDigest, containerImage.Digest)
212+
} else {
213+
assert.NotEmpty(t, containerImage.Digest)
245214
}
246-
opts = append(opts, layout.WithAnnotations(annotations))
247-
}
248-
249-
err = path.AppendImage(img, opts...)
250-
require.NoError(t, err)
251-
252-
// Get the digest of the image we just added
253-
index, err := path.ImageIndex()
254-
require.NoError(t, err)
255-
manifest, err := index.IndexManifest()
256-
require.NoError(t, err)
257-
// The last manifest is the one we just added
258-
digests = append(digests, manifest.Manifests[len(manifest.Manifests)-1].Digest.String())
259-
}
260-
261-
return layoutPath, digests
262-
}
263-
264-
// createTestOCILayout creates a minimal valid OCI layout directory for testing
265-
func createTestOCILayout(t *testing.T, imageName, tag string) string {
266-
t.Helper()
267-
268-
layoutPath := t.TempDir()
269-
270-
// Use go-containerregistry to create a random image
271-
img, err := random.Image(1024, 1)
272-
require.NoError(t, err)
273-
274-
// Write layout with empty index first
275-
path, err := layout.Write(layoutPath, empty.Index)
276-
require.NoError(t, err)
277-
278-
// Append the image with annotations if provided
279-
var opts []layout.Option
280-
if imageName != "" || tag != "" {
281-
annotations := make(map[string]string)
282-
if imageName != "" {
283-
annotations["org.opencontainers.image.ref.name"] = imageName
284-
}
285-
if tag != "" {
286-
annotations["io.containerd.image.name"] = imageName + ":" + tag
287-
}
288-
opts = append(opts, layout.WithAnnotations(annotations))
215+
})
289216
}
290-
291-
err = path.AppendImage(img, opts...)
292-
require.NoError(t, err)
293-
294-
return layoutPath
295217
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"schemaVersion": 2,
3+
"mediaType": "application/vnd.oci.image.index.v1+json",
4+
"manifests": [
5+
{
6+
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
7+
"size": 743,
8+
"digest": "sha256:fa6d9058c3d65a33ff565c0e35172f2d99e76fbf8358d91ffaa2208eff2be400"
9+
}
10+
]
11+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"imageLayoutVersion": "1.0.0"
3+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.docker.distribution.manifest.v2+json","digest":"sha256:b1747c197a0ab3cb89e109f60a3c5d4ede6946e447fd468fa82d85fa94c6c6e5","size":1578,"platform":{"architecture":"arm64","os":"linux"}},{"mediaType":"application/vnd.docker.distribution.manifest.v2+json","digest":"sha256:f333056ac987169b2a121c16d06112d88ec3d7cb50b098bb17b0f14b0c52f6f3","size":1578,"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.docker.distribution.manifest.list.v2+json","digest":"sha256:fa6d9058c3d65a33ff565c0e35172f2d99e76fbf8358d91ffaa2208eff2be400","size":743}]}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"imageLayoutVersion":"1.0.0"}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:a5303ef28a4bd9b6e06aa92c07831dd151ac64172695971226bdba4a11fc1b88","size":493,"annotations":{"org.opencontainers.image.ref.name":"v1.51.0"}}]}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"imageLayoutVersion":"1.0.0"}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.docker.distribution.manifest.list.v2+json","digest":"sha256:fa6d9058c3d65a33ff565c0e35172f2d99e76fbf8358d91ffaa2208eff2be400","size":743,"annotations":{"org.opencontainers.image.ref.name":"v1.51.0"}}]}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"imageLayoutVersion":"1.0.0"}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.docker.distribution.manifest.v2+json","digest":"sha256:8cb1d2d12cb680d67998ae917462779ba35ff6969ddc09fc6cdf825889bcd357","size":737,"annotations":{"org.opencontainers.image.base.name":"ghcr.io/chainloop-dev/chainloop/control-plane-migrations:v1.51.0","org.opencontainers.image.ref.name":"ghcr.io/chainloop-dev/chainloop/control-plane-migrations:v1.51.0"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.docker.distribution.manifest.v2+json","digest":"sha256:e8ac056f7b9b44b07935fe23b8383e5e550d479dc5c6261941e76449a8f7e926","size":739,"annotations":{"org.opencontainers.image.base.name":"ghcr.io/chainloop-dev/chainloop/artifact-cas:v1.51.0","org.opencontainers.image.ref.name":"ghcr.io/chainloop-dev/chainloop/artifact-cas:v1.51.0"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.docker.distribution.manifest.v2+json","digest":"sha256:f333056ac987169b2a121c16d06112d88ec3d7cb50b098bb17b0f14b0c52f6f3","size":1578,"annotations":{"org.opencontainers.image.base.name":"ghcr.io/chainloop-dev/chainloop/control-plane:v1.51.0","org.opencontainers.image.ref.name":"ghcr.io/chainloop-dev/chainloop/control-plane:v1.51.0"},"platform":{"architecture":"amd64","os":"linux"}}]}

0 commit comments

Comments
 (0)