config_builder.go 8.3 KB

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