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  }