1 package schema1
2
3 import (
4 "context"
5 "crypto/sha512"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "time"
10
11 "github.com/distribution/reference"
12 "github.com/docker/distribution"
13 "github.com/docker/distribution/manifest"
14 "github.com/docker/libtrust"
15 "github.com/opencontainers/go-digest"
16 )
17
18 type diffID digest.Digest
19
20
21
22 var gzippedEmptyTar = []byte{
23 31, 139, 8, 0, 0, 9, 110, 136, 0, 255, 98, 24, 5, 163, 96, 20, 140, 88,
24 0, 8, 0, 0, 255, 255, 46, 175, 181, 239, 0, 4, 0, 0,
25 }
26
27
28
29 const digestSHA256GzippedEmptyTar = digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")
30
31
32
33 type configManifestBuilder struct {
34
35
36 bs distribution.BlobService
37
38 pk libtrust.PrivateKey
39
40
41 configJSON []byte
42
43 ref reference.Named
44
45 descriptors []distribution.Descriptor
46
47
48 emptyTarDigest digest.Digest
49 }
50
51
52
53
54
55 func NewConfigManifestBuilder(bs distribution.BlobService, pk libtrust.PrivateKey, ref reference.Named, configJSON []byte) distribution.ManifestBuilder {
56 return &configManifestBuilder{
57 bs: bs,
58 pk: pk,
59 configJSON: configJSON,
60 ref: ref,
61 }
62 }
63
64
65 func (mb *configManifestBuilder) Build(ctx context.Context) (m distribution.Manifest, err error) {
66 type imageRootFS struct {
67 Type string `json:"type"`
68 DiffIDs []diffID `json:"diff_ids,omitempty"`
69 BaseLayer string `json:"base_layer,omitempty"`
70 }
71
72 type imageHistory struct {
73 Created time.Time `json:"created"`
74 Author string `json:"author,omitempty"`
75 CreatedBy string `json:"created_by,omitempty"`
76 Comment string `json:"comment,omitempty"`
77 EmptyLayer bool `json:"empty_layer,omitempty"`
78 }
79
80 type imageConfig struct {
81 RootFS *imageRootFS `json:"rootfs,omitempty"`
82 History []imageHistory `json:"history,omitempty"`
83 Architecture string `json:"architecture,omitempty"`
84 }
85
86 var img imageConfig
87
88 if err := json.Unmarshal(mb.configJSON, &img); err != nil {
89 return nil, err
90 }
91
92 if len(img.History) == 0 {
93 return nil, errors.New("empty history when trying to create schema1 manifest")
94 }
95
96 if len(img.RootFS.DiffIDs) != len(mb.descriptors) {
97 return nil, fmt.Errorf("number of descriptors and number of layers in rootfs must match: len(%v) != len(%v)", img.RootFS.DiffIDs, mb.descriptors)
98 }
99
100
101
102
103
104 type v1Compatibility struct {
105 ID string `json:"id"`
106 Parent string `json:"parent,omitempty"`
107 Comment string `json:"comment,omitempty"`
108 Created time.Time `json:"created"`
109 ContainerConfig struct {
110 Cmd []string
111 } `json:"container_config,omitempty"`
112 Author string `json:"author,omitempty"`
113 ThrowAway bool `json:"throwaway,omitempty"`
114 }
115
116 fsLayerList := make([]FSLayer, len(img.History))
117 history := make([]History, len(img.History))
118
119 parent := ""
120 layerCounter := 0
121 for i, h := range img.History[:len(img.History)-1] {
122 var blobsum digest.Digest
123 if h.EmptyLayer {
124 if blobsum, err = mb.emptyTar(ctx); err != nil {
125 return nil, err
126 }
127 } else {
128 if len(img.RootFS.DiffIDs) <= layerCounter {
129 return nil, errors.New("too many non-empty layers in History section")
130 }
131 blobsum = mb.descriptors[layerCounter].Digest
132 layerCounter++
133 }
134
135 v1ID := digest.FromBytes([]byte(blobsum.Hex() + " " + parent)).Hex()
136
137 if i == 0 && img.RootFS.BaseLayer != "" {
138
139 baseID := sha512.Sum384([]byte(img.RootFS.BaseLayer))
140 parent = fmt.Sprintf("%x", baseID[:32])
141 }
142
143 v1Compatibility := v1Compatibility{
144 ID: v1ID,
145 Parent: parent,
146 Comment: h.Comment,
147 Created: h.Created,
148 Author: h.Author,
149 }
150 v1Compatibility.ContainerConfig.Cmd = []string{img.History[i].CreatedBy}
151 if h.EmptyLayer {
152 v1Compatibility.ThrowAway = true
153 }
154 jsonBytes, err := json.Marshal(&v1Compatibility)
155 if err != nil {
156 return nil, err
157 }
158
159 reversedIndex := len(img.History) - i - 1
160 history[reversedIndex].V1Compatibility = string(jsonBytes)
161 fsLayerList[reversedIndex] = FSLayer{BlobSum: blobsum}
162
163 parent = v1ID
164 }
165
166 latestHistory := img.History[len(img.History)-1]
167
168 var blobsum digest.Digest
169 if latestHistory.EmptyLayer {
170 if blobsum, err = mb.emptyTar(ctx); err != nil {
171 return nil, err
172 }
173 } else {
174 if len(img.RootFS.DiffIDs) <= layerCounter {
175 return nil, errors.New("too many non-empty layers in History section")
176 }
177 blobsum = mb.descriptors[layerCounter].Digest
178 }
179
180 fsLayerList[0] = FSLayer{BlobSum: blobsum}
181 dgst := digest.FromBytes([]byte(blobsum.Hex() + " " + parent + " " + string(mb.configJSON)))
182
183
184
185 transformedConfig, err := MakeV1ConfigFromConfig(mb.configJSON, dgst.Hex(), parent, latestHistory.EmptyLayer)
186 if err != nil {
187 return nil, err
188 }
189
190 history[0].V1Compatibility = string(transformedConfig)
191
192 tag := ""
193 if tagged, isTagged := mb.ref.(reference.Tagged); isTagged {
194 tag = tagged.Tag()
195 }
196
197 mfst := Manifest{
198 Versioned: manifest.Versioned{
199 SchemaVersion: 1,
200 },
201 Name: mb.ref.Name(),
202 Tag: tag,
203 Architecture: img.Architecture,
204 FSLayers: fsLayerList,
205 History: history,
206 }
207
208 return Sign(&mfst, mb.pk)
209 }
210
211
212
213 func (mb *configManifestBuilder) emptyTar(ctx context.Context) (digest.Digest, error) {
214 if mb.emptyTarDigest != "" {
215
216 return mb.emptyTarDigest, nil
217 }
218
219 descriptor, err := mb.bs.Stat(ctx, digestSHA256GzippedEmptyTar)
220 switch err {
221 case nil:
222 mb.emptyTarDigest = descriptor.Digest
223 return descriptor.Digest, nil
224 case distribution.ErrBlobUnknown:
225
226 default:
227 return "", err
228 }
229
230
231 descriptor, err = mb.bs.Put(ctx, "", gzippedEmptyTar)
232 if err != nil {
233 return "", err
234 }
235
236 mb.emptyTarDigest = descriptor.Digest
237
238 return descriptor.Digest, nil
239 }
240
241
242 func (mb *configManifestBuilder) AppendReference(d distribution.Describable) error {
243 descriptor := d.Descriptor()
244
245 if err := descriptor.Digest.Validate(); err != nil {
246 return err
247 }
248
249 mb.descriptors = append(mb.descriptors, descriptor)
250 return nil
251 }
252
253
254 func (mb *configManifestBuilder) References() []distribution.Descriptor {
255 return mb.descriptors
256 }
257
258
259 func MakeV1ConfigFromConfig(configJSON []byte, v1ID, parentV1ID string, throwaway bool) ([]byte, error) {
260
261
262 var configAsMap map[string]*json.RawMessage
263 if err := json.Unmarshal(configJSON, &configAsMap); err != nil {
264 return nil, err
265 }
266
267
268 delete(configAsMap, "rootfs")
269 delete(configAsMap, "history")
270 configAsMap["id"] = rawJSON(v1ID)
271 if parentV1ID != "" {
272 configAsMap["parent"] = rawJSON(parentV1ID)
273 }
274 if throwaway {
275 configAsMap["throwaway"] = rawJSON(true)
276 }
277
278 return json.Marshal(configAsMap)
279 }
280
281 func rawJSON(value interface{}) *json.RawMessage {
282 jsonval, err := json.Marshal(value)
283 if err != nil {
284 return nil
285 }
286 return (*json.RawMessage)(&jsonval)
287 }
288
View as plain text