...

Source file src/github.com/docker/distribution/manifest/schema1/config_builder.go

Documentation: github.com/docker/distribution/manifest/schema1

     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  // 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  	// ref contains the name and optional tag provided to NewConfigManifestBuilder.
    43  	ref reference.Named
    44  	// descriptors is the set of descriptors referencing the layers.
    45  	descriptors []distribution.Descriptor
    46  	// emptyTarDigest is set to a valid digest if an empty tar has been
    47  	// put in the blob store; otherwise it is empty.
    48  	emptyTarDigest digest.Digest
    49  }
    50  
    51  // NewConfigManifestBuilder is used to build new manifests for the current
    52  // schema version from an image configuration and a set of descriptors.
    53  // It takes a BlobService so that it can add an empty tar to the blob store
    54  // if the resulting manifest needs empty layers.
    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  // Build produces a final manifest from the given references
    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  	// Generate IDs for each layer
   101  	// For non-top-level layers, create fake V1Compatibility strings that
   102  	// fit the format and don't collide with anything else, but don't
   103  	// result in runnable images on their own.
   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  			// windows-only baselayer setup
   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  	// Top-level v1compatibility string should be a modified version of the
   184  	// image config.
   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  // emptyTar pushes a compressed empty tar to the blob store if one doesn't
   212  // already exist, and returns its blobsum.
   213  func (mb *configManifestBuilder) emptyTar(ctx context.Context) (digest.Digest, error) {
   214  	if mb.emptyTarDigest != "" {
   215  		// Already put an empty tar
   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  		// nop
   226  	default:
   227  		return "", err
   228  	}
   229  
   230  	// Add gzipped empty tar to the blob store
   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  // AppendReference adds a reference to the current ManifestBuilder
   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  // References returns the current references added to this builder
   254  func (mb *configManifestBuilder) References() []distribution.Descriptor {
   255  	return mb.descriptors
   256  }
   257  
   258  // MakeV1ConfigFromConfig creates an legacy V1 image config from image config JSON
   259  func MakeV1ConfigFromConfig(configJSON []byte, v1ID, parentV1ID string, throwaway bool) ([]byte, error) {
   260  	// Top-level v1compatibility string should be a modified version of the
   261  	// image config.
   262  	var configAsMap map[string]*json.RawMessage
   263  	if err := json.Unmarshal(configJSON, &configAsMap); err != nil {
   264  		return nil, err
   265  	}
   266  
   267  	// Delete fields that didn't exist in old manifest
   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