github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/internal/testutils/specialimage/multilayer.go (about) 1 package specialimage 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "io" 7 "os" 8 "path/filepath" 9 10 "github.com/containerd/containerd/platforms" 11 "github.com/distribution/reference" 12 "github.com/docker/docker/pkg/archive" 13 "github.com/google/uuid" 14 "github.com/opencontainers/go-digest" 15 "github.com/opencontainers/image-spec/specs-go" 16 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 17 ) 18 19 func MultiLayer(dir string) error { 20 const imageRef = "multilayer:latest" 21 22 layer1Desc, err := writeLayerWithOneFile(dir, "foo", []byte("1")) 23 if err != nil { 24 return err 25 } 26 layer2Desc, err := writeLayerWithOneFile(dir, "bar", []byte("2")) 27 if err != nil { 28 return err 29 } 30 layer3Desc, err := writeLayerWithOneFile(dir, "hello", []byte("world")) 31 if err != nil { 32 return err 33 } 34 35 configDesc, err := writeJsonBlob(dir, ocispec.MediaTypeImageConfig, ocispec.Image{ 36 Platform: platforms.DefaultSpec(), 37 Config: ocispec.ImageConfig{ 38 Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, 39 }, 40 RootFS: ocispec.RootFS{ 41 Type: "layers", 42 DiffIDs: []digest.Digest{layer1Desc.Digest, layer2Desc.Digest, layer3Desc.Digest}, 43 }, 44 }) 45 if err != nil { 46 return err 47 } 48 49 manifest := ocispec.Manifest{ 50 MediaType: ocispec.MediaTypeImageManifest, 51 Config: configDesc, 52 Layers: []ocispec.Descriptor{layer1Desc, layer2Desc, layer3Desc}, 53 } 54 55 legacyManifests := []manifestItem{ 56 manifestItem{ 57 Config: blobPath(configDesc), 58 RepoTags: []string{imageRef}, 59 Layers: []string{blobPath(layer1Desc), blobPath(layer2Desc), blobPath(layer3Desc)}, 60 }, 61 } 62 63 ref, err := reference.ParseNormalizedNamed(imageRef) 64 if err != nil { 65 return err 66 } 67 return singlePlatformImage(dir, ref, manifest, legacyManifests) 68 } 69 70 // Legacy manifest item (manifests.json) 71 type manifestItem struct { 72 Config string 73 RepoTags []string 74 Layers []string 75 } 76 77 func singlePlatformImage(dir string, ref reference.Named, manifest ocispec.Manifest, legacyManifests []manifestItem) error { 78 manifestDesc, err := writeJsonBlob(dir, ocispec.MediaTypeImageManifest, manifest) 79 if err != nil { 80 return err 81 } 82 83 if ref != nil { 84 manifestDesc.Annotations = map[string]string{ 85 "io.containerd.image.name": ref.String(), 86 } 87 88 if tagged, ok := ref.(reference.Tagged); ok { 89 manifestDesc.Annotations[ocispec.AnnotationRefName] = tagged.Tag() 90 } 91 } 92 93 if err := writeJson(ocispec.Index{ 94 Versioned: specs.Versioned{SchemaVersion: 2}, 95 MediaType: ocispec.MediaTypeImageIndex, 96 Manifests: []ocispec.Descriptor{manifestDesc}, 97 }, filepath.Join(dir, "index.json")); err != nil { 98 return err 99 } 100 if err != nil { 101 return err 102 } 103 104 if err := writeJson(legacyManifests, filepath.Join(dir, "manifest.json")); err != nil { 105 return err 106 } 107 if err != nil { 108 return err 109 } 110 111 return os.WriteFile(filepath.Join(dir, "oci-layout"), []byte(`{"imageLayoutVersion": "1.0.0"}`), 0o644) 112 } 113 114 func fileArchive(dir string, name string, content []byte) (io.ReadCloser, error) { 115 tmp, err := os.MkdirTemp("", "") 116 if err != nil { 117 return nil, err 118 } 119 120 if err := os.WriteFile(filepath.Join(tmp, name), content, 0o644); err != nil { 121 return nil, err 122 } 123 124 return archive.Tar(tmp, archive.Uncompressed) 125 } 126 127 func writeLayerWithOneFile(dir string, filename string, content []byte) (ocispec.Descriptor, error) { 128 rd, err := fileArchive(dir, filename, content) 129 if err != nil { 130 return ocispec.Descriptor{}, err 131 } 132 133 return writeBlob(dir, ocispec.MediaTypeImageLayer, rd) 134 } 135 136 func writeJsonBlob(dir string, mt string, obj any) (ocispec.Descriptor, error) { 137 b, err := json.Marshal(obj) 138 if err != nil { 139 return ocispec.Descriptor{}, err 140 } 141 142 return writeBlob(dir, mt, bytes.NewReader(b)) 143 } 144 145 func writeJson(obj any, path string) error { 146 b, err := json.Marshal(obj) 147 if err != nil { 148 return err 149 } 150 151 return os.WriteFile(path, b, 0o644) 152 } 153 154 func writeBlob(dir string, mt string, rd io.Reader) (_ ocispec.Descriptor, outErr error) { 155 digester := digest.Canonical.Digester() 156 hashTee := io.TeeReader(rd, digester.Hash()) 157 158 blobsPath := filepath.Join(dir, "blobs", "sha256") 159 if err := os.MkdirAll(blobsPath, 0o755); err != nil { 160 return ocispec.Descriptor{}, err 161 } 162 163 tmpPath := filepath.Join(blobsPath, uuid.New().String()) 164 file, err := os.Create(tmpPath) 165 if err != nil { 166 return ocispec.Descriptor{}, err 167 } 168 169 defer func() { 170 if outErr != nil { 171 file.Close() 172 os.Remove(tmpPath) 173 } 174 }() 175 176 if _, err := io.Copy(file, hashTee); err != nil { 177 return ocispec.Descriptor{}, err 178 } 179 180 digest := digester.Digest() 181 182 stat, err := os.Stat(tmpPath) 183 if err != nil { 184 return ocispec.Descriptor{}, err 185 } 186 187 file.Close() 188 if err := os.Rename(tmpPath, filepath.Join(blobsPath, digest.Encoded())); err != nil { 189 return ocispec.Descriptor{}, err 190 } 191 192 return ocispec.Descriptor{ 193 MediaType: mt, 194 Digest: digest, 195 Size: stat.Size(), 196 }, nil 197 } 198 199 func blobPath(desc ocispec.Descriptor) string { 200 return "blobs/sha256/" + desc.Digest.Encoded() 201 }