1package ocischema
2
3import (
4	"context"
5	"reflect"
6	"testing"
7
8	"github.com/docker/distribution"
9	"github.com/opencontainers/go-digest"
10	"github.com/opencontainers/image-spec/specs-go/v1"
11)
12
13type mockBlobService struct {
14	descriptors map[digest.Digest]distribution.Descriptor
15}
16
17func (bs *mockBlobService) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
18	if descriptor, ok := bs.descriptors[dgst]; ok {
19		return descriptor, nil
20	}
21	return distribution.Descriptor{}, distribution.ErrBlobUnknown
22}
23
24func (bs *mockBlobService) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) {
25	panic("not implemented")
26}
27
28func (bs *mockBlobService) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) {
29	panic("not implemented")
30}
31
32func (bs *mockBlobService) Put(ctx context.Context, mediaType string, p []byte) (distribution.Descriptor, error) {
33	d := distribution.Descriptor{
34		Digest:    digest.FromBytes(p),
35		Size:      int64(len(p)),
36		MediaType: "application/octet-stream",
37	}
38	bs.descriptors[d.Digest] = d
39	return d, nil
40}
41
42func (bs *mockBlobService) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) {
43	panic("not implemented")
44}
45
46func (bs *mockBlobService) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) {
47	panic("not implemented")
48}
49
50func TestBuilder(t *testing.T) {
51	imgJSON := []byte(`{
52    "created": "2015-10-31T22:22:56.015925234Z",
53    "author": "Alyssa P. Hacker <alyspdev@example.com>",
54    "architecture": "amd64",
55    "os": "linux",
56    "config": {
57        "User": "alice",
58        "ExposedPorts": {
59            "8080/tcp": {}
60        },
61        "Env": [
62            "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
63            "FOO=oci_is_a",
64            "BAR=well_written_spec"
65        ],
66        "Entrypoint": [
67            "/bin/my-app-binary"
68        ],
69        "Cmd": [
70            "--foreground",
71            "--config",
72            "/etc/my-app.d/default.cfg"
73        ],
74        "Volumes": {
75            "/var/job-result-data": {},
76            "/var/log/my-app-logs": {}
77        },
78        "WorkingDir": "/home/alice",
79        "Labels": {
80            "com.example.project.git.url": "https://example.com/project.git",
81            "com.example.project.git.commit": "45a939b2999782a3f005621a8d0f29aa387e1d6b"
82        }
83    },
84    "rootfs": {
85      "diff_ids": [
86        "sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1",
87        "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
88      ],
89      "type": "layers"
90    },
91    "annotations": {
92       "hot": "potato"
93    }
94    "history": [
95      {
96        "created": "2015-10-31T22:22:54.690851953Z",
97        "created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /"
98      },
99      {
100        "created": "2015-10-31T22:22:55.613815829Z",
101        "created_by": "/bin/sh -c #(nop) CMD [\"sh\"]",
102        "empty_layer": true
103      }
104    ]
105}`)
106	configDigest := digest.FromBytes(imgJSON)
107
108	descriptors := []distribution.Descriptor{
109		{
110			Digest:      digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"),
111			Size:        5312,
112			MediaType:   v1.MediaTypeImageLayerGzip,
113			Annotations: map[string]string{"apple": "orange", "lettuce": "wrap"},
114		},
115		{
116			Digest:    digest.Digest("sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa"),
117			Size:      235231,
118			MediaType: v1.MediaTypeImageLayerGzip,
119		},
120		{
121			Digest:    digest.Digest("sha256:b4ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"),
122			Size:      639152,
123			MediaType: v1.MediaTypeImageLayerGzip,
124		},
125	}
126	annotations := map[string]string{"hot": "potato"}
127
128	bs := &mockBlobService{descriptors: make(map[digest.Digest]distribution.Descriptor)}
129	builder := NewManifestBuilder(bs, imgJSON, annotations)
130
131	for _, d := range descriptors {
132		if err := builder.AppendReference(d); err != nil {
133			t.Fatalf("AppendReference returned error: %v", err)
134		}
135	}
136
137	built, err := builder.Build(context.Background())
138	if err != nil {
139		t.Fatalf("Build returned error: %v", err)
140	}
141
142	// Check that the config was put in the blob store
143	_, err = bs.Stat(context.Background(), configDigest)
144	if err != nil {
145		t.Fatal("config was not put in the blob store")
146	}
147
148	manifest := built.(*DeserializedManifest).Manifest
149	if manifest.Annotations["hot"] != "potato" {
150		t.Fatalf("unexpected annotation in manifest: %s", manifest.Annotations["hot"])
151	}
152
153	if manifest.Versioned.SchemaVersion != 2 {
154		t.Fatal("SchemaVersion != 2")
155	}
156
157	target := manifest.Target()
158	if target.Digest != configDigest {
159		t.Fatalf("unexpected digest in target: %s", target.Digest.String())
160	}
161	if target.MediaType != v1.MediaTypeImageConfig {
162		t.Fatalf("unexpected media type in target: %s", target.MediaType)
163	}
164	if target.Size != 1632 {
165		t.Fatalf("unexpected size in target: %d", target.Size)
166	}
167
168	references := manifest.References()
169	expected := append([]distribution.Descriptor{manifest.Target()}, descriptors...)
170	if !reflect.DeepEqual(references, expected) {
171		t.Fatal("References() does not match the descriptors added")
172	}
173}
174