|
| 1 | +package add |
| 2 | + |
| 3 | +import ( |
| 4 | + "compress/gzip" |
| 5 | + "context" |
| 6 | + "encoding/json" |
| 7 | + "fmt" |
| 8 | + "io" |
| 9 | + "runtime" |
| 10 | + "time" |
| 11 | + |
| 12 | + "github.com/docker/distribution" |
| 13 | + "github.com/docker/distribution/manifest/schema2" |
| 14 | + digest "github.com/opencontainers/go-digest" |
| 15 | + |
| 16 | + "github.com/openshift/origin/pkg/image/apis/image/docker10" |
| 17 | + "github.com/openshift/origin/pkg/image/dockerlayer" |
| 18 | +) |
| 19 | + |
| 20 | +// get base manifest |
| 21 | +// check that I can access base layers |
| 22 | +// find the input file (assume I can stream) |
| 23 | +// start a streaming upload of the layer to the remote registry, while calculating digests |
| 24 | +// get back the final digest |
| 25 | +// build the new image manifest and config.json |
| 26 | +// upload config.json |
| 27 | +// upload the rest of the layers |
| 28 | +// tag the image |
| 29 | + |
| 30 | +const ( |
| 31 | + // dockerV2Schema2LayerMediaType is the MIME type used for schema 2 layers. |
| 32 | + dockerV2Schema2LayerMediaType = "application/vnd.docker.image.rootfs.diff.tar.gzip" |
| 33 | + // dockerV2Schema2ConfigMediaType is the MIME type used for schema 2 config blobs. |
| 34 | + dockerV2Schema2ConfigMediaType = "application/vnd.docker.container.image.v1+json" |
| 35 | +) |
| 36 | + |
| 37 | +// DigestCopy reads all of src into dst, where src is a gzipped stream. It will return the |
| 38 | +// sha256 sum of the underlying content (the layerDigest) and the sha256 sum of the |
| 39 | +// tar archive (the blobDigest) or an error. If the gzip layer has a modification time |
| 40 | +// it will be returned. |
| 41 | +// TODO: use configurable digests |
| 42 | +func DigestCopy(dst io.ReaderFrom, src io.Reader) (layerDigest, blobDigest digest.Digest, modTime *time.Time, size int64, err error) { |
| 43 | + algo := digest.Canonical |
| 44 | + // calculate the blob digest as the sha256 sum of the uploaded contents |
| 45 | + blobhash := algo.Hash() |
| 46 | + // calculate the diffID as the sha256 sum of the layer contents |
| 47 | + pr, pw := io.Pipe() |
| 48 | + layerhash := algo.Hash() |
| 49 | + ch := make(chan error) |
| 50 | + go func() { |
| 51 | + defer close(ch) |
| 52 | + gr, err := gzip.NewReader(pr) |
| 53 | + if err != nil { |
| 54 | + ch <- fmt.Errorf("unable to create gzip reader layer upload: %v", err) |
| 55 | + return |
| 56 | + } |
| 57 | + if !gr.Header.ModTime.IsZero() { |
| 58 | + modTime = &gr.Header.ModTime |
| 59 | + } |
| 60 | + _, err = io.Copy(layerhash, gr) |
| 61 | + ch <- err |
| 62 | + }() |
| 63 | + |
| 64 | + n, err := dst.ReadFrom(io.TeeReader(src, io.MultiWriter(blobhash, pw))) |
| 65 | + if err != nil { |
| 66 | + return "", "", nil, 0, fmt.Errorf("unable to upload new layer (%d): %v", n, err) |
| 67 | + } |
| 68 | + if err := pw.Close(); err != nil { |
| 69 | + return "", "", nil, 0, fmt.Errorf("unable to complete writing diffID: %v", err) |
| 70 | + } |
| 71 | + if err := <-ch; err != nil { |
| 72 | + return "", "", nil, 0, fmt.Errorf("unable to calculate layer diffID: %v", err) |
| 73 | + } |
| 74 | + |
| 75 | + layerDigest = digest.NewDigestFromBytes(algo, layerhash.Sum(make([]byte, 0, layerhash.Size()))) |
| 76 | + blobDigest = digest.NewDigestFromBytes(algo, blobhash.Sum(make([]byte, 0, blobhash.Size()))) |
| 77 | + return layerDigest, blobDigest, modTime, n, nil |
| 78 | +} |
| 79 | + |
| 80 | +func NewEmptyConfig() *docker10.DockerImageConfig { |
| 81 | + config := &docker10.DockerImageConfig{ |
| 82 | + DockerVersion: "", |
| 83 | + // Created must be non-zero |
| 84 | + Created: (time.Time{}).Add(1 * time.Second), |
| 85 | + OS: runtime.GOOS, |
| 86 | + Architecture: runtime.GOARCH, |
| 87 | + } |
| 88 | + return config |
| 89 | +} |
| 90 | + |
| 91 | +func AddScratchLayerToConfig(config *docker10.DockerImageConfig) distribution.Descriptor { |
| 92 | + layer := distribution.Descriptor{ |
| 93 | + MediaType: dockerV2Schema2LayerMediaType, |
| 94 | + Digest: digest.Digest(dockerlayer.GzippedEmptyLayerDigest), |
| 95 | + Size: int64(len(dockerlayer.GzippedEmptyLayer)), |
| 96 | + } |
| 97 | + AddLayerToConfig(config, layer, dockerlayer.EmptyLayerDiffID) |
| 98 | + return layer |
| 99 | +} |
| 100 | + |
| 101 | +func AddLayerToConfig(config *docker10.DockerImageConfig, layer distribution.Descriptor, diffID string) { |
| 102 | + if config.RootFS == nil { |
| 103 | + config.RootFS = &docker10.DockerConfigRootFS{Type: "layers"} |
| 104 | + } |
| 105 | + config.RootFS.DiffIDs = append(config.RootFS.DiffIDs, diffID) |
| 106 | + config.Size += layer.Size |
| 107 | +} |
| 108 | + |
| 109 | +func UploadSchema2Config(ctx context.Context, blobs distribution.BlobService, config *docker10.DockerImageConfig, layers []distribution.Descriptor) (*schema2.DeserializedManifest, error) { |
| 110 | + // ensure the image size is correct before persisting |
| 111 | + config.Size = 0 |
| 112 | + for _, layer := range layers { |
| 113 | + config.Size += layer.Size |
| 114 | + } |
| 115 | + configJSON, err := json.Marshal(config) |
| 116 | + if err != nil { |
| 117 | + return nil, err |
| 118 | + } |
| 119 | + return putSchema2ImageConfig(ctx, blobs, dockerV2Schema2ConfigMediaType, configJSON, layers) |
| 120 | +} |
| 121 | + |
| 122 | +// putSchema2ImageConfig uploads the provided configJSON to the blob store and returns the generated manifest |
| 123 | +// for the requested image. |
| 124 | +func putSchema2ImageConfig(ctx context.Context, blobs distribution.BlobService, mediaType string, configJSON []byte, layers []distribution.Descriptor) (*schema2.DeserializedManifest, error) { |
| 125 | + b := schema2.NewManifestBuilder(blobs, mediaType, configJSON) |
| 126 | + for _, layer := range layers { |
| 127 | + if err := b.AppendReference(layer); err != nil { |
| 128 | + return nil, err |
| 129 | + } |
| 130 | + } |
| 131 | + m, err := b.Build(ctx) |
| 132 | + if err != nil { |
| 133 | + return nil, err |
| 134 | + } |
| 135 | + manifest, ok := m.(*schema2.DeserializedManifest) |
| 136 | + if !ok { |
| 137 | + return nil, fmt.Errorf("unable to turn %T into a DeserializedManifest, unable to store image", m) |
| 138 | + } |
| 139 | + return manifest, nil |
| 140 | +} |
0 commit comments