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