config_builder.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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/manifest"
  11. "github.com/docker/distribution/reference"
  12. "github.com/docker/libtrust"
  13. "github.com/opencontainers/go-digest"
  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, fmt.Errorf("number of descriptors and number of layers in rootfs must match: len(%v) != len(%v)", img.RootFS.DiffIDs, mb.descriptors)
  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. Author string `json:"author,omitempty"`
  98. ThrowAway bool `json:"throwaway,omitempty"`
  99. }
  100. fsLayerList := make([]FSLayer, len(img.History))
  101. history := make([]History, len(img.History))
  102. parent := ""
  103. layerCounter := 0
  104. for i, h := range img.History[:len(img.History)-1] {
  105. var blobsum digest.Digest
  106. if h.EmptyLayer {
  107. if blobsum, err = mb.emptyTar(ctx); err != nil {
  108. return nil, err
  109. }
  110. } else {
  111. if len(img.RootFS.DiffIDs) <= layerCounter {
  112. return nil, errors.New("too many non-empty layers in History section")
  113. }
  114. blobsum = mb.descriptors[layerCounter].Digest
  115. layerCounter++
  116. }
  117. v1ID := digest.FromBytes([]byte(blobsum.Hex() + " " + parent)).Hex()
  118. if i == 0 && img.RootFS.BaseLayer != "" {
  119. // windows-only baselayer setup
  120. baseID := sha512.Sum384([]byte(img.RootFS.BaseLayer))
  121. parent = fmt.Sprintf("%x", baseID[:32])
  122. }
  123. v1Compatibility := v1Compatibility{
  124. ID: v1ID,
  125. Parent: parent,
  126. Comment: h.Comment,
  127. Created: h.Created,
  128. Author: h.Author,
  129. }
  130. v1Compatibility.ContainerConfig.Cmd = []string{img.History[i].CreatedBy}
  131. if h.EmptyLayer {
  132. v1Compatibility.ThrowAway = true
  133. }
  134. jsonBytes, err := json.Marshal(&v1Compatibility)
  135. if err != nil {
  136. return nil, err
  137. }
  138. reversedIndex := len(img.History) - i - 1
  139. history[reversedIndex].V1Compatibility = string(jsonBytes)
  140. fsLayerList[reversedIndex] = FSLayer{BlobSum: blobsum}
  141. parent = v1ID
  142. }
  143. latestHistory := img.History[len(img.History)-1]
  144. var blobsum digest.Digest
  145. if latestHistory.EmptyLayer {
  146. if blobsum, err = mb.emptyTar(ctx); err != nil {
  147. return nil, err
  148. }
  149. } else {
  150. if len(img.RootFS.DiffIDs) <= layerCounter {
  151. return nil, errors.New("too many non-empty layers in History section")
  152. }
  153. blobsum = mb.descriptors[layerCounter].Digest
  154. }
  155. fsLayerList[0] = FSLayer{BlobSum: blobsum}
  156. dgst := digest.FromBytes([]byte(blobsum.Hex() + " " + parent + " " + string(mb.configJSON)))
  157. // Top-level v1compatibility string should be a modified version of the
  158. // image config.
  159. transformedConfig, err := MakeV1ConfigFromConfig(mb.configJSON, dgst.Hex(), parent, latestHistory.EmptyLayer)
  160. if err != nil {
  161. return nil, err
  162. }
  163. history[0].V1Compatibility = string(transformedConfig)
  164. tag := ""
  165. if tagged, isTagged := mb.ref.(reference.Tagged); isTagged {
  166. tag = tagged.Tag()
  167. }
  168. mfst := Manifest{
  169. Versioned: manifest.Versioned{
  170. SchemaVersion: 1,
  171. },
  172. Name: mb.ref.Name(),
  173. Tag: tag,
  174. Architecture: img.Architecture,
  175. FSLayers: fsLayerList,
  176. History: history,
  177. }
  178. return Sign(&mfst, mb.pk)
  179. }
  180. // emptyTar pushes a compressed empty tar to the blob store if one doesn't
  181. // already exist, and returns its blobsum.
  182. func (mb *configManifestBuilder) emptyTar(ctx context.Context) (digest.Digest, error) {
  183. if mb.emptyTarDigest != "" {
  184. // Already put an empty tar
  185. return mb.emptyTarDigest, nil
  186. }
  187. descriptor, err := mb.bs.Stat(ctx, digestSHA256GzippedEmptyTar)
  188. switch err {
  189. case nil:
  190. mb.emptyTarDigest = descriptor.Digest
  191. return descriptor.Digest, nil
  192. case distribution.ErrBlobUnknown:
  193. // nop
  194. default:
  195. return "", err
  196. }
  197. // Add gzipped empty tar to the blob store
  198. descriptor, err = mb.bs.Put(ctx, "", gzippedEmptyTar)
  199. if err != nil {
  200. return "", err
  201. }
  202. mb.emptyTarDigest = descriptor.Digest
  203. return descriptor.Digest, nil
  204. }
  205. // AppendReference adds a reference to the current ManifestBuilder
  206. func (mb *configManifestBuilder) AppendReference(d distribution.Describable) error {
  207. descriptor := d.Descriptor()
  208. if err := descriptor.Digest.Validate(); err != nil {
  209. return err
  210. }
  211. mb.descriptors = append(mb.descriptors, descriptor)
  212. return nil
  213. }
  214. // References returns the current references added to this builder
  215. func (mb *configManifestBuilder) References() []distribution.Descriptor {
  216. return mb.descriptors
  217. }
  218. // MakeV1ConfigFromConfig creates an legacy V1 image config from image config JSON
  219. func MakeV1ConfigFromConfig(configJSON []byte, v1ID, parentV1ID string, throwaway bool) ([]byte, error) {
  220. // Top-level v1compatibility string should be a modified version of the
  221. // image config.
  222. var configAsMap map[string]*json.RawMessage
  223. if err := json.Unmarshal(configJSON, &configAsMap); err != nil {
  224. return nil, err
  225. }
  226. // Delete fields that didn't exist in old manifest
  227. delete(configAsMap, "rootfs")
  228. delete(configAsMap, "history")
  229. configAsMap["id"] = rawJSON(v1ID)
  230. if parentV1ID != "" {
  231. configAsMap["parent"] = rawJSON(parentV1ID)
  232. }
  233. if throwaway {
  234. configAsMap["throwaway"] = rawJSON(true)
  235. }
  236. return json.Marshal(configAsMap)
  237. }
  238. func rawJSON(value interface{}) *json.RawMessage {
  239. jsonval, err := json.Marshal(value)
  240. if err != nil {
  241. return nil
  242. }
  243. return (*json.RawMessage)(&jsonval)
  244. }