2017-02-11 00:48:15 +08:00
|
|
|
package buildah
|
2017-01-27 00:58:00 +08:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2017-01-27 22:38:32 +08:00
|
|
|
"time"
|
2017-01-27 00:58:00 +08:00
|
|
|
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
|
|
"github.com/containers/image/docker/reference"
|
|
|
|
"github.com/containers/image/image"
|
2017-06-02 00:09:23 +08:00
|
|
|
"github.com/containers/image/manifest"
|
2017-01-27 00:58:00 +08:00
|
|
|
is "github.com/containers/image/storage"
|
|
|
|
"github.com/containers/image/types"
|
2017-05-17 23:53:28 +08:00
|
|
|
"github.com/containers/storage"
|
2017-01-27 00:58:00 +08:00
|
|
|
"github.com/containers/storage/pkg/archive"
|
|
|
|
"github.com/containers/storage/pkg/ioutils"
|
|
|
|
digest "github.com/opencontainers/go-digest"
|
|
|
|
specs "github.com/opencontainers/image-spec/specs-go"
|
|
|
|
"github.com/opencontainers/image-spec/specs-go/v1"
|
2017-06-02 03:23:02 +08:00
|
|
|
"github.com/pkg/errors"
|
2017-05-18 05:01:06 +08:00
|
|
|
"github.com/projectatomic/buildah/docker"
|
2017-01-27 00:58:00 +08:00
|
|
|
)
|
|
|
|
|
2017-05-18 05:02:40 +08:00
|
|
|
const (
|
|
|
|
// OCIv1ImageManifest is the MIME type of an OCIv1 image manifest,
|
|
|
|
// suitable for specifying as a value of the PreferredManifestType
|
|
|
|
// member of a CommitOptions structure. It is also the default.
|
|
|
|
OCIv1ImageManifest = v1.MediaTypeImageManifest
|
|
|
|
// Dockerv2ImageManifest is the MIME type of a Docker v2s2 image
|
|
|
|
// manifest, suitable for specifying as a value of the
|
|
|
|
// PreferredManifestType member of a CommitOptions structure.
|
|
|
|
Dockerv2ImageManifest = docker.V2S2MediaTypeManifest
|
|
|
|
)
|
|
|
|
|
2017-01-27 00:58:00 +08:00
|
|
|
type containerImageRef struct {
|
2017-05-18 05:02:40 +08:00
|
|
|
store storage.Store
|
|
|
|
compression archive.Compression
|
|
|
|
name reference.Named
|
2017-06-02 00:09:23 +08:00
|
|
|
names []string
|
|
|
|
layerID string
|
|
|
|
addHistory bool
|
2017-05-18 05:02:40 +08:00
|
|
|
oconfig []byte
|
|
|
|
dconfig []byte
|
2017-06-01 01:44:41 +08:00
|
|
|
created time.Time
|
2017-05-18 05:02:40 +08:00
|
|
|
createdBy string
|
|
|
|
annotations map[string]string
|
|
|
|
preferredManifestType string
|
Take a shortcut when writing to local storage
When writing to local storage, take a couple of shortcuts: instead of
recompressing layers to ensure that the values we store in the image
manifest will be correct for content-addressibility, just pretend that
the layer ID is a blob hash value, and that it's a valid layer diffID.
Local storage doesn't generally care if these values are correct, and we
already have to recompute these values when exporting an image, but this
saves us quite a bit of time.
The image library's Copy() routine actually cares about and
sanity-checks these things, so if we're going to take advantage of the
shortcuts, we need to use its higher-level APIs to write a layer, write
the configuration, and write the manifest, then move those items that it
writes to an image with the right set of layers.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #141
Approved by: rhatdan
2017-06-01 01:56:25 +08:00
|
|
|
exporting bool
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
type containerImageSource struct {
|
|
|
|
path string
|
|
|
|
ref *containerImageRef
|
|
|
|
store storage.Store
|
2017-06-02 00:09:23 +08:00
|
|
|
layerID string
|
|
|
|
names []string
|
|
|
|
addHistory bool
|
2017-01-28 15:18:02 +08:00
|
|
|
compression archive.Compression
|
2017-01-27 00:58:00 +08:00
|
|
|
config []byte
|
|
|
|
configDigest digest.Digest
|
|
|
|
manifest []byte
|
2017-05-18 05:01:06 +08:00
|
|
|
manifestType string
|
Take a shortcut when writing to local storage
When writing to local storage, take a couple of shortcuts: instead of
recompressing layers to ensure that the values we store in the image
manifest will be correct for content-addressibility, just pretend that
the layer ID is a blob hash value, and that it's a valid layer diffID.
Local storage doesn't generally care if these values are correct, and we
already have to recompute these values when exporting an image, but this
saves us quite a bit of time.
The image library's Copy() routine actually cares about and
sanity-checks these things, so if we're going to take advantage of the
shortcuts, we need to use its higher-level APIs to write a layer, write
the configuration, and write the manifest, then move those items that it
writes to an image with the right set of layers.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #141
Approved by: rhatdan
2017-06-01 01:56:25 +08:00
|
|
|
exporting bool
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageRef) NewImage(sc *types.SystemContext) (types.Image, error) {
|
|
|
|
src, err := i.NewImageSource(sc, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return image.FromSource(src)
|
|
|
|
}
|
|
|
|
|
2017-06-02 00:09:23 +08:00
|
|
|
func selectManifestType(preferred string, acceptable, supported []string) string {
|
|
|
|
selected := preferred
|
|
|
|
for _, accept := range acceptable {
|
|
|
|
if preferred == accept {
|
|
|
|
return preferred
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
2017-06-02 00:09:23 +08:00
|
|
|
for _, support := range supported {
|
|
|
|
if accept == support {
|
|
|
|
selected = accept
|
2017-05-18 05:02:40 +08:00
|
|
|
}
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
}
|
2017-06-02 00:09:23 +08:00
|
|
|
return selected
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageRef) NewImageSource(sc *types.SystemContext, manifestTypes []string) (src types.ImageSource, err error) {
|
|
|
|
// Decide which type of manifest and configuration output we're going to provide.
|
|
|
|
supportedManifestTypes := []string{v1.MediaTypeImageManifest, docker.V2S2MediaTypeManifest}
|
|
|
|
manifestType := selectManifestType(i.preferredManifestType, manifestTypes, supportedManifestTypes)
|
2017-05-18 05:01:06 +08:00
|
|
|
// If it's not a format we support, return an error.
|
|
|
|
if manifestType != v1.MediaTypeImageManifest && manifestType != docker.V2S2MediaTypeManifest {
|
2017-06-03 00:17:27 +08:00
|
|
|
return nil, errors.Errorf("no supported manifest types (attempted to use %q, only know %q and %q)",
|
2017-05-18 05:01:06 +08:00
|
|
|
manifestType, v1.MediaTypeImageManifest, docker.V2S2MediaTypeManifest)
|
|
|
|
}
|
2017-06-01 01:44:41 +08:00
|
|
|
// Start building the list of layers using the read-write layer.
|
2017-01-27 00:58:00 +08:00
|
|
|
layers := []string{}
|
2017-06-02 00:09:23 +08:00
|
|
|
layerID := i.layerID
|
2017-05-17 23:53:28 +08:00
|
|
|
layer, err := i.store.Layer(layerID)
|
2017-01-27 00:58:00 +08:00
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return nil, errors.Wrapf(err, "unable to read layer %q", layerID)
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
2017-06-01 01:44:41 +08:00
|
|
|
// Walk the list of parent layers, prepending each as we go.
|
2017-01-27 00:58:00 +08:00
|
|
|
for layer != nil {
|
|
|
|
layers = append(append([]string{}, layerID), layers...)
|
|
|
|
layerID = layer.Parent
|
|
|
|
if layerID == "" {
|
|
|
|
err = nil
|
|
|
|
break
|
|
|
|
}
|
2017-05-17 23:53:28 +08:00
|
|
|
layer, err = i.store.Layer(layerID)
|
2017-01-27 00:58:00 +08:00
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return nil, errors.Wrapf(err, "unable to read layer %q", layerID)
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
}
|
2017-01-27 22:38:32 +08:00
|
|
|
logrus.Debugf("layer list: %q", layers)
|
|
|
|
|
2017-06-01 01:44:41 +08:00
|
|
|
// Make a temporary directory to hold blobs.
|
2017-01-27 19:28:12 +08:00
|
|
|
path, err := ioutil.TempDir(os.TempDir(), Package)
|
2017-01-27 00:58:00 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-01-28 15:18:02 +08:00
|
|
|
logrus.Debugf("using %q to hold temporary data", path)
|
2017-01-27 00:58:00 +08:00
|
|
|
defer func() {
|
|
|
|
if src == nil {
|
|
|
|
err2 := os.RemoveAll(path)
|
|
|
|
if err2 != nil {
|
|
|
|
logrus.Errorf("error removing %q: %v", path, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2017-06-01 01:44:41 +08:00
|
|
|
// Build fresh copies of the configurations so that we don't mess with the values in the Builder
|
|
|
|
// object itself.
|
2017-05-18 05:01:06 +08:00
|
|
|
oimage := v1.Image{}
|
|
|
|
err = json.Unmarshal(i.oconfig, &oimage)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
dimage := docker.V2Image{}
|
|
|
|
err = json.Unmarshal(i.dconfig, &dimage)
|
2017-01-27 22:38:32 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-06-01 01:44:41 +08:00
|
|
|
// Start building manifests.
|
2017-05-18 05:01:06 +08:00
|
|
|
omanifest := v1.Manifest{
|
2017-01-27 00:58:00 +08:00
|
|
|
Versioned: specs.Versioned{
|
|
|
|
SchemaVersion: 2,
|
|
|
|
},
|
|
|
|
Config: v1.Descriptor{
|
|
|
|
MediaType: v1.MediaTypeImageConfig,
|
|
|
|
},
|
2017-02-11 00:48:15 +08:00
|
|
|
Layers: []v1.Descriptor{},
|
|
|
|
Annotations: i.annotations,
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
2017-05-18 05:01:06 +08:00
|
|
|
dmanifest := docker.V2S2Manifest{
|
|
|
|
V2Versioned: docker.V2Versioned{
|
|
|
|
SchemaVersion: 2,
|
|
|
|
MediaType: docker.V2S2MediaTypeManifest,
|
|
|
|
},
|
|
|
|
Config: docker.V2S2Descriptor{
|
|
|
|
MediaType: docker.V2S2MediaTypeImageConfig,
|
|
|
|
},
|
|
|
|
Layers: []docker.V2S2Descriptor{},
|
|
|
|
}
|
2017-01-27 00:58:00 +08:00
|
|
|
|
2017-05-18 05:01:06 +08:00
|
|
|
oimage.RootFS.Type = docker.TypeLayers
|
|
|
|
oimage.RootFS.DiffIDs = []string{}
|
|
|
|
dimage.RootFS = &docker.V2S2RootFS{}
|
|
|
|
dimage.RootFS.Type = docker.TypeLayers
|
|
|
|
dimage.RootFS.DiffIDs = []digest.Digest{}
|
2017-01-27 22:38:32 +08:00
|
|
|
|
2017-06-01 01:44:41 +08:00
|
|
|
// Extract each layer and compute its digests, both compressed (if requested) and uncompressed.
|
2017-01-27 00:58:00 +08:00
|
|
|
for _, layerID := range layers {
|
Take a shortcut when writing to local storage
When writing to local storage, take a couple of shortcuts: instead of
recompressing layers to ensure that the values we store in the image
manifest will be correct for content-addressibility, just pretend that
the layer ID is a blob hash value, and that it's a valid layer diffID.
Local storage doesn't generally care if these values are correct, and we
already have to recompute these values when exporting an image, but this
saves us quite a bit of time.
The image library's Copy() routine actually cares about and
sanity-checks these things, so if we're going to take advantage of the
shortcuts, we need to use its higher-level APIs to write a layer, write
the configuration, and write the manifest, then move those items that it
writes to an image with the right set of layers.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #141
Approved by: rhatdan
2017-06-01 01:56:25 +08:00
|
|
|
omediaType := v1.MediaTypeImageLayer
|
|
|
|
dmediaType := docker.V2S2MediaTypeUncompressedLayer
|
|
|
|
// Figure out which media type we want to call this. Assume no compression.
|
|
|
|
if i.compression != archive.Uncompressed {
|
|
|
|
switch i.compression {
|
|
|
|
case archive.Gzip:
|
|
|
|
omediaType = v1.MediaTypeImageLayerGzip
|
|
|
|
dmediaType = docker.V2S2MediaTypeLayer
|
|
|
|
logrus.Debugf("compressing layer %q with gzip", layerID)
|
|
|
|
case archive.Bzip2:
|
|
|
|
// Until the image specs define a media type for bzip2-compressed layers, even if we know
|
|
|
|
// how to decompress them, we can't try to compress layers with bzip2.
|
|
|
|
return nil, errors.New("media type for bzip2-compressed layers is not defined")
|
|
|
|
default:
|
|
|
|
logrus.Debugf("compressing layer %q with unknown compressor(?)", layerID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If we're not re-exporting the data, just fake up layer and diff IDs for the manifest.
|
|
|
|
if !i.exporting {
|
|
|
|
fakeLayerDigest := digest.NewDigestFromHex(digest.Canonical.String(), layerID)
|
|
|
|
// Add a note in the manifest about the layer. The blobs should be identified by their
|
|
|
|
// possibly-compressed blob digests, but just use the layer IDs here.
|
|
|
|
olayerDescriptor := v1.Descriptor{
|
|
|
|
MediaType: omediaType,
|
|
|
|
Digest: fakeLayerDigest,
|
|
|
|
Size: -1,
|
|
|
|
}
|
|
|
|
omanifest.Layers = append(omanifest.Layers, olayerDescriptor)
|
|
|
|
dlayerDescriptor := docker.V2S2Descriptor{
|
|
|
|
MediaType: dmediaType,
|
|
|
|
Digest: fakeLayerDigest,
|
|
|
|
Size: -1,
|
|
|
|
}
|
|
|
|
dmanifest.Layers = append(dmanifest.Layers, dlayerDescriptor)
|
|
|
|
// Add a note about the diffID, which should be uncompressed digest of the blob, but
|
|
|
|
// just use the layer ID here.
|
|
|
|
oimage.RootFS.DiffIDs = append(oimage.RootFS.DiffIDs, fakeLayerDigest.String())
|
|
|
|
dimage.RootFS.DiffIDs = append(dimage.RootFS.DiffIDs, fakeLayerDigest)
|
|
|
|
continue
|
|
|
|
}
|
2017-06-01 01:44:41 +08:00
|
|
|
// Start reading the layer.
|
2017-01-27 00:58:00 +08:00
|
|
|
rc, err := i.store.Diff("", layerID)
|
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return nil, errors.Wrapf(err, "error extracting layer %q", layerID)
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
defer rc.Close()
|
2017-06-01 01:44:41 +08:00
|
|
|
// Set up to decompress the layer, in case it's coming out compressed. Due to implementation
|
|
|
|
// differences, the result may not match the digest the blob had when it was originally imported,
|
|
|
|
// so we have to recompute all of this anyway if we want to be sure the digests we use will be
|
|
|
|
// correct.
|
2017-01-27 00:58:00 +08:00
|
|
|
uncompressed, err := archive.DecompressStream(rc)
|
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return nil, errors.Wrapf(err, "error decompressing layer %q", layerID)
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
defer uncompressed.Close()
|
2017-01-28 15:18:02 +08:00
|
|
|
srcHasher := digest.Canonical.Digester()
|
|
|
|
reader := io.TeeReader(uncompressed, srcHasher.Hash())
|
2017-06-01 01:44:41 +08:00
|
|
|
// Set up to write the possibly-recompressed blob.
|
2017-01-27 00:58:00 +08:00
|
|
|
layerFile, err := os.OpenFile(filepath.Join(path, "layer"), os.O_CREATE|os.O_WRONLY, 0600)
|
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return nil, errors.Wrapf(err, "error opening file for layer %q", layerID)
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
2017-01-28 15:18:02 +08:00
|
|
|
destHasher := digest.Canonical.Digester()
|
|
|
|
counter := ioutils.NewWriteCounter(layerFile)
|
|
|
|
multiWriter := io.MultiWriter(counter, destHasher.Hash())
|
Take a shortcut when writing to local storage
When writing to local storage, take a couple of shortcuts: instead of
recompressing layers to ensure that the values we store in the image
manifest will be correct for content-addressibility, just pretend that
the layer ID is a blob hash value, and that it's a valid layer diffID.
Local storage doesn't generally care if these values are correct, and we
already have to recompute these values when exporting an image, but this
saves us quite a bit of time.
The image library's Copy() routine actually cares about and
sanity-checks these things, so if we're going to take advantage of the
shortcuts, we need to use its higher-level APIs to write a layer, write
the configuration, and write the manifest, then move those items that it
writes to an image with the right set of layers.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #141
Approved by: rhatdan
2017-06-01 01:56:25 +08:00
|
|
|
// Compress the layer, if we're compressing it.
|
2017-06-01 01:44:41 +08:00
|
|
|
writer, err := archive.CompressStream(multiWriter, i.compression)
|
2017-01-28 15:18:02 +08:00
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return nil, errors.Wrapf(err, "error compressing layer %q", layerID)
|
2017-01-28 15:18:02 +08:00
|
|
|
}
|
2017-06-01 01:44:41 +08:00
|
|
|
size, err := io.Copy(writer, reader)
|
2017-01-27 00:58:00 +08:00
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return nil, errors.Wrapf(err, "error storing layer %q to file", layerID)
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
2017-06-01 01:44:41 +08:00
|
|
|
writer.Close()
|
2017-01-27 00:58:00 +08:00
|
|
|
layerFile.Close()
|
2017-01-28 15:18:02 +08:00
|
|
|
if i.compression == archive.Uncompressed {
|
|
|
|
if size != counter.Count {
|
2017-06-03 00:17:27 +08:00
|
|
|
return nil, errors.Errorf("error storing layer %q to file: inconsistent layer size (copied %d, wrote %d)", layerID, size, counter.Count)
|
2017-01-28 15:18:02 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
size = counter.Count
|
|
|
|
}
|
|
|
|
logrus.Debugf("layer %q size is %d bytes", layerID, size)
|
2017-06-01 01:44:41 +08:00
|
|
|
// Rename the layer so that we can more easily find it by digest later.
|
2017-01-28 15:18:02 +08:00
|
|
|
err = os.Rename(filepath.Join(path, "layer"), filepath.Join(path, destHasher.Digest().String()))
|
2017-03-07 23:41:25 +08:00
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return nil, errors.Wrapf(err, "error storing layer %q to file", layerID)
|
2017-03-07 23:41:25 +08:00
|
|
|
}
|
2017-06-01 01:44:41 +08:00
|
|
|
// Add a note in the manifest about the layer. The blobs are identified by their possibly-
|
|
|
|
// compressed blob digests.
|
2017-05-18 05:01:06 +08:00
|
|
|
olayerDescriptor := v1.Descriptor{
|
|
|
|
MediaType: omediaType,
|
|
|
|
Digest: destHasher.Digest(),
|
|
|
|
Size: size,
|
|
|
|
}
|
|
|
|
omanifest.Layers = append(omanifest.Layers, olayerDescriptor)
|
|
|
|
dlayerDescriptor := docker.V2S2Descriptor{
|
|
|
|
MediaType: dmediaType,
|
2017-02-14 00:44:47 +08:00
|
|
|
Digest: destHasher.Digest(),
|
2017-01-27 00:58:00 +08:00
|
|
|
Size: size,
|
|
|
|
}
|
2017-05-18 05:01:06 +08:00
|
|
|
dmanifest.Layers = append(dmanifest.Layers, dlayerDescriptor)
|
2017-06-01 01:44:41 +08:00
|
|
|
// Add a note about the diffID, which is always an uncompressed value.
|
2017-05-18 05:01:06 +08:00
|
|
|
oimage.RootFS.DiffIDs = append(oimage.RootFS.DiffIDs, srcHasher.Digest().String())
|
|
|
|
dimage.RootFS.DiffIDs = append(dimage.RootFS.DiffIDs, srcHasher.Digest())
|
2017-01-27 22:38:32 +08:00
|
|
|
}
|
|
|
|
|
2017-06-02 00:09:23 +08:00
|
|
|
if i.addHistory {
|
|
|
|
// Build history notes in the image configurations.
|
|
|
|
onews := v1.History{
|
|
|
|
Created: i.created,
|
|
|
|
CreatedBy: i.createdBy,
|
|
|
|
Author: oimage.Author,
|
|
|
|
EmptyLayer: false,
|
|
|
|
}
|
|
|
|
oimage.History = append(oimage.History, onews)
|
|
|
|
dnews := docker.V2S2History{
|
|
|
|
Created: i.created,
|
|
|
|
CreatedBy: i.createdBy,
|
|
|
|
Author: dimage.Author,
|
|
|
|
EmptyLayer: false,
|
|
|
|
}
|
|
|
|
dimage.History = append(dimage.History, dnews)
|
2017-05-18 05:01:06 +08:00
|
|
|
}
|
2017-01-27 22:38:32 +08:00
|
|
|
|
2017-06-01 01:44:41 +08:00
|
|
|
// Encode the image configuration blob.
|
2017-05-18 05:01:06 +08:00
|
|
|
oconfig, err := json.Marshal(&oimage)
|
2017-01-27 22:38:32 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
2017-05-18 05:01:06 +08:00
|
|
|
logrus.Debugf("OCIv1 config = %s", oconfig)
|
2017-01-27 22:38:32 +08:00
|
|
|
|
2017-06-01 01:44:41 +08:00
|
|
|
// Add the configuration blob to the manifest.
|
|
|
|
omanifest.Config.Digest = digest.Canonical.FromBytes(oconfig)
|
2017-05-18 05:01:06 +08:00
|
|
|
omanifest.Config.Size = int64(len(oconfig))
|
|
|
|
omanifest.Config.MediaType = v1.MediaTypeImageConfig
|
|
|
|
|
2017-06-01 01:44:41 +08:00
|
|
|
// Encode the manifest.
|
2017-05-18 05:01:06 +08:00
|
|
|
omanifestbytes, err := json.Marshal(&omanifest)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
logrus.Debugf("OCIv1 manifest = %s", omanifestbytes)
|
2017-01-27 00:58:00 +08:00
|
|
|
|
2017-06-01 01:44:41 +08:00
|
|
|
// Encode the image configuration blob.
|
2017-05-18 05:01:06 +08:00
|
|
|
dconfig, err := json.Marshal(&dimage)
|
2017-01-27 00:58:00 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-05-18 05:01:06 +08:00
|
|
|
logrus.Debugf("Docker v2s2 config = %s", dconfig)
|
2017-01-27 00:58:00 +08:00
|
|
|
|
2017-06-01 01:44:41 +08:00
|
|
|
// Add the configuration blob to the manifest.
|
|
|
|
dmanifest.Config.Digest = digest.Canonical.FromBytes(dconfig)
|
2017-05-18 05:01:06 +08:00
|
|
|
dmanifest.Config.Size = int64(len(dconfig))
|
|
|
|
dmanifest.Config.MediaType = docker.V2S2MediaTypeImageConfig
|
|
|
|
|
2017-06-01 01:44:41 +08:00
|
|
|
// Encode the manifest.
|
2017-05-18 05:01:06 +08:00
|
|
|
dmanifestbytes, err := json.Marshal(&dmanifest)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
logrus.Debugf("Docker v2s2 manifest = %s", dmanifestbytes)
|
|
|
|
|
2017-06-01 01:44:41 +08:00
|
|
|
// Decide which manifest and configuration blobs we'll actually output.
|
2017-05-18 05:01:06 +08:00
|
|
|
var config []byte
|
|
|
|
var manifest []byte
|
|
|
|
switch manifestType {
|
|
|
|
case v1.MediaTypeImageManifest:
|
|
|
|
manifest = omanifestbytes
|
2017-06-01 01:44:41 +08:00
|
|
|
config = oconfig
|
2017-05-18 05:01:06 +08:00
|
|
|
case docker.V2S2MediaTypeManifest:
|
|
|
|
manifest = dmanifestbytes
|
2017-06-01 01:44:41 +08:00
|
|
|
config = dconfig
|
2017-05-18 05:01:06 +08:00
|
|
|
default:
|
|
|
|
panic("unreachable code: unsupported manifest type")
|
|
|
|
}
|
2017-01-27 00:58:00 +08:00
|
|
|
src = &containerImageSource{
|
|
|
|
path: path,
|
|
|
|
ref: i,
|
|
|
|
store: i.store,
|
2017-06-02 00:09:23 +08:00
|
|
|
layerID: i.layerID,
|
|
|
|
names: i.names,
|
|
|
|
addHistory: i.addHistory,
|
2017-01-28 15:18:02 +08:00
|
|
|
compression: i.compression,
|
2017-05-18 05:01:06 +08:00
|
|
|
config: config,
|
2017-06-01 01:44:41 +08:00
|
|
|
configDigest: digest.Canonical.FromBytes(config),
|
2017-06-02 00:09:23 +08:00
|
|
|
manifest: manifest,
|
|
|
|
manifestType: manifestType,
|
Take a shortcut when writing to local storage
When writing to local storage, take a couple of shortcuts: instead of
recompressing layers to ensure that the values we store in the image
manifest will be correct for content-addressibility, just pretend that
the layer ID is a blob hash value, and that it's a valid layer diffID.
Local storage doesn't generally care if these values are correct, and we
already have to recompute these values when exporting an image, but this
saves us quite a bit of time.
The image library's Copy() routine actually cares about and
sanity-checks these things, so if we're going to take advantage of the
shortcuts, we need to use its higher-level APIs to write a layer, write
the configuration, and write the manifest, then move those items that it
writes to an image with the right set of layers.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #141
Approved by: rhatdan
2017-06-01 01:56:25 +08:00
|
|
|
exporting: i.exporting,
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
return src, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageRef) NewImageDestination(sc *types.SystemContext) (types.ImageDestination, error) {
|
2017-06-03 00:17:27 +08:00
|
|
|
return nil, errors.Errorf("can't write to a container")
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageRef) DockerReference() reference.Named {
|
|
|
|
return i.name
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageRef) StringWithinTransport() string {
|
2017-06-02 00:09:23 +08:00
|
|
|
if len(i.names) > 0 {
|
|
|
|
return i.names[0]
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageRef) DeleteImage(*types.SystemContext) error {
|
|
|
|
// we were never here
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageRef) PolicyConfigurationIdentity() string {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageRef) PolicyConfigurationNamespaces() []string {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageRef) Transport() types.ImageTransport {
|
|
|
|
return is.Transport
|
|
|
|
}
|
|
|
|
|
2017-03-22 04:38:50 +08:00
|
|
|
func (i *containerImageSource) Close() error {
|
2017-01-27 00:58:00 +08:00
|
|
|
err := os.RemoveAll(i.path)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("error removing %q: %v", i.path, err)
|
|
|
|
}
|
2017-03-22 04:38:50 +08:00
|
|
|
return err
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageSource) Reference() types.ImageReference {
|
|
|
|
return i.ref
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageSource) GetSignatures() ([][]byte, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
|
2017-06-03 00:17:27 +08:00
|
|
|
return []byte{}, "", errors.Errorf("TODO")
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageSource) GetManifest() ([]byte, string, error) {
|
2017-05-18 05:01:06 +08:00
|
|
|
return i.manifest, i.manifestType, nil
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageSource) GetBlob(blob types.BlobInfo) (reader io.ReadCloser, size int64, err error) {
|
|
|
|
if blob.Digest == i.configDigest {
|
|
|
|
logrus.Debugf("start reading config")
|
|
|
|
reader := bytes.NewReader(i.config)
|
|
|
|
closer := func() error {
|
|
|
|
logrus.Debugf("finished reading config")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return ioutils.NewReadCloserWrapper(reader, closer), reader.Size(), nil
|
|
|
|
}
|
|
|
|
layerFile, err := os.OpenFile(filepath.Join(i.path, blob.Digest.String()), os.O_RDONLY, 0600)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Debugf("error reading layer %q: %v", blob.Digest.String(), err)
|
|
|
|
return nil, -1, err
|
|
|
|
}
|
|
|
|
size = -1
|
|
|
|
st, err := layerFile.Stat()
|
|
|
|
if err != nil {
|
|
|
|
logrus.Warnf("error reading size of layer %q: %v", blob.Digest.String(), err)
|
|
|
|
} else {
|
|
|
|
size = st.Size()
|
|
|
|
}
|
|
|
|
logrus.Debugf("reading layer %q", blob.Digest.String())
|
2017-01-27 19:28:41 +08:00
|
|
|
closer := func() error {
|
|
|
|
layerFile.Close()
|
|
|
|
logrus.Debugf("finished reading layer %q", blob.Digest.String())
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return ioutils.NewReadCloserWrapper(layerFile, closer), size, nil
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
|
2017-06-02 00:09:23 +08:00
|
|
|
func (b *Builder) makeImageRef(manifestType string, exporting, addHistory bool, compress archive.Compression, names []string, layerID string) (types.ImageReference, error) {
|
2017-01-27 00:58:00 +08:00
|
|
|
var name reference.Named
|
2017-06-02 00:09:23 +08:00
|
|
|
if len(names) > 0 {
|
|
|
|
if parsed, err := reference.ParseNamed(names[0]); err == nil {
|
|
|
|
name = parsed
|
|
|
|
}
|
|
|
|
}
|
2017-05-18 05:02:40 +08:00
|
|
|
if manifestType == "" {
|
|
|
|
manifestType = OCIv1ImageManifest
|
|
|
|
}
|
2017-05-18 05:01:06 +08:00
|
|
|
oconfig, err := json.Marshal(&b.OCIv1)
|
|
|
|
if err != nil {
|
2017-06-02 00:09:23 +08:00
|
|
|
return nil, errors.Wrapf(err, "error encoding OCI-format image configuration")
|
2017-05-18 05:01:06 +08:00
|
|
|
}
|
|
|
|
dconfig, err := json.Marshal(&b.Docker)
|
Maintain multiple working container configs
Maintain the container configuration in multiple formats in the Buildah
object, initializing one based on the other, depending on which format
the source image used for its configuration.
Replace directly manipulated fields in the Buildah object (Annotations,
CreatedBy, OS, Architecture, Maintainer, User, Workdir, Env, Cmd,
Entrypoint, Expose, Labels, and Volumes) with accessor functions which
update both configurations and which read from whichever one we consider
to be authoritative. Drop Args because we weren't using them.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #102
Approved by: rhatdan
2017-05-16 23:08:52 +08:00
|
|
|
if err != nil {
|
2017-06-02 00:09:23 +08:00
|
|
|
return nil, errors.Wrapf(err, "error encoding docker-format image configuration")
|
Maintain multiple working container configs
Maintain the container configuration in multiple formats in the Buildah
object, initializing one based on the other, depending on which format
the source image used for its configuration.
Replace directly manipulated fields in the Buildah object (Annotations,
CreatedBy, OS, Architecture, Maintainer, User, Workdir, Env, Cmd,
Entrypoint, Expose, Labels, and Volumes) with accessor functions which
update both configurations and which read from whichever one we consider
to be authoritative. Drop Args because we weren't using them.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #102
Approved by: rhatdan
2017-05-16 23:08:52 +08:00
|
|
|
}
|
2017-02-11 00:48:15 +08:00
|
|
|
ref := &containerImageRef{
|
2017-05-18 05:02:40 +08:00
|
|
|
store: b.store,
|
|
|
|
compression: compress,
|
|
|
|
name: name,
|
2017-06-02 00:09:23 +08:00
|
|
|
names: names,
|
|
|
|
layerID: layerID,
|
|
|
|
addHistory: addHistory,
|
2017-05-18 05:02:40 +08:00
|
|
|
oconfig: oconfig,
|
|
|
|
dconfig: dconfig,
|
2017-06-01 01:44:41 +08:00
|
|
|
created: time.Now().UTC(),
|
2017-05-18 05:02:40 +08:00
|
|
|
createdBy: b.CreatedBy(),
|
|
|
|
annotations: b.Annotations(),
|
|
|
|
preferredManifestType: manifestType,
|
Take a shortcut when writing to local storage
When writing to local storage, take a couple of shortcuts: instead of
recompressing layers to ensure that the values we store in the image
manifest will be correct for content-addressibility, just pretend that
the layer ID is a blob hash value, and that it's a valid layer diffID.
Local storage doesn't generally care if these values are correct, and we
already have to recompute these values when exporting an image, but this
saves us quite a bit of time.
The image library's Copy() routine actually cares about and
sanity-checks these things, so if we're going to take advantage of the
shortcuts, we need to use its higher-level APIs to write a layer, write
the configuration, and write the manifest, then move those items that it
writes to an image with the right set of layers.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #141
Approved by: rhatdan
2017-06-01 01:56:25 +08:00
|
|
|
exporting: exporting,
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
2017-02-11 00:48:15 +08:00
|
|
|
return ref, nil
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
2017-06-02 00:09:23 +08:00
|
|
|
|
|
|
|
func (b *Builder) makeContainerImageRef(manifestType string, exporting bool, compress archive.Compression) (types.ImageReference, error) {
|
|
|
|
if manifestType == "" {
|
|
|
|
manifestType = OCIv1ImageManifest
|
|
|
|
}
|
|
|
|
container, err := b.store.Container(b.ContainerID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "error locating container %q", b.ContainerID)
|
|
|
|
}
|
|
|
|
return b.makeImageRef(manifestType, exporting, true, compress, container.Names, container.LayerID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Builder) makeImageImageRef(compress archive.Compression, names []string, layerID string) (types.ImageReference, error) {
|
|
|
|
return b.makeImageRef(manifest.GuessMIMEType(b.Manifest), true, false, compress, names, layerID)
|
|
|
|
}
|