github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/manifest/schema1/config_builder.go (about) 1 package schema1 2 3 import ( 4 "crypto/sha512" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "time" 9 10 "github.com/docker/distribution" 11 "github.com/docker/distribution/context" 12 "github.com/docker/libtrust" 13 14 "github.com/docker/distribution/digest" 15 "github.com/docker/distribution/manifest" 16 ) 17 18 type diffID digest.Digest 19 20 // gzippedEmptyTar is a gzip-compressed version of an empty tar file 21 // (1024 NULL bytes) 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 // digestSHA256GzippedEmptyTar is the canonical sha256 digest of 28 // gzippedEmptyTar 29 const digestSHA256GzippedEmptyTar = digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4") 30 31 // configManifestBuilder is a type for constructing manifests from an image 32 // configuration and generic descriptors. 33 type configManifestBuilder struct { 34 // bs is a BlobService used to create empty layer tars in the 35 // blob store if necessary. 36 bs distribution.BlobService 37 // pk is the libtrust private key used to sign the final manifest. 38 pk libtrust.PrivateKey 39 // configJSON is configuration supplied when the ManifestBuilder was 40 // created. 41 configJSON []byte 42 // name is the name provided to NewConfigManifestBuilder 43 name string 44 // tag is the tag provided to NewConfigManifestBuilder 45 tag string 46 // descriptors is the set of descriptors referencing the layers. 47 descriptors []distribution.Descriptor 48 // emptyTarDigest is set to a valid digest if an empty tar has been 49 // put in the blob store; otherwise it is empty. 50 emptyTarDigest digest.Digest 51 } 52 53 // NewConfigManifestBuilder is used to build new manifests for the current 54 // schema version from an image configuration and a set of descriptors. 55 // It takes a BlobService so that it can add an empty tar to the blob store 56 // if the resulting manifest needs empty layers. 57 func NewConfigManifestBuilder(bs distribution.BlobService, pk libtrust.PrivateKey, name, tag string, configJSON []byte) distribution.ManifestBuilder { 58 return &configManifestBuilder{ 59 bs: bs, 60 pk: pk, 61 configJSON: configJSON, 62 name: name, 63 tag: tag, 64 } 65 } 66 67 // Build produces a final manifest from the given references 68 func (mb *configManifestBuilder) Build(ctx context.Context) (m distribution.Manifest, err error) { 69 type imageRootFS struct { 70 Type string `json:"type"` 71 DiffIDs []diffID `json:"diff_ids,omitempty"` 72 BaseLayer string `json:"base_layer,omitempty"` 73 } 74 75 type imageHistory struct { 76 Created time.Time `json:"created"` 77 Author string `json:"author,omitempty"` 78 CreatedBy string `json:"created_by,omitempty"` 79 Comment string `json:"comment,omitempty"` 80 EmptyLayer bool `json:"empty_layer,omitempty"` 81 } 82 83 type imageConfig struct { 84 RootFS *imageRootFS `json:"rootfs,omitempty"` 85 History []imageHistory `json:"history,omitempty"` 86 Architecture string `json:"architecture,omitempty"` 87 } 88 89 var img imageConfig 90 91 if err := json.Unmarshal(mb.configJSON, &img); err != nil { 92 return nil, err 93 } 94 95 if len(img.History) == 0 { 96 return nil, errors.New("empty history when trying to create schema1 manifest") 97 } 98 99 if len(img.RootFS.DiffIDs) != len(mb.descriptors) { 100 return nil, errors.New("number of descriptors and number of layers in rootfs must match") 101 } 102 103 // Generate IDs for each layer 104 // For non-top-level layers, create fake V1Compatibility strings that 105 // fit the format and don't collide with anything else, but don't 106 // result in runnable images on their own. 107 type v1Compatibility struct { 108 ID string `json:"id"` 109 Parent string `json:"parent,omitempty"` 110 Comment string `json:"comment,omitempty"` 111 Created time.Time `json:"created"` 112 ContainerConfig struct { 113 Cmd []string 114 } `json:"container_config,omitempty"` 115 ThrowAway bool `json:"throwaway,omitempty"` 116 } 117 118 fsLayerList := make([]FSLayer, len(img.History)) 119 history := make([]History, len(img.History)) 120 121 parent := "" 122 layerCounter := 0 123 for i, h := range img.History[:len(img.History)-1] { 124 var blobsum digest.Digest 125 if h.EmptyLayer { 126 if blobsum, err = mb.emptyTar(ctx); err != nil { 127 return nil, err 128 } 129 } else { 130 if len(img.RootFS.DiffIDs) <= layerCounter { 131 return nil, errors.New("too many non-empty layers in History section") 132 } 133 blobsum = mb.descriptors[layerCounter].Digest 134 layerCounter++ 135 } 136 137 v1ID := digest.FromBytes([]byte(blobsum.Hex() + " " + parent)).Hex() 138 139 if i == 0 && img.RootFS.BaseLayer != "" { 140 // windows-only baselayer setup 141 baseID := sha512.Sum384([]byte(img.RootFS.BaseLayer)) 142 parent = fmt.Sprintf("%x", baseID[:32]) 143 } 144 145 v1Compatibility := v1Compatibility{ 146 ID: v1ID, 147 Parent: parent, 148 Comment: h.Comment, 149 Created: h.Created, 150 } 151 v1Compatibility.ContainerConfig.Cmd = []string{img.History[i].CreatedBy} 152 if h.EmptyLayer { 153 v1Compatibility.ThrowAway = true 154 } 155 jsonBytes, err := json.Marshal(&v1Compatibility) 156 if err != nil { 157 return nil, err 158 } 159 160 reversedIndex := len(img.History) - i - 1 161 history[reversedIndex].V1Compatibility = string(jsonBytes) 162 fsLayerList[reversedIndex] = FSLayer{BlobSum: blobsum} 163 164 parent = v1ID 165 } 166 167 latestHistory := img.History[len(img.History)-1] 168 169 var blobsum digest.Digest 170 if latestHistory.EmptyLayer { 171 if blobsum, err = mb.emptyTar(ctx); err != nil { 172 return nil, err 173 } 174 } else { 175 if len(img.RootFS.DiffIDs) <= layerCounter { 176 return nil, errors.New("too many non-empty layers in History section") 177 } 178 blobsum = mb.descriptors[layerCounter].Digest 179 } 180 181 fsLayerList[0] = FSLayer{BlobSum: blobsum} 182 dgst := digest.FromBytes([]byte(blobsum.Hex() + " " + parent + " " + string(mb.configJSON))) 183 184 // Top-level v1compatibility string should be a modified version of the 185 // image config. 186 transformedConfig, err := MakeV1ConfigFromConfig(mb.configJSON, dgst.Hex(), parent, latestHistory.EmptyLayer) 187 if err != nil { 188 return nil, err 189 } 190 191 history[0].V1Compatibility = string(transformedConfig) 192 193 mfst := Manifest{ 194 Versioned: manifest.Versioned{ 195 SchemaVersion: 1, 196 }, 197 Name: mb.name, 198 Tag: mb.tag, 199 Architecture: img.Architecture, 200 FSLayers: fsLayerList, 201 History: history, 202 } 203 204 return Sign(&mfst, mb.pk) 205 } 206 207 // emptyTar pushes a compressed empty tar to the blob store if one doesn't 208 // already exist, and returns its blobsum. 209 func (mb *configManifestBuilder) emptyTar(ctx context.Context) (digest.Digest, error) { 210 if mb.emptyTarDigest != "" { 211 // Already put an empty tar 212 return mb.emptyTarDigest, nil 213 } 214 215 descriptor, err := mb.bs.Stat(ctx, digestSHA256GzippedEmptyTar) 216 switch err { 217 case nil: 218 mb.emptyTarDigest = descriptor.Digest 219 return descriptor.Digest, nil 220 case distribution.ErrBlobUnknown: 221 // nop 222 default: 223 return "", err 224 } 225 226 // Add gzipped empty tar to the blob store 227 descriptor, err = mb.bs.Put(ctx, "", gzippedEmptyTar) 228 if err != nil { 229 return "", err 230 } 231 232 mb.emptyTarDigest = descriptor.Digest 233 234 return descriptor.Digest, nil 235 } 236 237 // AppendReference adds a reference to the current ManifestBuilder 238 func (mb *configManifestBuilder) AppendReference(d distribution.Describable) error { 239 // todo: verification here? 240 mb.descriptors = append(mb.descriptors, d.Descriptor()) 241 return nil 242 } 243 244 // References returns the current references added to this builder 245 func (mb *configManifestBuilder) References() []distribution.Descriptor { 246 return mb.descriptors 247 } 248 249 // MakeV1ConfigFromConfig creates an legacy V1 image config from image config JSON 250 func MakeV1ConfigFromConfig(configJSON []byte, v1ID, parentV1ID string, throwaway bool) ([]byte, error) { 251 // Top-level v1compatibility string should be a modified version of the 252 // image config. 253 var configAsMap map[string]*json.RawMessage 254 if err := json.Unmarshal(configJSON, &configAsMap); err != nil { 255 return nil, err 256 } 257 258 // Delete fields that didn't exist in old manifest 259 delete(configAsMap, "rootfs") 260 delete(configAsMap, "history") 261 configAsMap["id"] = rawJSON(v1ID) 262 if parentV1ID != "" { 263 configAsMap["parent"] = rawJSON(parentV1ID) 264 } 265 if throwaway { 266 configAsMap["throwaway"] = rawJSON(true) 267 } 268 269 return json.Marshal(configAsMap) 270 } 271 272 func rawJSON(value interface{}) *json.RawMessage { 273 jsonval, err := json.Marshal(value) 274 if err != nil { 275 return nil 276 } 277 return (*json.RawMessage)(&jsonval) 278 }