2017-02-11 00:48:15 +08:00
|
|
|
package buildah
|
2017-01-27 00:58:00 +08:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"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"
|
|
|
|
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"
|
|
|
|
)
|
|
|
|
|
|
|
|
type containerImageRef struct {
|
2017-01-28 15:18:02 +08:00
|
|
|
store storage.Store
|
|
|
|
container *storage.Container
|
|
|
|
compression archive.Compression
|
|
|
|
name reference.Named
|
|
|
|
config []byte
|
2017-02-11 00:48:15 +08:00
|
|
|
createdBy string
|
|
|
|
annotations map[string]string
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
type containerImageSource struct {
|
|
|
|
path string
|
|
|
|
ref *containerImageRef
|
|
|
|
store storage.Store
|
|
|
|
container *storage.Container
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageRef) NewImageSource(sc *types.SystemContext, manifestTypes []string) (src types.ImageSource, err error) {
|
|
|
|
if len(manifestTypes) > 0 {
|
|
|
|
ok := false
|
|
|
|
for _, mt := range manifestTypes {
|
|
|
|
if mt == v1.MediaTypeImageManifest {
|
|
|
|
ok = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("no supported manifest types")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
layers := []string{}
|
|
|
|
layerID := i.container.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 {
|
|
|
|
return nil, fmt.Errorf("unable to read layer %q: %v", layerID, err)
|
|
|
|
}
|
|
|
|
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 {
|
|
|
|
return nil, fmt.Errorf("unable to read layer %q: %v", layerID, err)
|
|
|
|
}
|
|
|
|
}
|
2017-01-27 22:38:32 +08:00
|
|
|
logrus.Debugf("layer list: %q", layers)
|
|
|
|
|
2017-02-14 00:44:47 +08:00
|
|
|
created := time.Now().UTC()
|
2017-01-27 00:58:00 +08:00
|
|
|
|
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-01-27 22:38:32 +08:00
|
|
|
image := v1.Image{}
|
|
|
|
err = json.Unmarshal(i.config, &image)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-01-27 00:58:00 +08:00
|
|
|
manifest := v1.Manifest{
|
|
|
|
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-01-27 22:38:32 +08:00
|
|
|
image.RootFS.Type = "layers"
|
|
|
|
image.RootFS.DiffIDs = []string{}
|
|
|
|
|
2017-01-27 00:58:00 +08:00
|
|
|
for _, layerID := range layers {
|
|
|
|
rc, err := i.store.Diff("", layerID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error extracting layer %q: %v", layerID, err)
|
|
|
|
}
|
|
|
|
defer rc.Close()
|
|
|
|
uncompressed, err := archive.DecompressStream(rc)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error decompressing layer %q: %v", layerID, err)
|
|
|
|
}
|
|
|
|
defer uncompressed.Close()
|
2017-01-28 15:18:02 +08:00
|
|
|
srcHasher := digest.Canonical.Digester()
|
|
|
|
reader := io.TeeReader(uncompressed, srcHasher.Hash())
|
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 {
|
|
|
|
return nil, fmt.Errorf("error opening file for layer %q: %v", layerID, err)
|
|
|
|
}
|
2017-01-28 15:18:02 +08:00
|
|
|
destHasher := digest.Canonical.Digester()
|
|
|
|
counter := ioutils.NewWriteCounter(layerFile)
|
|
|
|
multiWriter := io.MultiWriter(counter, destHasher.Hash())
|
2017-01-28 15:34:15 +08:00
|
|
|
mediaType := v1.MediaTypeImageLayer
|
2017-01-28 15:18:02 +08:00
|
|
|
if i.compression != archive.Uncompressed {
|
|
|
|
switch i.compression {
|
|
|
|
case archive.Gzip:
|
2017-02-14 00:44:47 +08:00
|
|
|
mediaType = v1.MediaTypeImageLayerGzip
|
2017-01-28 15:18:02 +08:00
|
|
|
logrus.Debugf("compressing layer %q with gzip", layerID)
|
|
|
|
case archive.Bzip2:
|
|
|
|
logrus.Debugf("compressing layer %q with bzip2", layerID)
|
|
|
|
default:
|
|
|
|
logrus.Debugf("compressing layer %q with unknown compressor(?)", layerID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
compressor, err := archive.CompressStream(multiWriter, i.compression)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error compressing layer %q: %v", layerID, err)
|
|
|
|
}
|
|
|
|
size, err := io.Copy(compressor, reader)
|
2017-01-27 00:58:00 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error storing layer %q to file: %v", layerID, err)
|
|
|
|
}
|
2017-01-28 15:18:02 +08:00
|
|
|
compressor.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 {
|
|
|
|
return nil, fmt.Errorf("error storing layer %q to file: inconsistent layer size (copied %d, wrote %d)", layerID, size, counter.Count)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
size = counter.Count
|
|
|
|
}
|
|
|
|
logrus.Debugf("layer %q size is %d bytes", layerID, size)
|
|
|
|
err = os.Rename(filepath.Join(path, "layer"), filepath.Join(path, destHasher.Digest().String()))
|
2017-03-07 23:41:25 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error storing layer %q to file: %v", layerID, err)
|
|
|
|
}
|
2017-01-27 00:58:00 +08:00
|
|
|
layerDescriptor := v1.Descriptor{
|
2017-01-28 15:34:15 +08:00
|
|
|
MediaType: mediaType,
|
2017-02-14 00:44:47 +08:00
|
|
|
Digest: destHasher.Digest(),
|
2017-01-27 00:58:00 +08:00
|
|
|
Size: size,
|
|
|
|
}
|
|
|
|
manifest.Layers = append(manifest.Layers, layerDescriptor)
|
2017-04-04 05:44:23 +08:00
|
|
|
lastLayerDiffID := destHasher.Digest().String()
|
2017-02-11 00:48:15 +08:00
|
|
|
image.RootFS.DiffIDs = append(image.RootFS.DiffIDs, lastLayerDiffID)
|
2017-01-27 22:38:32 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
news := v1.History{
|
2017-02-14 00:44:47 +08:00
|
|
|
Created: created,
|
2017-02-11 00:48:15 +08:00
|
|
|
CreatedBy: i.createdBy,
|
2017-01-27 22:38:32 +08:00
|
|
|
Author: image.Author,
|
|
|
|
EmptyLayer: false,
|
|
|
|
}
|
2017-02-03 07:30:26 +08:00
|
|
|
image.History = append(image.History, news)
|
2017-01-27 22:38:32 +08:00
|
|
|
|
|
|
|
config, err := json.Marshal(&image)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
2017-01-27 22:38:32 +08:00
|
|
|
logrus.Debugf("config = %s\n", config)
|
|
|
|
i.config = config
|
|
|
|
|
2017-02-14 00:44:47 +08:00
|
|
|
manifest.Config.Digest = digest.FromBytes(config)
|
2017-01-27 22:38:32 +08:00
|
|
|
manifest.Config.Size = int64(len(config))
|
2017-01-27 00:58:00 +08:00
|
|
|
|
|
|
|
mfest, err := json.Marshal(&manifest)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
logrus.Debugf("manifest = %s\n", mfest)
|
|
|
|
|
|
|
|
src = &containerImageSource{
|
|
|
|
path: path,
|
|
|
|
ref: i,
|
|
|
|
store: i.store,
|
|
|
|
container: i.container,
|
2017-01-28 15:18:02 +08:00
|
|
|
compression: i.compression,
|
2017-01-27 00:58:00 +08:00
|
|
|
manifest: mfest,
|
|
|
|
config: i.config,
|
2017-01-28 05:20:28 +08:00
|
|
|
configDigest: digest.FromBytes(config),
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
return src, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageRef) NewImageDestination(sc *types.SystemContext) (types.ImageDestination, error) {
|
|
|
|
return nil, fmt.Errorf("can't write to a container")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageRef) DockerReference() reference.Named {
|
|
|
|
return i.name
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageRef) StringWithinTransport() string {
|
|
|
|
if len(i.container.Names) > 0 {
|
|
|
|
return i.container.Names[0]
|
|
|
|
}
|
|
|
|
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) {
|
|
|
|
return []byte{}, "", fmt.Errorf("TODO")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageSource) GetManifest() ([]byte, string, error) {
|
|
|
|
return i.manifest, v1.MediaTypeImageManifest, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
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-02-11 00:48:15 +08:00
|
|
|
func (b *Builder) makeContainerImageRef(compress archive.Compression) (types.ImageReference, error) {
|
2017-01-27 00:58:00 +08:00
|
|
|
var name reference.Named
|
2017-05-17 23:53:28 +08:00
|
|
|
container, err := b.store.Container(b.ContainerID)
|
2017-02-11 00:48:15 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-01-27 00:58:00 +08:00
|
|
|
if len(container.Names) > 0 {
|
|
|
|
name, err = reference.ParseNamed(container.Names[0])
|
|
|
|
if err != nil {
|
|
|
|
name = nil
|
|
|
|
}
|
|
|
|
}
|
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
|
|
|
config, err := json.Marshal(&b.OCIv1)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-02-11 00:48:15 +08:00
|
|
|
ref := &containerImageRef{
|
|
|
|
store: b.store,
|
2017-01-28 15:18:02 +08:00
|
|
|
container: container,
|
|
|
|
compression: compress,
|
|
|
|
name: name,
|
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
|
|
|
config: config,
|
|
|
|
createdBy: b.CreatedBy(),
|
|
|
|
annotations: b.Annotations(),
|
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
|
|
|
}
|