0651d3a8de
Bump containers/image to 3d0304a02154dddc8f97cc833aa0861cea5e9ade, and containers/storage to 0d32dfce498e06c132c60dac945081bf44c22464. Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
260 lines
8.2 KiB
Go
260 lines
8.2 KiB
Go
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())
|
|
}
|
|
|
|
// GetManifest returns the image's manifest along with its MIME type (which may be empty when it can't be determined but the manifest is available).
|
|
// It may use a remote (= slow) service.
|
|
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list);
|
|
// this never happens if the primary manifest is not a manifest list (e.g. if the source never returns manifest lists).
|
|
func (is *tarballImageSource) GetManifest(instanceDigest *digest.Digest) ([]byte, string, error) {
|
|
if instanceDigest != nil {
|
|
return nil, "", fmt.Errorf("manifest lists are not supported by the %q transport", transportName)
|
|
}
|
|
return is.manifest, imgspecv1.MediaTypeImageManifest, nil
|
|
}
|
|
|
|
// GetSignatures returns the image's signatures. It may use a remote (= slow) service.
|
|
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for
|
|
// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list
|
|
// (e.g. if the source never returns manifest lists).
|
|
func (*tarballImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
|
|
if instanceDigest != nil {
|
|
return nil, fmt.Errorf("manifest lists are not supported by the %q transport", transportName)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (is *tarballImageSource) Reference() types.ImageReference {
|
|
return &is.reference
|
|
}
|
|
|
|
// LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
|
|
func (*tarballImageSource) LayerInfosForCopy() []types.BlobInfo {
|
|
return nil
|
|
}
|