Vendor in latest containers/image

Adds the tarball transport, which is used by the kpod import command

Signed-off-by: Urvashi Mohnani <umohnani@redhat.com>

Closes: #12
Approved by: rhatdan
This commit is contained in:
Urvashi Mohnani
2017-11-06 11:04:34 -05:00
committed by Atomic Bot
parent df54adeaae
commit d086beb7ab
12 changed files with 543 additions and 21 deletions

View File

@@ -27,12 +27,23 @@ func newImageDestination(ref ociReference) (types.ImageDestination, error) {
if ref.image == "" {
return nil, errors.Errorf("cannot save image with empty image.ref.name")
}
index := imgspecv1.Index{
Versioned: imgspec.Versioned{
SchemaVersion: 2,
},
var index *imgspecv1.Index
if indexExists(ref) {
var err error
index, err = ref.getIndex()
if err != nil {
return nil, err
}
} else {
index = &imgspecv1.Index{
Versioned: imgspec.Versioned{
SchemaVersion: 2,
},
}
}
return &ociImageDestination{ref: ref, index: index}, nil
return &ociImageDestination{ref: ref, index: *index}, nil
}
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
@@ -191,23 +202,20 @@ func (d *ociImageDestination) PutManifest(m []byte) error {
Architecture: runtime.GOARCH,
OS: runtime.GOOS,
}
d.index.Manifests = append(d.index.Manifests, desc)
d.addManifest(&desc)
return nil
}
func ensureDirectoryExists(path string) error {
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
if err := os.MkdirAll(path, 0755); err != nil {
return err
func (d *ociImageDestination) addManifest(desc *imgspecv1.Descriptor) {
for i, manifest := range d.index.Manifests {
if manifest.Annotations["org.opencontainers.image.ref.name"] == desc.Annotations["org.opencontainers.image.ref.name"] {
// TODO Should there first be a cleanup based on the descriptor we are going to replace?
d.index.Manifests[i] = *desc
return
}
}
return nil
}
// ensureParentDirectoryExists ensures the parent of the supplied path exists.
func ensureParentDirectoryExists(path string) error {
return ensureDirectoryExists(filepath.Dir(path))
d.index.Manifests = append(d.index.Manifests, *desc)
}
func (d *ociImageDestination) PutSignatures(signatures [][]byte) error {
@@ -231,3 +239,30 @@ func (d *ociImageDestination) Commit() error {
}
return ioutil.WriteFile(d.ref.indexPath(), indexJSON, 0644)
}
func ensureDirectoryExists(path string) error {
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
if err := os.MkdirAll(path, 0755); err != nil {
return err
}
}
return nil
}
// ensureParentDirectoryExists ensures the parent of the supplied path exists.
func ensureParentDirectoryExists(path string) error {
return ensureDirectoryExists(filepath.Dir(path))
}
// indexExists checks whether the index location specified in the OCI reference exists.
// The implementation is opinionated, since in case of unexpected errors false is returned
func indexExists(ref ociReference) bool {
_, err := os.Stat(ref.indexPath())
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return true
}

View File

@@ -189,14 +189,25 @@ func (ref ociReference) NewImage(ctx *types.SystemContext) (types.Image, error)
return image.FromSource(src)
}
func (ref ociReference) getManifestDescriptor() (imgspecv1.Descriptor, error) {
// getIndex returns a pointer to the index references by this ociReference. If an error occurs opening an index nil is returned together
// with an error.
func (ref ociReference) getIndex() (*imgspecv1.Index, error) {
indexJSON, err := os.Open(ref.indexPath())
if err != nil {
return imgspecv1.Descriptor{}, err
return nil, err
}
defer indexJSON.Close()
index := imgspecv1.Index{}
if err := json.NewDecoder(indexJSON).Decode(&index); err != nil {
index := &imgspecv1.Index{}
if err := json.NewDecoder(indexJSON).Decode(index); err != nil {
return nil, err
}
return index, nil
}
func (ref ociReference) getManifestDescriptor() (imgspecv1.Descriptor, error) {
index, err := ref.getIndex()
if err != nil {
return imgspecv1.Descriptor{}, err
}

View File

@@ -1,3 +1,5 @@
// +build !containers_image_storage_stub
package storage
import (

View File

@@ -1,3 +1,5 @@
// +build !containers_image_storage_stub
package storage
import (

View File

@@ -1,3 +1,5 @@
// +build !containers_image_storage_stub
package storage
import (

48
vendor/github.com/containers/image/tarball/doc.go generated vendored Normal file
View File

@@ -0,0 +1,48 @@
// Package tarball provides a way to generate images using one or more layer
// tarballs and an optional template configuration.
//
// An example:
// package main
//
// import (
// "fmt"
//
// cp "github.com/containers/image/copy"
// "github.com/containers/image/tarball"
// "github.com/containers/image/transports/alltransports"
//
// imgspecv1 "github.com/containers/image/transports/alltransports"
// )
//
// func imageFromTarball() {
// src, err := alltransports.ParseImageName("tarball:/var/cache/mock/fedora-26-x86_64/root_cache/cache.tar.gz")
// // - or -
// // src, err := tarball.Transport.ParseReference("/var/cache/mock/fedora-26-x86_64/root_cache/cache.tar.gz")
// if err != nil {
// panic(err)
// }
// updater, ok := src.(tarball.ConfigUpdater)
// if !ok {
// panic("unexpected: a tarball reference should implement tarball.ConfigUpdater")
// }
// config := imgspecv1.Image{
// Config: imgspecv1.ImageConfig{
// Cmd: []string{"/bin/bash"},
// },
// }
// annotations := make(map[string]string)
// annotations[imgspecv1.AnnotationDescription] = "test image built from a mock root cache"
// err = updater.ConfigUpdate(config, annotations)
// if err != nil {
// panic(err)
// }
// dest, err := alltransports.ParseImageName("docker-daemon:mock:latest")
// if err != nil {
// panic(err)
// }
// err = cp.Image(nil, dest, src, nil)
// if err != nil {
// panic(err)
// }
// }
package tarball

View File

@@ -0,0 +1,88 @@
package tarball
import (
"fmt"
"os"
"strings"
"github.com/containers/image/docker/reference"
"github.com/containers/image/image"
"github.com/containers/image/types"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
)
// ConfigUpdater is an interface that ImageReferences for "tarball" images also
// implement. It can be used to set values for a configuration, and to set
// image annotations which will be present in the images returned by the
// reference's NewImage() or NewImageSource() methods.
type ConfigUpdater interface {
ConfigUpdate(config imgspecv1.Image, annotations map[string]string) error
}
type tarballReference struct {
transport types.ImageTransport
config imgspecv1.Image
annotations map[string]string
filenames []string
stdin []byte
}
// ConfigUpdate updates the image's default configuration and adds annotations
// which will be visible in source images created using this reference.
func (r *tarballReference) ConfigUpdate(config imgspecv1.Image, annotations map[string]string) error {
r.config = config
if r.annotations == nil {
r.annotations = make(map[string]string)
}
for k, v := range annotations {
r.annotations[k] = v
}
return nil
}
func (r *tarballReference) Transport() types.ImageTransport {
return r.transport
}
func (r *tarballReference) StringWithinTransport() string {
return strings.Join(r.filenames, ":")
}
func (r *tarballReference) DockerReference() reference.Named {
return nil
}
func (r *tarballReference) PolicyConfigurationIdentity() string {
return ""
}
func (r *tarballReference) PolicyConfigurationNamespaces() []string {
return nil
}
func (r *tarballReference) NewImage(ctx *types.SystemContext) (types.Image, error) {
src, err := r.NewImageSource(ctx)
if err != nil {
return nil, err
}
img, err := image.FromSource(src)
if err != nil {
src.Close()
return nil, err
}
return img, nil
}
func (r *tarballReference) DeleteImage(ctx *types.SystemContext) error {
for _, filename := range r.filenames {
if err := os.Remove(filename); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("error removing %q: %v", filename, err)
}
}
return nil
}
func (r *tarballReference) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
return nil, fmt.Errorf("destination not implemented yet")
}

View File

@@ -0,0 +1,250 @@
package tarball
import (
"bytes"
"compress/gzip"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"runtime"
"strings"
"time"
"github.com/containers/image/types"
digest "github.com/opencontainers/go-digest"
imgspecs "github.com/opencontainers/image-spec/specs-go"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
)
type tarballImageSource struct {
reference tarballReference
filenames []string
diffIDs []digest.Digest
diffSizes []int64
blobIDs []digest.Digest
blobSizes []int64
blobTypes []string
config []byte
configID digest.Digest
configSize int64
manifest []byte
}
func (r *tarballReference) NewImageSource(ctx *types.SystemContext) (types.ImageSource, error) {
// Gather up the digests, sizes, and date information for all of the files.
filenames := []string{}
diffIDs := []digest.Digest{}
diffSizes := []int64{}
blobIDs := []digest.Digest{}
blobSizes := []int64{}
blobTimes := []time.Time{}
blobTypes := []string{}
for _, filename := range r.filenames {
var file *os.File
var err error
var blobSize int64
var blobTime time.Time
var reader io.Reader
if filename == "-" {
blobSize = int64(len(r.stdin))
blobTime = time.Now()
reader = bytes.NewReader(r.stdin)
} else {
file, err = os.Open(filename)
if err != nil {
return nil, fmt.Errorf("error opening %q for reading: %v", filename, err)
}
defer file.Close()
reader = file
fileinfo, err := file.Stat()
if err != nil {
return nil, fmt.Errorf("error reading size of %q: %v", filename, err)
}
blobSize = fileinfo.Size()
blobTime = fileinfo.ModTime()
}
// Default to assuming the layer is compressed.
layerType := imgspecv1.MediaTypeImageLayerGzip
// Set up to digest the file as it is.
blobIDdigester := digest.Canonical.Digester()
reader = io.TeeReader(reader, blobIDdigester.Hash())
// Set up to digest the file after we maybe decompress it.
diffIDdigester := digest.Canonical.Digester()
uncompressed, err := gzip.NewReader(reader)
if err == nil {
// It is compressed, so the diffID is the digest of the uncompressed version
reader = io.TeeReader(uncompressed, diffIDdigester.Hash())
} else {
// It is not compressed, so the diffID and the blobID are going to be the same
diffIDdigester = blobIDdigester
layerType = imgspecv1.MediaTypeImageLayer
uncompressed = nil
}
n, err := io.Copy(ioutil.Discard, reader)
if err != nil {
return nil, fmt.Errorf("error reading %q: %v", filename, err)
}
if uncompressed != nil {
uncompressed.Close()
}
// Grab our uncompressed and possibly-compressed digests and sizes.
filenames = append(filenames, filename)
diffIDs = append(diffIDs, diffIDdigester.Digest())
diffSizes = append(diffSizes, n)
blobIDs = append(blobIDs, blobIDdigester.Digest())
blobSizes = append(blobSizes, blobSize)
blobTimes = append(blobTimes, blobTime)
blobTypes = append(blobTypes, layerType)
}
// Build the rootfs and history for the configuration blob.
rootfs := imgspecv1.RootFS{
Type: "layers",
DiffIDs: diffIDs,
}
created := time.Time{}
history := []imgspecv1.History{}
// Pick up the layer comment from the configuration's history list, if one is set.
comment := "imported from tarball"
if len(r.config.History) > 0 && r.config.History[0].Comment != "" {
comment = r.config.History[0].Comment
}
for i := range diffIDs {
createdBy := fmt.Sprintf("/bin/sh -c #(nop) ADD file:%s in %c", diffIDs[i].Hex(), os.PathSeparator)
history = append(history, imgspecv1.History{
Created: &blobTimes[i],
CreatedBy: createdBy,
Comment: comment,
})
// Use the mtime of the most recently modified file as the image's creation time.
if created.Before(blobTimes[i]) {
created = blobTimes[i]
}
}
// Pick up other defaults from the config in the reference.
config := r.config
if config.Created == nil {
config.Created = &created
}
if config.Architecture == "" {
config.Architecture = runtime.GOARCH
}
if config.OS == "" {
config.OS = runtime.GOOS
}
config.RootFS = rootfs
config.History = history
// Encode and digest the image configuration blob.
configBytes, err := json.Marshal(&config)
if err != nil {
return nil, fmt.Errorf("error generating configuration blob for %q: %v", strings.Join(r.filenames, separator), err)
}
configID := digest.Canonical.FromBytes(configBytes)
configSize := int64(len(configBytes))
// Populate a manifest with the configuration blob and the file as the single layer.
layerDescriptors := []imgspecv1.Descriptor{}
for i := range blobIDs {
layerDescriptors = append(layerDescriptors, imgspecv1.Descriptor{
Digest: blobIDs[i],
Size: blobSizes[i],
MediaType: blobTypes[i],
})
}
annotations := make(map[string]string)
for k, v := range r.annotations {
annotations[k] = v
}
manifest := imgspecv1.Manifest{
Versioned: imgspecs.Versioned{
SchemaVersion: 2,
},
Config: imgspecv1.Descriptor{
Digest: configID,
Size: configSize,
MediaType: imgspecv1.MediaTypeImageConfig,
},
Layers: layerDescriptors,
Annotations: annotations,
}
// Encode the manifest.
manifestBytes, err := json.Marshal(&manifest)
if err != nil {
return nil, fmt.Errorf("error generating manifest for %q: %v", strings.Join(r.filenames, separator), err)
}
// Return the image.
src := &tarballImageSource{
reference: *r,
filenames: filenames,
diffIDs: diffIDs,
diffSizes: diffSizes,
blobIDs: blobIDs,
blobSizes: blobSizes,
blobTypes: blobTypes,
config: configBytes,
configID: configID,
configSize: configSize,
manifest: manifestBytes,
}
return src, nil
}
func (is *tarballImageSource) Close() error {
return nil
}
func (is *tarballImageSource) GetBlob(blobinfo types.BlobInfo) (io.ReadCloser, int64, error) {
// We should only be asked about things in the manifest. Maybe the configuration blob.
if blobinfo.Digest == is.configID {
return ioutil.NopCloser(bytes.NewBuffer(is.config)), is.configSize, nil
}
// Maybe one of the layer blobs.
for i := range is.blobIDs {
if blobinfo.Digest == is.blobIDs[i] {
// We want to read that layer: open the file or memory block and hand it back.
if is.filenames[i] == "-" {
return ioutil.NopCloser(bytes.NewBuffer(is.reference.stdin)), int64(len(is.reference.stdin)), nil
}
reader, err := os.Open(is.filenames[i])
if err != nil {
return nil, -1, fmt.Errorf("error opening %q: %v", is.filenames[i], err)
}
return reader, is.blobSizes[i], nil
}
}
return nil, -1, fmt.Errorf("no blob with digest %q found", blobinfo.Digest.String())
}
func (is *tarballImageSource) GetManifest() ([]byte, string, error) {
return is.manifest, imgspecv1.MediaTypeImageManifest, nil
}
func (*tarballImageSource) GetSignatures(context.Context) ([][]byte, error) {
return nil, nil
}
func (*tarballImageSource) GetTargetManifest(digest.Digest) ([]byte, string, error) {
return nil, "", fmt.Errorf("manifest lists are not supported by the %q transport", transportName)
}
func (is *tarballImageSource) Reference() types.ImageReference {
return &is.reference
}
// UpdatedLayerInfos() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
func (*tarballImageSource) UpdatedLayerInfos() []types.BlobInfo {
return nil
}

View File

@@ -0,0 +1,66 @@
package tarball
import (
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/containers/image/transports"
"github.com/containers/image/types"
)
const (
transportName = "tarball"
separator = ":"
)
var (
// Transport implements the types.ImageTransport interface for "tarball:" images,
// which are makeshift images constructed using one or more possibly-compressed tar
// archives.
Transport = &tarballTransport{}
)
type tarballTransport struct {
}
func (t *tarballTransport) Name() string {
return transportName
}
func (t *tarballTransport) ParseReference(reference string) (types.ImageReference, error) {
var stdin []byte
var err error
filenames := strings.Split(reference, separator)
for _, filename := range filenames {
if filename == "-" {
stdin, err = ioutil.ReadAll(os.Stdin)
if err != nil {
return nil, fmt.Errorf("error buffering stdin: %v", err)
}
continue
}
f, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("error opening %q: %v", filename, err)
}
f.Close()
}
ref := &tarballReference{
transport: t,
filenames: filenames,
stdin: stdin,
}
return ref, nil
}
func (t *tarballTransport) ValidatePolicyConfigurationScope(scope string) error {
// See the explanation in daemonReference.PolicyConfigurationIdentity.
return errors.New(`tarball: does not support any scopes except the default "" one`)
}
func init() {
transports.Register(Transport)
}

View File

@@ -13,8 +13,9 @@ import (
_ "github.com/containers/image/oci/archive"
_ "github.com/containers/image/oci/layout"
_ "github.com/containers/image/openshift"
_ "github.com/containers/image/tarball"
// The ostree transport is registered by ostree*.go
_ "github.com/containers/image/storage"
// The storage transport is registered by storage*.go
"github.com/containers/image/transports"
"github.com/containers/image/types"
"github.com/pkg/errors"

View File

@@ -0,0 +1,8 @@
// +build !containers_image_storage_stub
package alltransports
import (
// Register the storage transport
_ "github.com/containers/image/storage"
)

View File

@@ -0,0 +1,9 @@
// +build containers_image_storage_stub
package alltransports
import "github.com/containers/image/transports"
func init() {
transports.Register(transports.NewStubTransport("storage"))
}