mirror of
https://github.com/GoogleContainerTools/kaniko
synced 2026-10-19 00:01:50 +02:00
Added to vendor
This commit is contained in:
+131
@@ -0,0 +1,131 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/containers/image/types"
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ociArchiveImageDestination struct {
|
||||
ref ociArchiveReference
|
||||
unpackedDest types.ImageDestination
|
||||
tempDirRef tempDirOCIRef
|
||||
}
|
||||
|
||||
// newImageDestination returns an ImageDestination for writing to an existing directory.
|
||||
func newImageDestination(ctx *types.SystemContext, ref ociArchiveReference) (types.ImageDestination, error) {
|
||||
tempDirRef, err := createOCIRef(ref.image)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating oci reference")
|
||||
}
|
||||
unpackedDest, err := tempDirRef.ociRefExtracted.NewImageDestination(ctx)
|
||||
if err != nil {
|
||||
if err := tempDirRef.deleteTempDir(); err != nil {
|
||||
return nil, errors.Wrapf(err, "error deleting temp directory", tempDirRef.tempDirectory)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &ociArchiveImageDestination{ref: ref,
|
||||
unpackedDest: unpackedDest,
|
||||
tempDirRef: tempDirRef}, nil
|
||||
}
|
||||
|
||||
// Reference returns the reference used to set up this destination.
|
||||
func (d *ociArchiveImageDestination) Reference() types.ImageReference {
|
||||
return d.ref
|
||||
}
|
||||
|
||||
// Close removes resources associated with an initialized ImageDestination, if any
|
||||
// Close deletes the temp directory of the oci-archive image
|
||||
func (d *ociArchiveImageDestination) Close() error {
|
||||
defer d.tempDirRef.deleteTempDir()
|
||||
return d.unpackedDest.Close()
|
||||
}
|
||||
|
||||
func (d *ociArchiveImageDestination) SupportedManifestMIMETypes() []string {
|
||||
return d.unpackedDest.SupportedManifestMIMETypes()
|
||||
}
|
||||
|
||||
// SupportsSignatures returns an error (to be displayed to the user) if the destination certainly can't store signatures
|
||||
func (d *ociArchiveImageDestination) SupportsSignatures() error {
|
||||
return d.unpackedDest.SupportsSignatures()
|
||||
}
|
||||
|
||||
// ShouldCompressLayers returns true iff it is desirable to compress layer blobs written to this destination
|
||||
func (d *ociArchiveImageDestination) ShouldCompressLayers() bool {
|
||||
return d.unpackedDest.ShouldCompressLayers()
|
||||
}
|
||||
|
||||
// AcceptsForeignLayerURLs returns false iff foreign layers in manifest should be actually
|
||||
// uploaded to the image destination, true otherwise.
|
||||
func (d *ociArchiveImageDestination) AcceptsForeignLayerURLs() bool {
|
||||
return d.unpackedDest.AcceptsForeignLayerURLs()
|
||||
}
|
||||
|
||||
// MustMatchRuntimeOS returns true iff the destination can store only images targeted for the current runtime OS. False otherwise
|
||||
func (d *ociArchiveImageDestination) MustMatchRuntimeOS() bool {
|
||||
return d.unpackedDest.MustMatchRuntimeOS()
|
||||
}
|
||||
|
||||
// PutBlob writes contents of stream and returns data representing the result (with all data filled in).
|
||||
// inputInfo.Digest can be optionally provided if known; it is not mandatory for the implementation to verify it.
|
||||
// inputInfo.Size is the expected length of stream, if known.
|
||||
func (d *ociArchiveImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types.BlobInfo, error) {
|
||||
return d.unpackedDest.PutBlob(stream, inputInfo)
|
||||
}
|
||||
|
||||
// HasBlob returns true iff the image destination already contains a blob with the matching digest which can be reapplied using ReapplyBlob
|
||||
func (d *ociArchiveImageDestination) HasBlob(info types.BlobInfo) (bool, int64, error) {
|
||||
return d.unpackedDest.HasBlob(info)
|
||||
}
|
||||
|
||||
func (d *ociArchiveImageDestination) ReapplyBlob(info types.BlobInfo) (types.BlobInfo, error) {
|
||||
return d.unpackedDest.ReapplyBlob(info)
|
||||
}
|
||||
|
||||
// PutManifest writes manifest to the destination
|
||||
func (d *ociArchiveImageDestination) PutManifest(m []byte) error {
|
||||
return d.unpackedDest.PutManifest(m)
|
||||
}
|
||||
|
||||
func (d *ociArchiveImageDestination) PutSignatures(signatures [][]byte) error {
|
||||
return d.unpackedDest.PutSignatures(signatures)
|
||||
}
|
||||
|
||||
// Commit marks the process of storing the image as successful and asks for the image to be persisted
|
||||
// after the directory is made, it is tarred up into a file and the directory is deleted
|
||||
func (d *ociArchiveImageDestination) Commit() error {
|
||||
if err := d.unpackedDest.Commit(); err != nil {
|
||||
return errors.Wrapf(err, "error storing image %q", d.ref.image)
|
||||
}
|
||||
|
||||
// path of directory to tar up
|
||||
src := d.tempDirRef.tempDirectory
|
||||
// path to save tarred up file
|
||||
dst := d.ref.resolvedFile
|
||||
return tarDirectory(src, dst)
|
||||
}
|
||||
|
||||
// tar converts the directory at src and saves it to dst
|
||||
func tarDirectory(src, dst string) error {
|
||||
// input is a stream of bytes from the archive of the directory at path
|
||||
input, err := archive.Tar(src, archive.Uncompressed)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error retrieving stream of bytes from %q", src)
|
||||
}
|
||||
|
||||
// creates the tar file
|
||||
outFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error creating tar file %q", dst)
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
// copies the contents of the directory to the tar file
|
||||
_, err = io.Copy(outFile, input)
|
||||
|
||||
return err
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
ocilayout "github.com/containers/image/oci/layout"
|
||||
"github.com/containers/image/types"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ociArchiveImageSource struct {
|
||||
ref ociArchiveReference
|
||||
unpackedSrc types.ImageSource
|
||||
tempDirRef tempDirOCIRef
|
||||
}
|
||||
|
||||
// newImageSource returns an ImageSource for reading from an existing directory.
|
||||
// newImageSource untars the file and saves it in a temp directory
|
||||
func newImageSource(ctx *types.SystemContext, ref ociArchiveReference) (types.ImageSource, error) {
|
||||
tempDirRef, err := createUntarTempDir(ref)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error creating temp directory")
|
||||
}
|
||||
|
||||
unpackedSrc, err := tempDirRef.ociRefExtracted.NewImageSource(ctx)
|
||||
if err != nil {
|
||||
if err := tempDirRef.deleteTempDir(); err != nil {
|
||||
return nil, errors.Wrapf(err, "error deleting temp directory", tempDirRef.tempDirectory)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &ociArchiveImageSource{ref: ref,
|
||||
unpackedSrc: unpackedSrc,
|
||||
tempDirRef: tempDirRef}, nil
|
||||
}
|
||||
|
||||
// LoadManifestDescriptor loads the manifest
|
||||
func LoadManifestDescriptor(imgRef types.ImageReference) (imgspecv1.Descriptor, error) {
|
||||
ociArchRef, ok := imgRef.(ociArchiveReference)
|
||||
if !ok {
|
||||
return imgspecv1.Descriptor{}, errors.Errorf("error typecasting, need type ociArchiveReference")
|
||||
}
|
||||
tempDirRef, err := createUntarTempDir(ociArchRef)
|
||||
if err != nil {
|
||||
return imgspecv1.Descriptor{}, errors.Wrap(err, "error creating temp directory")
|
||||
}
|
||||
defer tempDirRef.deleteTempDir()
|
||||
|
||||
descriptor, err := ocilayout.LoadManifestDescriptor(tempDirRef.ociRefExtracted)
|
||||
if err != nil {
|
||||
return imgspecv1.Descriptor{}, errors.Wrap(err, "error loading index")
|
||||
}
|
||||
return descriptor, nil
|
||||
}
|
||||
|
||||
// Reference returns the reference used to set up this source.
|
||||
func (s *ociArchiveImageSource) Reference() types.ImageReference {
|
||||
return s.ref
|
||||
}
|
||||
|
||||
// Close removes resources associated with an initialized ImageSource, if any.
|
||||
// Close deletes the temporary directory at dst
|
||||
func (s *ociArchiveImageSource) Close() error {
|
||||
defer s.tempDirRef.deleteTempDir()
|
||||
return s.unpackedSrc.Close()
|
||||
}
|
||||
|
||||
// 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 (s *ociArchiveImageSource) GetManifest(instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
return s.unpackedSrc.GetManifest(instanceDigest)
|
||||
}
|
||||
|
||||
// GetBlob returns a stream for the specified blob, and the blob's size.
|
||||
func (s *ociArchiveImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
return s.unpackedSrc.GetBlob(info)
|
||||
}
|
||||
|
||||
// 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 (s *ociArchiveImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
|
||||
return s.unpackedSrc.GetSignatures(ctx, instanceDigest)
|
||||
}
|
||||
|
||||
// LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
|
||||
func (s *ociArchiveImageSource) LayerInfosForCopy() []types.BlobInfo {
|
||||
return nil
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/directory/explicitfilepath"
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/image"
|
||||
"github.com/containers/image/internal/tmpdir"
|
||||
"github.com/containers/image/oci/internal"
|
||||
ocilayout "github.com/containers/image/oci/layout"
|
||||
"github.com/containers/image/transports"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
transports.Register(Transport)
|
||||
}
|
||||
|
||||
// Transport is an ImageTransport for OCI archive
|
||||
// it creates an oci-archive tar file by calling into the OCI transport
|
||||
// tarring the directory created by oci and deleting the directory
|
||||
var Transport = ociArchiveTransport{}
|
||||
|
||||
type ociArchiveTransport struct{}
|
||||
|
||||
// ociArchiveReference is an ImageReference for OCI Archive paths
|
||||
type ociArchiveReference struct {
|
||||
file string
|
||||
resolvedFile string
|
||||
image string
|
||||
}
|
||||
|
||||
func (t ociArchiveTransport) Name() string {
|
||||
return "oci-archive"
|
||||
}
|
||||
|
||||
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix
|
||||
// into an ImageReference.
|
||||
func (t ociArchiveTransport) ParseReference(reference string) (types.ImageReference, error) {
|
||||
return ParseReference(reference)
|
||||
}
|
||||
|
||||
// ValidatePolicyConfigurationScope checks that scope is a valid name for a signature.PolicyTransportScopes keys
|
||||
func (t ociArchiveTransport) ValidatePolicyConfigurationScope(scope string) error {
|
||||
return internal.ValidateScope(scope)
|
||||
}
|
||||
|
||||
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an OCI ImageReference.
|
||||
func ParseReference(reference string) (types.ImageReference, error) {
|
||||
file, image := internal.SplitPathAndImage(reference)
|
||||
return NewReference(file, image)
|
||||
}
|
||||
|
||||
// NewReference returns an OCI reference for a file and a image.
|
||||
func NewReference(file, image string) (types.ImageReference, error) {
|
||||
resolved, err := explicitfilepath.ResolvePathToFullyExplicit(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := internal.ValidateOCIPath(file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := internal.ValidateImageName(image); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ociArchiveReference{file: file, resolvedFile: resolved, image: image}, nil
|
||||
}
|
||||
|
||||
func (ref ociArchiveReference) Transport() types.ImageTransport {
|
||||
return Transport
|
||||
}
|
||||
|
||||
// StringWithinTransport returns a string representation of the reference, which MUST be such that
|
||||
// reference.Transport().ParseReference(reference.StringWithinTransport()) returns an equivalent reference.
|
||||
func (ref ociArchiveReference) StringWithinTransport() string {
|
||||
return fmt.Sprintf("%s:%s", ref.file, ref.image)
|
||||
}
|
||||
|
||||
// DockerReference returns a Docker reference associated with this reference
|
||||
func (ref ociArchiveReference) DockerReference() reference.Named {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PolicyConfigurationIdentity returns a string representation of the reference, suitable for policy lookup.
|
||||
func (ref ociArchiveReference) PolicyConfigurationIdentity() string {
|
||||
// NOTE: ref.image is not a part of the image identity, because "$dir:$someimage" and "$dir:" may mean the
|
||||
// same image and the two can’t be statically disambiguated. Using at least the repository directory is
|
||||
// less granular but hopefully still useful.
|
||||
return fmt.Sprintf("%s", ref.resolvedFile)
|
||||
}
|
||||
|
||||
// PolicyConfigurationNamespaces returns a list of other policy configuration namespaces to search
|
||||
// for if explicit configuration for PolicyConfigurationIdentity() is not set
|
||||
func (ref ociArchiveReference) PolicyConfigurationNamespaces() []string {
|
||||
res := []string{}
|
||||
path := ref.resolvedFile
|
||||
for {
|
||||
lastSlash := strings.LastIndex(path, "/")
|
||||
// Note that we do not include "/"; it is redundant with the default "" global default,
|
||||
// and rejected by ociTransport.ValidatePolicyConfigurationScope above.
|
||||
if lastSlash == -1 || path == "/" {
|
||||
break
|
||||
}
|
||||
res = append(res, path)
|
||||
path = path[:lastSlash]
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// NewImage returns a types.ImageCloser for this reference, possibly specialized for this ImageTransport.
|
||||
// The caller must call .Close() on the returned ImageCloser.
|
||||
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
|
||||
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
|
||||
// WARNING: This may not do the right thing for a manifest list, see image.FromSource for details.
|
||||
func (ref ociArchiveReference) NewImage(ctx *types.SystemContext) (types.ImageCloser, error) {
|
||||
src, err := newImageSource(ctx, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return image.FromSource(ctx, src)
|
||||
}
|
||||
|
||||
// NewImageSource returns a types.ImageSource for this reference.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func (ref ociArchiveReference) NewImageSource(ctx *types.SystemContext) (types.ImageSource, error) {
|
||||
return newImageSource(ctx, ref)
|
||||
}
|
||||
|
||||
// NewImageDestination returns a types.ImageDestination for this reference.
|
||||
// The caller must call .Close() on the returned ImageDestination.
|
||||
func (ref ociArchiveReference) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
|
||||
return newImageDestination(ctx, ref)
|
||||
}
|
||||
|
||||
// DeleteImage deletes the named image from the registry, if supported.
|
||||
func (ref ociArchiveReference) DeleteImage(ctx *types.SystemContext) error {
|
||||
return errors.Errorf("Deleting images not implemented for oci: images")
|
||||
}
|
||||
|
||||
// struct to store the ociReference and temporary directory returned by createOCIRef
|
||||
type tempDirOCIRef struct {
|
||||
tempDirectory string
|
||||
ociRefExtracted types.ImageReference
|
||||
}
|
||||
|
||||
// deletes the temporary directory created
|
||||
func (t *tempDirOCIRef) deleteTempDir() error {
|
||||
return os.RemoveAll(t.tempDirectory)
|
||||
}
|
||||
|
||||
// createOCIRef creates the oci reference of the image
|
||||
func createOCIRef(image string) (tempDirOCIRef, error) {
|
||||
dir, err := ioutil.TempDir(tmpdir.TemporaryDirectoryForBigFiles(), "oci")
|
||||
if err != nil {
|
||||
return tempDirOCIRef{}, errors.Wrapf(err, "error creating temp directory")
|
||||
}
|
||||
ociRef, err := ocilayout.NewReference(dir, image)
|
||||
if err != nil {
|
||||
return tempDirOCIRef{}, err
|
||||
}
|
||||
|
||||
tempDirRef := tempDirOCIRef{tempDirectory: dir, ociRefExtracted: ociRef}
|
||||
return tempDirRef, nil
|
||||
}
|
||||
|
||||
// creates the temporary directory and copies the tarred content to it
|
||||
func createUntarTempDir(ref ociArchiveReference) (tempDirOCIRef, error) {
|
||||
tempDirRef, err := createOCIRef(ref.image)
|
||||
if err != nil {
|
||||
return tempDirOCIRef{}, errors.Wrap(err, "error creating oci reference")
|
||||
}
|
||||
src := ref.resolvedFile
|
||||
dst := tempDirRef.tempDirectory
|
||||
if err := archive.UntarPath(src, dst); err != nil {
|
||||
if err := tempDirRef.deleteTempDir(); err != nil {
|
||||
return tempDirOCIRef{}, errors.Wrapf(err, "error deleting temp directory %q", tempDirRef.tempDirectory)
|
||||
}
|
||||
return tempDirOCIRef{}, errors.Wrapf(err, "error untarring file %q", tempDirRef.tempDirectory)
|
||||
}
|
||||
return tempDirRef, nil
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// annotation spex from https://github.com/opencontainers/image-spec/blob/master/annotations.md#pre-defined-annotation-keys
|
||||
const (
|
||||
separator = `(?:[-._:@+]|--)`
|
||||
alphanum = `(?:[A-Za-z0-9]+)`
|
||||
component = `(?:` + alphanum + `(?:` + separator + alphanum + `)*)`
|
||||
)
|
||||
|
||||
var refRegexp = regexp.MustCompile(`^` + component + `(?:/` + component + `)*$`)
|
||||
var windowsRefRegexp = regexp.MustCompile(`^([a-zA-Z]:\\.+?):(.*)$`)
|
||||
|
||||
// ValidateImageName returns nil if the image name is empty or matches the open-containers image name specs.
|
||||
// In any other case an error is returned.
|
||||
func ValidateImageName(image string) error {
|
||||
if len(image) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
if !refRegexp.MatchString(image) {
|
||||
err = errors.Errorf("Invalid image %s", image)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SplitPathAndImage tries to split the provided OCI reference into the OCI path and image.
|
||||
// Neither path nor image parts are validated at this stage.
|
||||
func SplitPathAndImage(reference string) (string, string) {
|
||||
if runtime.GOOS == "windows" {
|
||||
return splitPathAndImageWindows(reference)
|
||||
}
|
||||
return splitPathAndImageNonWindows(reference)
|
||||
}
|
||||
|
||||
func splitPathAndImageWindows(reference string) (string, string) {
|
||||
groups := windowsRefRegexp.FindStringSubmatch(reference)
|
||||
// nil group means no match
|
||||
if groups == nil {
|
||||
return reference, ""
|
||||
}
|
||||
|
||||
// we expect three elements. First one full match, second the capture group for the path and
|
||||
// the third the capture group for the image
|
||||
if len(groups) != 3 {
|
||||
return reference, ""
|
||||
}
|
||||
return groups[1], groups[2]
|
||||
}
|
||||
|
||||
func splitPathAndImageNonWindows(reference string) (string, string) {
|
||||
sep := strings.SplitN(reference, ":", 2)
|
||||
path := sep[0]
|
||||
|
||||
var image string
|
||||
if len(sep) == 2 {
|
||||
image = sep[1]
|
||||
}
|
||||
return path, image
|
||||
}
|
||||
|
||||
// ValidateOCIPath takes the OCI path and validates it.
|
||||
func ValidateOCIPath(path string) error {
|
||||
if runtime.GOOS == "windows" {
|
||||
// On Windows we must allow for a ':' as part of the path
|
||||
if strings.Count(path, ":") > 1 {
|
||||
return errors.Errorf("Invalid OCI reference: path %s contains more than one colon", path)
|
||||
}
|
||||
} else {
|
||||
if strings.Contains(path, ":") {
|
||||
return errors.Errorf("Invalid OCI reference: path %s contains a colon", path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateScope validates a policy configuration scope for an OCI transport.
|
||||
func ValidateScope(scope string) error {
|
||||
var err error
|
||||
if runtime.GOOS == "windows" {
|
||||
err = validateScopeWindows(scope)
|
||||
} else {
|
||||
err = validateScopeNonWindows(scope)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cleaned := filepath.Clean(scope)
|
||||
if cleaned != scope {
|
||||
return errors.Errorf(`Invalid scope %s: Uses non-canonical path format, perhaps try with path %s`, scope, cleaned)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateScopeWindows(scope string) error {
|
||||
matched, _ := regexp.Match(`^[a-zA-Z]:\\`, []byte(scope))
|
||||
if !matched {
|
||||
return errors.Errorf("Invalid scope '%s'. Must be an absolute path", scope)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateScopeNonWindows(scope string) error {
|
||||
if !strings.HasPrefix(scope, "/") {
|
||||
return errors.Errorf("Invalid scope %s: must be an absolute path", scope)
|
||||
}
|
||||
|
||||
// Refuse also "/", otherwise "/" and "" would have the same semantics,
|
||||
// and "" could be unexpectedly shadowed by the "/" entry.
|
||||
if scope == "/" {
|
||||
return errors.New(`Invalid scope "/": Use the generic default scope ""`)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
package layout
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
imgspec "github.com/opencontainers/image-spec/specs-go"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
type ociImageDestination struct {
|
||||
ref ociReference
|
||||
index imgspecv1.Index
|
||||
sharedBlobDir string
|
||||
}
|
||||
|
||||
// newImageDestination returns an ImageDestination for writing to an existing directory.
|
||||
func newImageDestination(ctx *types.SystemContext, ref ociReference) (types.ImageDestination, error) {
|
||||
if ref.image == "" {
|
||||
return nil, errors.Errorf("cannot save image with empty image.ref.name")
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
d := &ociImageDestination{ref: ref, index: *index}
|
||||
if ctx != nil {
|
||||
d.sharedBlobDir = ctx.OCISharedBlobDirPath
|
||||
}
|
||||
|
||||
if err := ensureDirectoryExists(d.ref.dir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Per the OCI image specification, layouts MUST have a "blobs" subdirectory,
|
||||
// but it MAY be empty (e.g. if we never end up calling PutBlob)
|
||||
// https://github.com/opencontainers/image-spec/blame/7c889fafd04a893f5c5f50b7ab9963d5d64e5242/image-layout.md#L19
|
||||
if err := ensureDirectoryExists(filepath.Join(d.ref.dir, "blobs")); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
|
||||
// e.g. it should use the public hostname instead of the result of resolving CNAMEs or following redirects.
|
||||
func (d *ociImageDestination) Reference() types.ImageReference {
|
||||
return d.ref
|
||||
}
|
||||
|
||||
// Close removes resources associated with an initialized ImageDestination, if any.
|
||||
func (d *ociImageDestination) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ociImageDestination) SupportedManifestMIMETypes() []string {
|
||||
return []string{
|
||||
imgspecv1.MediaTypeImageManifest,
|
||||
}
|
||||
}
|
||||
|
||||
// SupportsSignatures returns an error (to be displayed to the user) if the destination certainly can't store signatures.
|
||||
// Note: It is still possible for PutSignatures to fail if SupportsSignatures returns nil.
|
||||
func (d *ociImageDestination) SupportsSignatures() error {
|
||||
return errors.Errorf("Pushing signatures for OCI images is not supported")
|
||||
}
|
||||
|
||||
// ShouldCompressLayers returns true iff it is desirable to compress layer blobs written to this destination.
|
||||
func (d *ociImageDestination) ShouldCompressLayers() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// AcceptsForeignLayerURLs returns false iff foreign layers in manifest should be actually
|
||||
// uploaded to the image destination, true otherwise.
|
||||
func (d *ociImageDestination) AcceptsForeignLayerURLs() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// MustMatchRuntimeOS returns true iff the destination can store only images targeted for the current runtime OS. False otherwise.
|
||||
func (d *ociImageDestination) MustMatchRuntimeOS() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// PutBlob writes contents of stream and returns data representing the result (with all data filled in).
|
||||
// inputInfo.Digest can be optionally provided if known; it is not mandatory for the implementation to verify it.
|
||||
// inputInfo.Size is the expected length of stream, if known.
|
||||
// WARNING: The contents of stream are being verified on the fly. Until stream.Read() returns io.EOF, the contents of the data SHOULD NOT be available
|
||||
// to any other readers for download using the supplied digest.
|
||||
// If stream.Read() at any time, ESPECIALLY at end of input, returns an error, PutBlob MUST 1) fail, and 2) delete any data stored so far.
|
||||
func (d *ociImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types.BlobInfo, error) {
|
||||
blobFile, err := ioutil.TempFile(d.ref.dir, "oci-put-blob")
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
succeeded := false
|
||||
explicitClosed := false
|
||||
defer func() {
|
||||
if !explicitClosed {
|
||||
blobFile.Close()
|
||||
}
|
||||
if !succeeded {
|
||||
os.Remove(blobFile.Name())
|
||||
}
|
||||
}()
|
||||
|
||||
digester := digest.Canonical.Digester()
|
||||
tee := io.TeeReader(stream, digester.Hash())
|
||||
|
||||
size, err := io.Copy(blobFile, tee)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
computedDigest := digester.Digest()
|
||||
if inputInfo.Size != -1 && size != inputInfo.Size {
|
||||
return types.BlobInfo{}, errors.Errorf("Size mismatch when copying %s, expected %d, got %d", computedDigest, inputInfo.Size, size)
|
||||
}
|
||||
if err := blobFile.Sync(); err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
|
||||
// On POSIX systems, blobFile was created with mode 0600, so we need to make it readable.
|
||||
// On Windows, the “permissions of newly created files” argument to syscall.Open is
|
||||
// ignored and the file is already readable; besides, blobFile.Chmod, i.e. syscall.Fchmod,
|
||||
// always fails on Windows.
|
||||
if runtime.GOOS != "windows" {
|
||||
if err := blobFile.Chmod(0644); err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
}
|
||||
|
||||
blobPath, err := d.ref.blobPath(computedDigest, d.sharedBlobDir)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
if err := ensureParentDirectoryExists(blobPath); err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
|
||||
// need to explicitly close the file, since a rename won't otherwise not work on Windows
|
||||
blobFile.Close()
|
||||
explicitClosed = true
|
||||
if err := os.Rename(blobFile.Name(), blobPath); err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
succeeded = true
|
||||
return types.BlobInfo{Digest: computedDigest, Size: size}, nil
|
||||
}
|
||||
|
||||
// HasBlob returns true iff the image destination already contains a blob with the matching digest which can be reapplied using ReapplyBlob.
|
||||
// Unlike PutBlob, the digest can not be empty. If HasBlob returns true, the size of the blob must also be returned.
|
||||
// If the destination does not contain the blob, or it is unknown, HasBlob ordinarily returns (false, -1, nil);
|
||||
// it returns a non-nil error only on an unexpected failure.
|
||||
func (d *ociImageDestination) HasBlob(info types.BlobInfo) (bool, int64, error) {
|
||||
if info.Digest == "" {
|
||||
return false, -1, errors.Errorf(`"Can not check for a blob with unknown digest`)
|
||||
}
|
||||
blobPath, err := d.ref.blobPath(info.Digest, d.sharedBlobDir)
|
||||
if err != nil {
|
||||
return false, -1, err
|
||||
}
|
||||
finfo, err := os.Stat(blobPath)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
return false, -1, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, -1, err
|
||||
}
|
||||
return true, finfo.Size(), nil
|
||||
}
|
||||
|
||||
func (d *ociImageDestination) ReapplyBlob(info types.BlobInfo) (types.BlobInfo, error) {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// PutManifest writes manifest to the destination.
|
||||
// FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
|
||||
// If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema),
|
||||
// but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError.
|
||||
func (d *ociImageDestination) PutManifest(m []byte) error {
|
||||
digest, err := manifest.Digest(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
desc := imgspecv1.Descriptor{}
|
||||
desc.Digest = digest
|
||||
// TODO(runcom): beaware and add support for OCI manifest list
|
||||
desc.MediaType = imgspecv1.MediaTypeImageManifest
|
||||
desc.Size = int64(len(m))
|
||||
|
||||
blobPath, err := d.ref.blobPath(digest, d.sharedBlobDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureParentDirectoryExists(blobPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(blobPath, m, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.ref.image == "" {
|
||||
return errors.Errorf("cannot save image with empyt image.ref.name")
|
||||
}
|
||||
|
||||
annotations := make(map[string]string)
|
||||
annotations["org.opencontainers.image.ref.name"] = d.ref.image
|
||||
desc.Annotations = annotations
|
||||
desc.Platform = &imgspecv1.Platform{
|
||||
Architecture: runtime.GOARCH,
|
||||
OS: runtime.GOOS,
|
||||
}
|
||||
d.addManifest(&desc)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
d.index.Manifests = append(d.index.Manifests, *desc)
|
||||
}
|
||||
|
||||
func (d *ociImageDestination) PutSignatures(signatures [][]byte) error {
|
||||
if len(signatures) != 0 {
|
||||
return errors.Errorf("Pushing signatures for OCI images is not supported")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Commit marks the process of storing the image as successful and asks for the image to be persisted.
|
||||
// WARNING: This does not have any transactional semantics:
|
||||
// - Uploaded data MAY be visible to others before Commit() is called
|
||||
// - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
|
||||
func (d *ociImageDestination) Commit() error {
|
||||
if err := ioutil.WriteFile(d.ref.ociLayoutPath(), []byte(`{"imageLayoutVersion": "1.0.0"}`), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
indexJSON, err := json.Marshal(d.index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
package layout
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/containers/image/pkg/tlsclientconfig"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/opencontainers/go-digest"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ociImageSource struct {
|
||||
ref ociReference
|
||||
descriptor imgspecv1.Descriptor
|
||||
client *http.Client
|
||||
sharedBlobDir string
|
||||
}
|
||||
|
||||
// newImageSource returns an ImageSource for reading from an existing directory.
|
||||
func newImageSource(ctx *types.SystemContext, ref ociReference) (types.ImageSource, error) {
|
||||
tr := tlsclientconfig.NewTransport()
|
||||
tr.TLSClientConfig = tlsconfig.ServerDefault()
|
||||
|
||||
if ctx != nil && ctx.OCICertPath != "" {
|
||||
if err := tlsclientconfig.SetupCertificates(ctx.OCICertPath, tr.TLSClientConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tr.TLSClientConfig.InsecureSkipVerify = ctx.OCIInsecureSkipTLSVerify
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
client.Transport = tr
|
||||
descriptor, err := ref.getManifestDescriptor()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d := &ociImageSource{ref: ref, descriptor: descriptor, client: client}
|
||||
if ctx != nil {
|
||||
// TODO(jonboulle): check dir existence?
|
||||
d.sharedBlobDir = ctx.OCISharedBlobDirPath
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// Reference returns the reference used to set up this source.
|
||||
func (s *ociImageSource) Reference() types.ImageReference {
|
||||
return s.ref
|
||||
}
|
||||
|
||||
// Close removes resources associated with an initialized ImageSource, if any.
|
||||
func (s *ociImageSource) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 (s *ociImageSource) GetManifest(instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
var dig digest.Digest
|
||||
var mimeType string
|
||||
if instanceDigest == nil {
|
||||
dig = digest.Digest(s.descriptor.Digest)
|
||||
mimeType = s.descriptor.MediaType
|
||||
} else {
|
||||
dig = *instanceDigest
|
||||
// XXX: instanceDigest means that we don't immediately have the context of what
|
||||
// mediaType the manifest has. In OCI this means that we don't know
|
||||
// what reference it came from, so we just *assume* that its
|
||||
// MediaTypeImageManifest.
|
||||
// FIXME: We should actually be able to look up the manifest in the index,
|
||||
// and see the MIME type there.
|
||||
mimeType = imgspecv1.MediaTypeImageManifest
|
||||
}
|
||||
|
||||
manifestPath, err := s.ref.blobPath(dig, s.sharedBlobDir)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
m, err := ioutil.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return m, mimeType, nil
|
||||
}
|
||||
|
||||
// GetBlob returns a stream for the specified blob, and the blob's size.
|
||||
func (s *ociImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
if len(info.URLs) != 0 {
|
||||
return s.getExternalBlob(info.URLs)
|
||||
}
|
||||
|
||||
path, err := s.ref.blobPath(info.Digest, s.sharedBlobDir)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
r, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
fi, err := r.Stat()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return r, fi.Size(), 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 (s *ociImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
|
||||
return [][]byte{}, nil
|
||||
}
|
||||
|
||||
func (s *ociImageSource) getExternalBlob(urls []string) (io.ReadCloser, int64, error) {
|
||||
errWrap := errors.New("failed fetching external blob from all urls")
|
||||
for _, url := range urls {
|
||||
resp, err := s.client.Get(url)
|
||||
if err != nil {
|
||||
errWrap = errors.Wrapf(errWrap, "fetching %s failed %s", url, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
resp.Body.Close()
|
||||
errWrap = errors.Wrapf(errWrap, "fetching %s failed, response code not 200", url)
|
||||
continue
|
||||
}
|
||||
|
||||
return resp.Body, getBlobSize(resp), nil
|
||||
}
|
||||
|
||||
return nil, 0, errWrap
|
||||
}
|
||||
|
||||
// LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
|
||||
func (s *ociImageSource) LayerInfosForCopy() []types.BlobInfo {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getBlobSize(resp *http.Response) int64 {
|
||||
size, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
|
||||
if err != nil {
|
||||
size = -1
|
||||
}
|
||||
return size
|
||||
}
|
||||
+255
@@ -0,0 +1,255 @@
|
||||
package layout
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/directory/explicitfilepath"
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/image"
|
||||
"github.com/containers/image/oci/internal"
|
||||
"github.com/containers/image/transports"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
transports.Register(Transport)
|
||||
}
|
||||
|
||||
// Transport is an ImageTransport for OCI directories.
|
||||
var Transport = ociTransport{}
|
||||
|
||||
type ociTransport struct{}
|
||||
|
||||
func (t ociTransport) Name() string {
|
||||
return "oci"
|
||||
}
|
||||
|
||||
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an ImageReference.
|
||||
func (t ociTransport) ParseReference(reference string) (types.ImageReference, error) {
|
||||
return ParseReference(reference)
|
||||
}
|
||||
|
||||
// ValidatePolicyConfigurationScope checks that scope is a valid name for a signature.PolicyTransportScopes keys
|
||||
// (i.e. a valid PolicyConfigurationIdentity() or PolicyConfigurationNamespaces() return value).
|
||||
// It is acceptable to allow an invalid value which will never be matched, it can "only" cause user confusion.
|
||||
// scope passed to this function will not be "", that value is always allowed.
|
||||
func (t ociTransport) ValidatePolicyConfigurationScope(scope string) error {
|
||||
return internal.ValidateScope(scope)
|
||||
}
|
||||
|
||||
// ociReference is an ImageReference for OCI directory paths.
|
||||
type ociReference struct {
|
||||
// Note that the interpretation of paths below depends on the underlying filesystem state, which may change under us at any time!
|
||||
// Either of the paths may point to a different, or no, inode over time. resolvedDir may contain symbolic links, and so on.
|
||||
|
||||
// Generally we follow the intent of the user, and use the "dir" member for filesystem operations (e.g. the user can use a relative path to avoid
|
||||
// being exposed to symlinks and renames in the parent directories to the working directory).
|
||||
// (But in general, we make no attempt to be completely safe against concurrent hostile filesystem modifications.)
|
||||
dir string // As specified by the user. May be relative, contain symlinks, etc.
|
||||
resolvedDir string // Absolute path with no symlinks, at least at the time of its creation. Primarily used for policy namespaces.
|
||||
image string // If image=="", it means the only image in the index.json is used
|
||||
}
|
||||
|
||||
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an OCI ImageReference.
|
||||
func ParseReference(reference string) (types.ImageReference, error) {
|
||||
dir, image := internal.SplitPathAndImage(reference)
|
||||
return NewReference(dir, image)
|
||||
}
|
||||
|
||||
// NewReference returns an OCI reference for a directory and a image.
|
||||
//
|
||||
// We do not expose an API supplying the resolvedDir; we could, but recomputing it
|
||||
// is generally cheap enough that we prefer being confident about the properties of resolvedDir.
|
||||
func NewReference(dir, image string) (types.ImageReference, error) {
|
||||
resolved, err := explicitfilepath.ResolvePathToFullyExplicit(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := internal.ValidateOCIPath(dir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = internal.ValidateImageName(image); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ociReference{dir: dir, resolvedDir: resolved, image: image}, nil
|
||||
}
|
||||
|
||||
func (ref ociReference) Transport() types.ImageTransport {
|
||||
return Transport
|
||||
}
|
||||
|
||||
// StringWithinTransport returns a string representation of the reference, which MUST be such that
|
||||
// reference.Transport().ParseReference(reference.StringWithinTransport()) returns an equivalent reference.
|
||||
// NOTE: The returned string is not promised to be equal to the original input to ParseReference;
|
||||
// e.g. default attribute values omitted by the user may be filled in in the return value, or vice versa.
|
||||
// WARNING: Do not use the return value in the UI to describe an image, it does not contain the Transport().Name() prefix.
|
||||
func (ref ociReference) StringWithinTransport() string {
|
||||
return fmt.Sprintf("%s:%s", ref.dir, ref.image)
|
||||
}
|
||||
|
||||
// DockerReference returns a Docker reference associated with this reference
|
||||
// (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent,
|
||||
// not e.g. after redirect or alias processing), or nil if unknown/not applicable.
|
||||
func (ref ociReference) DockerReference() reference.Named {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PolicyConfigurationIdentity returns a string representation of the reference, suitable for policy lookup.
|
||||
// This MUST reflect user intent, not e.g. after processing of third-party redirects or aliases;
|
||||
// The value SHOULD be fully explicit about its semantics, with no hidden defaults, AND canonical
|
||||
// (i.e. various references with exactly the same semantics should return the same configuration identity)
|
||||
// It is fine for the return value to be equal to StringWithinTransport(), and it is desirable but
|
||||
// not required/guaranteed that it will be a valid input to Transport().ParseReference().
|
||||
// Returns "" if configuration identities for these references are not supported.
|
||||
func (ref ociReference) PolicyConfigurationIdentity() string {
|
||||
// NOTE: ref.image is not a part of the image identity, because "$dir:$someimage" and "$dir:" may mean the
|
||||
// same image and the two can’t be statically disambiguated. Using at least the repository directory is
|
||||
// less granular but hopefully still useful.
|
||||
return fmt.Sprintf("%s", ref.resolvedDir)
|
||||
}
|
||||
|
||||
// PolicyConfigurationNamespaces returns a list of other policy configuration namespaces to search
|
||||
// for if explicit configuration for PolicyConfigurationIdentity() is not set. The list will be processed
|
||||
// in order, terminating on first match, and an implicit "" is always checked at the end.
|
||||
// It is STRONGLY recommended for the first element, if any, to be a prefix of PolicyConfigurationIdentity(),
|
||||
// and each following element to be a prefix of the element preceding it.
|
||||
func (ref ociReference) PolicyConfigurationNamespaces() []string {
|
||||
res := []string{}
|
||||
path := ref.resolvedDir
|
||||
for {
|
||||
lastSlash := strings.LastIndex(path, "/")
|
||||
// Note that we do not include "/"; it is redundant with the default "" global default,
|
||||
// and rejected by ociTransport.ValidatePolicyConfigurationScope above.
|
||||
if lastSlash == -1 || path == "/" {
|
||||
break
|
||||
}
|
||||
res = append(res, path)
|
||||
path = path[:lastSlash]
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// NewImage returns a types.ImageCloser for this reference, possibly specialized for this ImageTransport.
|
||||
// The caller must call .Close() on the returned ImageCloser.
|
||||
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
|
||||
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
|
||||
// WARNING: This may not do the right thing for a manifest list, see image.FromSource for details.
|
||||
func (ref ociReference) NewImage(ctx *types.SystemContext) (types.ImageCloser, error) {
|
||||
src, err := newImageSource(ctx, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return image.FromSource(ctx, src)
|
||||
}
|
||||
|
||||
// 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 nil, err
|
||||
}
|
||||
defer indexJSON.Close()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
var d *imgspecv1.Descriptor
|
||||
if ref.image == "" {
|
||||
// return manifest if only one image is in the oci directory
|
||||
if len(index.Manifests) == 1 {
|
||||
d = &index.Manifests[0]
|
||||
} else {
|
||||
// ask user to choose image when more than one image in the oci directory
|
||||
return imgspecv1.Descriptor{}, errors.Wrapf(err, "more than one image in oci, choose an image")
|
||||
}
|
||||
} else {
|
||||
// if image specified, look through all manifests for a match
|
||||
for _, md := range index.Manifests {
|
||||
if md.MediaType != imgspecv1.MediaTypeImageManifest {
|
||||
continue
|
||||
}
|
||||
refName, ok := md.Annotations["org.opencontainers.image.ref.name"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if refName == ref.image {
|
||||
d = &md
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if d == nil {
|
||||
return imgspecv1.Descriptor{}, fmt.Errorf("no descriptor found for reference %q", ref.image)
|
||||
}
|
||||
return *d, nil
|
||||
}
|
||||
|
||||
// LoadManifestDescriptor loads the manifest descriptor to be used to retrieve the image name
|
||||
// when pulling an image
|
||||
func LoadManifestDescriptor(imgRef types.ImageReference) (imgspecv1.Descriptor, error) {
|
||||
ociRef, ok := imgRef.(ociReference)
|
||||
if !ok {
|
||||
return imgspecv1.Descriptor{}, errors.Errorf("error typecasting, need type ociRef")
|
||||
}
|
||||
return ociRef.getManifestDescriptor()
|
||||
}
|
||||
|
||||
// NewImageSource returns a types.ImageSource for this reference.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func (ref ociReference) NewImageSource(ctx *types.SystemContext) (types.ImageSource, error) {
|
||||
return newImageSource(ctx, ref)
|
||||
}
|
||||
|
||||
// NewImageDestination returns a types.ImageDestination for this reference.
|
||||
// The caller must call .Close() on the returned ImageDestination.
|
||||
func (ref ociReference) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
|
||||
return newImageDestination(ctx, ref)
|
||||
}
|
||||
|
||||
// DeleteImage deletes the named image from the registry, if supported.
|
||||
func (ref ociReference) DeleteImage(ctx *types.SystemContext) error {
|
||||
return errors.Errorf("Deleting images not implemented for oci: images")
|
||||
}
|
||||
|
||||
// ociLayoutPath returns a path for the oci-layout within a directory using OCI conventions.
|
||||
func (ref ociReference) ociLayoutPath() string {
|
||||
return filepath.Join(ref.dir, "oci-layout")
|
||||
}
|
||||
|
||||
// indexPath returns a path for the index.json within a directory using OCI conventions.
|
||||
func (ref ociReference) indexPath() string {
|
||||
return filepath.Join(ref.dir, "index.json")
|
||||
}
|
||||
|
||||
// blobPath returns a path for a blob within a directory using OCI image-layout conventions.
|
||||
func (ref ociReference) blobPath(digest digest.Digest, sharedBlobDir string) (string, error) {
|
||||
if err := digest.Validate(); err != nil {
|
||||
return "", errors.Wrapf(err, "unexpected digest reference %s", digest)
|
||||
}
|
||||
blobDir := filepath.Join(ref.dir, "blobs")
|
||||
if sharedBlobDir != "" {
|
||||
blobDir = sharedBlobDir
|
||||
}
|
||||
return filepath.Join(blobDir, digest.Algorithm().String(), digest.Hex()), nil
|
||||
}
|
||||
Reference in New Issue
Block a user