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