Merge pull request #523 from runcom/up-c-storage

vendor: upgrade containers/storage
This commit is contained in:
Mrunal Patel 2017-05-17 13:44:16 -07:00 committed by GitHub
commit 0e2af44d72
114 changed files with 11464 additions and 1003 deletions

View file

@ -1,5 +1,5 @@
{
"memo": "a13cb8f78972694597c79648073de6966e267da85e1a2bcb70d2a0fdd8e8ddec",
"memo": "2005de643a6067047c2105429c375537db3ff6899467af5f8204d70a2c745fc4",
"projects": [
{
"name": "cloud.google.com/go",
@ -92,7 +92,7 @@
{
"name": "github.com/containers/image",
"branch": "master",
"revision": "efae29995d4846ffa6163eb4d466fd61bda43aae",
"revision": "11dfba3e17bbb9c1ef50ee3687e5525e56dbd151",
"packages": [
"copy",
"directory",
@ -121,8 +121,9 @@
{
"name": "github.com/containers/storage",
"branch": "master",
"revision": "d10d8680af74070b362637408a7fe28c4b1f1eff",
"revision": "2c75d14b978bff468e7d5ec3ff8a003eca443209",
"packages": [
".",
"drivers",
"drivers/aufs",
"drivers/btrfs",
@ -154,7 +155,6 @@
"pkg/stringid",
"pkg/system",
"pkg/truncindex",
"storage",
"storageversion"
]
},
@ -510,8 +510,8 @@
},
{
"name": "github.com/opencontainers/image-spec",
"branch": "master",
"revision": "5fccf471dfc2e02d5509daf0feddeab86f80510a",
"version": "v1.0.0-rc5",
"revision": "5faaada8762b465d5ce8a8da27b92d577a1aa576",
"packages": [
"specs-go",
"specs-go/v1"

View file

@ -6,6 +6,9 @@
"github.com/containers/image": {
"branch": "master"
},
"github.com/opencontainers/image-spec": {
"version": "v1.0.0-rc5"
},
"github.com/containers/storage": {
"branch": "master"
},

View file

@ -7,7 +7,7 @@ import (
istorage "github.com/containers/image/storage"
"github.com/containers/image/transports/alltransports"
"github.com/containers/image/types"
"github.com/containers/storage/storage"
"github.com/containers/storage"
)
// ImageResult wraps a subset of information about an image: its ID, its names,

View file

@ -11,7 +11,7 @@ import (
istorage "github.com/containers/image/storage"
"github.com/containers/image/transports/alltransports"
"github.com/containers/image/types"
"github.com/containers/storage/storage"
"github.com/containers/storage"
"github.com/opencontainers/image-spec/specs-go/v1"
)
@ -113,7 +113,7 @@ type RuntimeServer interface {
// specifying attributes of pod sandboxes and containers when they are being
// created, and allows a container's MountLabel, and possibly other values, to
// be modified in one read/write cycle via calls to
// RuntimeServer.GetContainerMetadata, RuntimeContainerMetadata.SetMountLabel,
// RuntimeServer.ContainerMetadata, RuntimeContainerMetadata.SetMountLabel,
// and RuntimeServer.SetContainerMetadata.
type RuntimeContainerMetadata struct {
// Pod is true if this is the pod's infrastructure container.
@ -281,7 +281,7 @@ func (r *runtimeService) createContainerOrPodSandbox(systemContext *types.System
// Add a name to the container's layer so that it's easier to follow
// what's going on if we're just looking at the storage-eye view of things.
layerName := metadata.ContainerName + "-layer"
names, err = r.storageImageServer.GetStore().GetNames(container.LayerID)
names, err = r.storageImageServer.GetStore().Names(container.LayerID)
if err != nil {
return ContainerInfo{}, err
}
@ -292,7 +292,7 @@ func (r *runtimeService) createContainerOrPodSandbox(systemContext *types.System
}
// Find out where the container work directories are, so that we can return them.
containerDir, err := r.storageImageServer.GetStore().GetContainerDirectory(container.ID)
containerDir, err := r.storageImageServer.GetStore().ContainerDirectory(container.ID)
if err != nil {
return ContainerInfo{}, err
}
@ -302,7 +302,7 @@ func (r *runtimeService) createContainerOrPodSandbox(systemContext *types.System
logrus.Debugf("container %q has work directory %q", container.ID, containerDir)
}
containerRunDir, err := r.storageImageServer.GetStore().GetContainerRunDirectory(container.ID)
containerRunDir, err := r.storageImageServer.GetStore().ContainerRunDirectory(container.ID)
if err != nil {
return ContainerInfo{}, err
}
@ -329,7 +329,7 @@ func (r *runtimeService) CreateContainer(systemContext *types.SystemContext, pod
}
func (r *runtimeService) RemovePodSandbox(idOrName string) error {
container, err := r.storageImageServer.GetStore().GetContainer(idOrName)
container, err := r.storageImageServer.GetStore().Container(idOrName)
if err != nil {
if err == storage.ErrContainerUnknown {
return ErrInvalidSandboxID
@ -345,7 +345,7 @@ func (r *runtimeService) RemovePodSandbox(idOrName string) error {
}
func (r *runtimeService) DeleteContainer(idOrName string) error {
container, err := r.storageImageServer.GetStore().GetContainer(idOrName)
container, err := r.storageImageServer.GetStore().Container(idOrName)
if err != nil {
if err == storage.ErrContainerUnknown {
return ErrInvalidContainerID
@ -371,7 +371,7 @@ func (r *runtimeService) SetContainerMetadata(idOrName string, metadata RuntimeC
func (r *runtimeService) GetContainerMetadata(idOrName string) (RuntimeContainerMetadata, error) {
metadata := RuntimeContainerMetadata{}
mdata, err := r.storageImageServer.GetStore().GetMetadata(idOrName)
mdata, err := r.storageImageServer.GetStore().Metadata(idOrName)
if err != nil {
return metadata, err
}
@ -382,7 +382,7 @@ func (r *runtimeService) GetContainerMetadata(idOrName string) (RuntimeContainer
}
func (r *runtimeService) StartContainer(idOrName string) (string, error) {
container, err := r.storageImageServer.GetStore().GetContainer(idOrName)
container, err := r.storageImageServer.GetStore().Container(idOrName)
if err != nil {
if err == storage.ErrContainerUnknown {
return "", ErrInvalidContainerID
@ -403,7 +403,7 @@ func (r *runtimeService) StartContainer(idOrName string) (string, error) {
}
func (r *runtimeService) StopContainer(idOrName string) error {
container, err := r.storageImageServer.GetStore().GetContainer(idOrName)
container, err := r.storageImageServer.GetStore().Container(idOrName)
if err != nil {
if err == storage.ErrContainerUnknown {
return ErrInvalidContainerID
@ -420,25 +420,25 @@ func (r *runtimeService) StopContainer(idOrName string) error {
}
func (r *runtimeService) GetWorkDir(id string) (string, error) {
container, err := r.storageImageServer.GetStore().GetContainer(id)
container, err := r.storageImageServer.GetStore().Container(id)
if err != nil {
if err == storage.ErrContainerUnknown {
return "", ErrInvalidContainerID
}
return "", err
}
return r.storageImageServer.GetStore().GetContainerDirectory(container.ID)
return r.storageImageServer.GetStore().ContainerDirectory(container.ID)
}
func (r *runtimeService) GetRunDir(id string) (string, error) {
container, err := r.storageImageServer.GetStore().GetContainer(id)
container, err := r.storageImageServer.GetStore().Container(id)
if err != nil {
if err == storage.ErrContainerUnknown {
return "", ErrInvalidContainerID
}
return "", err
}
return r.storageImageServer.GetStore().GetContainerRunDirectory(container.ID)
return r.storageImageServer.GetStore().ContainerRunDirectory(container.ID)
}
// GetRuntimeService returns a RuntimeServer that uses the passed-in image

View file

@ -79,7 +79,7 @@ func (s *Server) ContainerStatus(ctx context.Context, req *pb.ContainerStatusReq
}
func (s *Server) getMounts(id string) ([]*pb.Mount, error) {
config, err := s.store.GetFromContainerDirectory(id, "config.json")
config, err := s.store.FromContainerDirectory(id, "config.json")
if err != nil {
return nil, err
}

View file

@ -4,7 +4,7 @@ import (
"fmt"
"github.com/Sirupsen/logrus"
"github.com/containers/storage/storage"
"github.com/containers/storage"
"golang.org/x/net/context"
pb "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
)

View file

@ -9,7 +9,7 @@ import (
"syscall"
"github.com/Sirupsen/logrus"
"github.com/containers/storage/storage"
"github.com/containers/storage"
"github.com/kubernetes-incubator/cri-o/oci"
"github.com/opencontainers/runtime-tools/generate"
"github.com/opencontainers/selinux/go-selinux/label"

View file

@ -10,7 +10,7 @@ import (
"github.com/Sirupsen/logrus"
"github.com/containers/image/types"
sstorage "github.com/containers/storage/storage"
sstorage "github.com/containers/storage"
"github.com/docker/docker/pkg/registrar"
"github.com/docker/docker/pkg/truncindex"
"github.com/kubernetes-incubator/cri-o/oci"
@ -78,7 +78,7 @@ func (s *Server) GetPortForward(req *pb.PortForwardRequest) (*pb.PortForwardResp
}
func (s *Server) loadContainer(id string) error {
config, err := s.store.GetFromContainerDirectory(id, "config.json")
config, err := s.store.FromContainerDirectory(id, "config.json")
if err != nil {
return err
}
@ -115,7 +115,7 @@ func (s *Server) loadContainer(id string) error {
if v := m.Annotations["crio/tty"]; v == "true" {
tty = true
}
containerPath, err := s.store.GetContainerRunDirectory(id)
containerPath, err := s.store.ContainerRunDirectory(id)
if err != nil {
return err
}
@ -161,7 +161,7 @@ func configNetNsPath(spec rspec.Spec) (string, error) {
}
func (s *Server) loadSandbox(id string) error {
config, err := s.store.GetFromContainerDirectory(id, "config.json")
config, err := s.store.FromContainerDirectory(id, "config.json")
if err != nil {
return err
}
@ -239,7 +239,7 @@ func (s *Server) loadSandbox(id string) error {
}
}()
sandboxPath, err := s.store.GetContainerRunDirectory(id)
sandboxPath, err := s.store.ContainerRunDirectory(id)
if err != nil {
return err
}

View file

@ -11,8 +11,8 @@ import (
"github.com/Sirupsen/logrus"
"github.com/containers/image/storage"
"github.com/containers/image/types"
sstorage "github.com/containers/storage"
"github.com/containers/storage/pkg/reexec"
sstorage "github.com/containers/storage/storage"
digest "github.com/opencontainers/go-digest"
specs "github.com/opencontainers/image-spec/specs-go"
"github.com/opencontainers/image-spec/specs-go/v1"

View file

@ -9,8 +9,8 @@ import (
"github.com/containers/image/storage"
"github.com/containers/image/transports/alltransports"
"github.com/containers/image/types"
sstorage "github.com/containers/storage"
"github.com/containers/storage/pkg/reexec"
sstorage "github.com/containers/storage/storage"
"github.com/urfave/cli"
)

View file

@ -7,13 +7,13 @@ import (
"io"
"io/ioutil"
"reflect"
"strings"
"time"
pb "gopkg.in/cheggaaa/pb.v1"
"github.com/Sirupsen/logrus"
"github.com/containers/image/image"
"github.com/containers/image/manifest"
"github.com/containers/image/pkg/compression"
"github.com/containers/image/signature"
"github.com/containers/image/transports"
@ -22,11 +22,6 @@ import (
"github.com/pkg/errors"
)
// preferredManifestMIMETypes lists manifest MIME types in order of our preference, if we can't use the original manifest and need to convert.
// Prefer v2s2 to v2s1 because v2s2 does not need to be changed when uploading to a different location.
// Include v2s1 signed but not v2s1 unsigned, because docker/distribution requires a signature even if the unsigned MIME type is used.
var preferredManifestMIMETypes = []string{manifest.DockerV2Schema2MediaType, manifest.DockerV2Schema1SignedMediaType}
type digestingReader struct {
source io.Reader
digester digest.Digester
@ -186,8 +181,16 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe
canModifyManifest := len(sigs) == 0
manifestUpdates := types.ManifestUpdateOptions{}
manifestUpdates.InformationOnly.Destination = dest
if err := determineManifestConversion(&manifestUpdates, src, destSupportedManifestMIMETypes, canModifyManifest); err != nil {
if err := updateEmbeddedDockerReference(&manifestUpdates, dest, src, canModifyManifest); err != nil {
return err
}
// We compute preferredManifestMIMEType only to show it in error messages.
// Without having to add this context in an error message, we would be happy enough to know only that no conversion is needed.
preferredManifestMIMEType, otherManifestMIMETypeCandidates, err := determineManifestConversion(&manifestUpdates, src, destSupportedManifestMIMETypes, canModifyManifest)
if err != nil {
return err
}
@ -210,54 +213,58 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe
return err
}
pendingImage := src
if !reflect.DeepEqual(manifestUpdates, types.ManifestUpdateOptions{InformationOnly: manifestUpdates.InformationOnly}) {
if !canModifyManifest {
return errors.Errorf("Internal error: copy needs an updated manifest but that was known to be forbidden")
}
manifestUpdates.InformationOnly.Destination = dest
pendingImage, err = src.UpdatedImage(manifestUpdates)
if err != nil {
return errors.Wrap(err, "Error creating an updated image manifest")
}
}
manifest, _, err := pendingImage.Manifest()
// With docker/distribution registries we do not know whether the registry accepts schema2 or schema1 only;
// and at least with the OpenShift registry "acceptschema2" option, there is no way to detect the support
// without actually trying to upload something and getting a types.ManifestTypeRejectedError.
// So, try the preferred manifest MIME type. If the process succeeds, fine…
manifest, err := ic.copyUpdatedConfigAndManifest()
if err != nil {
return errors.Wrap(err, "Error reading manifest")
}
logrus.Debugf("Writing manifest using preferred type %s failed: %v", preferredManifestMIMEType, err)
// … if it fails, _and_ the failure is because the manifest is rejected, we may have other options.
if _, isManifestRejected := errors.Cause(err).(types.ManifestTypeRejectedError); !isManifestRejected || len(otherManifestMIMETypeCandidates) == 0 {
// We dont have other options.
// In principle the code below would handle this as well, but the resulting error message is fairly ugly.
// Dont bother the user with MIME types if we have no choice.
return err
}
// If the original MIME type is acceptable, determineManifestConversion always uses it as preferredManifestMIMEType.
// So if we are here, we will definitely be trying to convert the manifest.
// With !canModifyManifest, that would just be a string of repeated failures for the same reason,
// so lets bail out early and with a better error message.
if !canModifyManifest {
return errors.Wrap(err, "Writing manifest failed (and converting it is not possible)")
}
if err := ic.copyConfig(pendingImage); err != nil {
return err
// errs is a list of errors when trying various manifest types. Also serves as an "upload succeeded" flag when set to nil.
errs := []string{fmt.Sprintf("%s(%v)", preferredManifestMIMEType, err)}
for _, manifestMIMEType := range otherManifestMIMETypeCandidates {
logrus.Debugf("Trying to use manifest type %s…", manifestMIMEType)
manifestUpdates.ManifestMIMEType = manifestMIMEType
attemptedManifest, err := ic.copyUpdatedConfigAndManifest()
if err != nil {
logrus.Debugf("Upload of manifest type %s failed: %v", manifestMIMEType, err)
errs = append(errs, fmt.Sprintf("%s(%v)", manifestMIMEType, err))
continue
}
// We have successfully uploaded a manifest.
manifest = attemptedManifest
errs = nil // Mark this as a success so that we don't abort below.
break
}
if errs != nil {
return fmt.Errorf("Uploading manifest failed, attempted the following formats: %s", strings.Join(errs, ", "))
}
}
if options.SignBy != "" {
mech, err := signature.NewGPGSigningMechanism()
newSig, err := createSignature(dest, manifest, options.SignBy, reportWriter)
if err != nil {
return errors.Wrap(err, "Error initializing GPG")
}
defer mech.Close()
if err := mech.SupportsSigning(); err != nil {
return errors.Wrap(err, "Signing not supported")
}
dockerReference := dest.Reference().DockerReference()
if dockerReference == nil {
return errors.Errorf("Cannot determine canonical Docker reference for destination %s", transports.ImageName(dest.Reference()))
}
writeReport("Signing manifest\n")
newSig, err := signature.SignDockerManifest(manifest, dockerReference.String(), mech, options.SignBy)
if err != nil {
return errors.Wrap(err, "Error creating signature")
return err
}
sigs = append(sigs, newSig)
}
writeReport("Writing manifest to image destination\n")
if err := dest.PutManifest(manifest); err != nil {
return errors.Wrap(err, "Error writing manifest")
}
writeReport("Storing signatures\n")
if err := dest.PutSignatures(sigs); err != nil {
return errors.Wrap(err, "Error writing signatures")
@ -270,6 +277,24 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe
return nil
}
// updateEmbeddedDockerReference handles the Docker reference embedded in Docker schema1 manifests.
func updateEmbeddedDockerReference(manifestUpdates *types.ManifestUpdateOptions, dest types.ImageDestination, src types.Image, canModifyManifest bool) error {
destRef := dest.Reference().DockerReference()
if destRef == nil {
return nil // Destination does not care about Docker references
}
if !src.EmbeddedDockerReferenceConflicts(destRef) {
return nil // No reference embedded in the manifest, or it matches destRef already.
}
if !canModifyManifest {
return errors.Errorf("Copying a schema1 image with an embedded Docker reference to %s (Docker reference %s) would invalidate existing signatures. Explicitly enable signature removal to proceed anyway",
transports.ImageName(dest.Reference()), destRef.String())
}
manifestUpdates.EmbeddedDockerReference = destRef
return nil
}
// copyLayers copies layers from src/rawSource to dest, using and updating ic.manifestUpdates if necessary and ic.canModifyManifest.
func (ic *imageCopier) copyLayers() error {
srcInfos := ic.src.LayerInfos()
@ -322,6 +347,45 @@ func layerDigestsDiffer(a, b []types.BlobInfo) bool {
return false
}
// copyUpdatedConfigAndManifest updates the image per ic.manifestUpdates, if necessary,
// stores the resulting config and manifest to the destination, and returns the stored manifest.
func (ic *imageCopier) copyUpdatedConfigAndManifest() ([]byte, error) {
pendingImage := ic.src
if !reflect.DeepEqual(*ic.manifestUpdates, types.ManifestUpdateOptions{InformationOnly: ic.manifestUpdates.InformationOnly}) {
if !ic.canModifyManifest {
return nil, errors.Errorf("Internal error: copy needs an updated manifest but that was known to be forbidden")
}
if !ic.diffIDsAreNeeded && ic.src.UpdatedImageNeedsLayerDiffIDs(*ic.manifestUpdates) {
// We have set ic.diffIDsAreNeeded based on the preferred MIME type returned by determineManifestConversion.
// So, this can only happen if we are trying to upload using one of the other MIME type candidates.
// Because UpdatedImageNeedsLayerDiffIDs is true only when converting from s1 to s2, this case should only arise
// when ic.dest.SupportedManifestMIMETypes() includes both s1 and s2, the upload using s1 failed, and we are now trying s2.
// Supposedly s2-only registries do not exist or are extremely rare, so failing with this error message is good enough for now.
// If handling such registries turns out to be necessary, we could compute ic.diffIDsAreNeeded based on the full list of manifest MIME type candidates.
return nil, errors.Errorf("Can not convert image to %s, preparing DiffIDs for this case is not supported", ic.manifestUpdates.ManifestMIMEType)
}
pi, err := ic.src.UpdatedImage(*ic.manifestUpdates)
if err != nil {
return nil, errors.Wrap(err, "Error creating an updated image manifest")
}
pendingImage = pi
}
manifest, _, err := pendingImage.Manifest()
if err != nil {
return nil, errors.Wrap(err, "Error reading manifest")
}
if err := ic.copyConfig(pendingImage); err != nil {
return nil, err
}
fmt.Fprintf(ic.reportWriter, "Writing manifest to image destination\n")
if err := ic.dest.PutManifest(manifest); err != nil {
return nil, errors.Wrap(err, "Error writing manifest")
}
return manifest, nil
}
// copyConfig copies config.json, if any, from src to dest.
func (ic *imageCopier) copyConfig(src types.Image) error {
srcInfo := src.ConfigInfo()
@ -575,41 +639,3 @@ func compressGoroutine(dest *io.PipeWriter, src io.Reader) {
_, err = io.Copy(zipper, src) // Sets err to nil, i.e. causes dest.Close()
}
// determineManifestConversion updates manifestUpdates to convert manifest to a supported MIME type, if necessary and canModifyManifest.
// Note that the conversion will only happen later, through src.UpdatedImage
func determineManifestConversion(manifestUpdates *types.ManifestUpdateOptions, src types.Image, destSupportedManifestMIMETypes []string, canModifyManifest bool) error {
if len(destSupportedManifestMIMETypes) == 0 {
return nil // Anything goes
}
supportedByDest := map[string]struct{}{}
for _, t := range destSupportedManifestMIMETypes {
supportedByDest[t] = struct{}{}
}
_, srcType, err := src.Manifest()
if err != nil { // This should have been cached?!
return errors.Wrap(err, "Error reading manifest")
}
if _, ok := supportedByDest[srcType]; ok {
logrus.Debugf("Manifest MIME type %s is declared supported by the destination", srcType)
return nil
}
// OK, we should convert the manifest.
if !canModifyManifest {
logrus.Debugf("Manifest MIME type %s is not supported by the destination, but we can't modify the manifest, hoping for the best...")
return nil // Take our chances - FIXME? Or should we fail without trying?
}
var chosenType = destSupportedManifestMIMETypes[0] // This one is known to be supported.
for _, t := range preferredManifestMIMETypes {
if _, ok := supportedByDest[t]; ok {
chosenType = t
break
}
}
logrus.Debugf("Will convert manifest from MIME type %s to %s", srcType, chosenType)
manifestUpdates.ManifestMIMEType = chosenType
return nil
}

102
vendor/github.com/containers/image/copy/manifest.go generated vendored Normal file
View file

@ -0,0 +1,102 @@
package copy
import (
"strings"
"github.com/Sirupsen/logrus"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/pkg/errors"
)
// preferredManifestMIMETypes lists manifest MIME types in order of our preference, if we can't use the original manifest and need to convert.
// Prefer v2s2 to v2s1 because v2s2 does not need to be changed when uploading to a different location.
// Include v2s1 signed but not v2s1 unsigned, because docker/distribution requires a signature even if the unsigned MIME type is used.
var preferredManifestMIMETypes = []string{manifest.DockerV2Schema2MediaType, manifest.DockerV2Schema1SignedMediaType}
// orderedSet is a list of strings (MIME types in our case), with each string appearing at most once.
type orderedSet struct {
list []string
included map[string]struct{}
}
// newOrderedSet creates a correctly initialized orderedSet.
// [Sometimes it would be really nice if Golang had constructors…]
func newOrderedSet() *orderedSet {
return &orderedSet{
list: []string{},
included: map[string]struct{}{},
}
}
// append adds s to the end of os, only if it is not included already.
func (os *orderedSet) append(s string) {
if _, ok := os.included[s]; !ok {
os.list = append(os.list, s)
os.included[s] = struct{}{}
}
}
// determineManifestConversion updates manifestUpdates to convert manifest to a supported MIME type, if necessary and canModifyManifest.
// Note that the conversion will only happen later, through src.UpdatedImage
// Returns the preferred manifest MIME type (whether we are converting to it or using it unmodified),
// and a list of other possible alternatives, in order.
func determineManifestConversion(manifestUpdates *types.ManifestUpdateOptions, src types.Image, destSupportedManifestMIMETypes []string, canModifyManifest bool) (string, []string, error) {
_, srcType, err := src.Manifest()
if err != nil { // This should have been cached?!
return "", nil, errors.Wrap(err, "Error reading manifest")
}
if len(destSupportedManifestMIMETypes) == 0 {
return srcType, []string{}, nil // Anything goes; just use the original as is, do not try any conversions.
}
supportedByDest := map[string]struct{}{}
for _, t := range destSupportedManifestMIMETypes {
supportedByDest[t] = struct{}{}
}
// destSupportedManifestMIMETypes is a static guess; a particular registry may still only support a subset of the types.
// So, build a list of types to try in order of decreasing preference.
// FIXME? This treats manifest.DockerV2Schema1SignedMediaType and manifest.DockerV2Schema1MediaType as distinct,
// although we are not really making any conversion, and it is very unlikely that a destination would support one but not the other.
// In practice, schema1 is probably the lowest common denominator, so we would expect to try the first one of the MIME types
// and never attempt the other one.
prioritizedTypes := newOrderedSet()
// First of all, prefer to keep the original manifest unmodified.
if _, ok := supportedByDest[srcType]; ok {
prioritizedTypes.append(srcType)
}
if !canModifyManifest {
// We could also drop the !canModifyManifest parameter and have the caller
// make the choice; it is already doing that to an extent, to improve error
// messages. But it is nice to hide the “if !canModifyManifest, do no conversion”
// special case in here; the caller can then worry (or not) only about a good UI.
logrus.Debugf("We can't modify the manifest, hoping for the best...")
return srcType, []string{}, nil // Take our chances - FIXME? Or should we fail without trying?
}
// Then use our list of preferred types.
for _, t := range preferredManifestMIMETypes {
if _, ok := supportedByDest[t]; ok {
prioritizedTypes.append(t)
}
}
// Finally, try anything else the destination supports.
for _, t := range destSupportedManifestMIMETypes {
prioritizedTypes.append(t)
}
logrus.Debugf("Manifest has MIME type %s, ordered candidate list [%s]", srcType, strings.Join(prioritizedTypes.list, ", "))
if len(prioritizedTypes.list) == 0 { // Coverage: destSupportedManifestMIMETypes is not empty (or we would have exited in the “Anything goes” case above), so this should never happen.
return "", nil, errors.New("Internal error: no candidate MIME types")
}
preferredType := prioritizedTypes.list[0]
if preferredType != srcType {
manifestUpdates.ManifestMIMEType = preferredType
} else {
logrus.Debugf("... will first try using the original manifest unmodified")
}
return preferredType, prioritizedTypes.list[1:], nil
}

View file

@ -0,0 +1,164 @@
package copy
import (
"errors"
"fmt"
"testing"
"github.com/containers/image/docker/reference"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestOrderedSet(t *testing.T) {
for _, c := range []struct{ input, expected []string }{
{[]string{}, []string{}},
{[]string{"a", "b", "c"}, []string{"a", "b", "c"}},
{[]string{"a", "b", "a", "c"}, []string{"a", "b", "c"}},
} {
os := newOrderedSet()
for _, s := range c.input {
os.append(s)
}
assert.Equal(t, c.expected, os.list, fmt.Sprintf("%#v", c.input))
}
}
// fakeImageSource is an implementation of types.Image which only returns itself as a MIME type in Manifest
// except that "" means “reading the manifest should fail”
type fakeImageSource string
func (f fakeImageSource) Reference() types.ImageReference {
panic("Unexpected call to a mock function")
}
func (f fakeImageSource) Close() error {
panic("Unexpected call to a mock function")
}
func (f fakeImageSource) Manifest() ([]byte, string, error) {
if string(f) == "" {
return nil, "", errors.New("Manifest() directed to fail")
}
return nil, string(f), nil
}
func (f fakeImageSource) Signatures() ([][]byte, error) {
panic("Unexpected call to a mock function")
}
func (f fakeImageSource) ConfigInfo() types.BlobInfo {
panic("Unexpected call to a mock function")
}
func (f fakeImageSource) ConfigBlob() ([]byte, error) {
panic("Unexpected call to a mock function")
}
func (f fakeImageSource) OCIConfig() (*v1.Image, error) {
panic("Unexpected call to a mock function")
}
func (f fakeImageSource) LayerInfos() []types.BlobInfo {
panic("Unexpected call to a mock function")
}
func (f fakeImageSource) EmbeddedDockerReferenceConflicts(ref reference.Named) bool {
panic("Unexpected call to a mock function")
}
func (f fakeImageSource) Inspect() (*types.ImageInspectInfo, error) {
panic("Unexpected call to a mock function")
}
func (f fakeImageSource) UpdatedImageNeedsLayerDiffIDs(options types.ManifestUpdateOptions) bool {
panic("Unexpected call to a mock function")
}
func (f fakeImageSource) UpdatedImage(options types.ManifestUpdateOptions) (types.Image, error) {
panic("Unexpected call to a mock function")
}
func (f fakeImageSource) IsMultiImage() bool {
panic("Unexpected call to a mock function")
}
func (f fakeImageSource) Size() (int64, error) {
panic("Unexpected call to a mock function")
}
func TestDetermineManifestConversion(t *testing.T) {
supportS1S2OCI := []string{
v1.MediaTypeImageManifest,
manifest.DockerV2Schema2MediaType,
manifest.DockerV2Schema1SignedMediaType,
manifest.DockerV2Schema1MediaType,
}
supportS1OCI := []string{
v1.MediaTypeImageManifest,
manifest.DockerV2Schema1SignedMediaType,
manifest.DockerV2Schema1MediaType,
}
supportS1S2 := []string{
manifest.DockerV2Schema2MediaType,
manifest.DockerV2Schema1SignedMediaType,
manifest.DockerV2Schema1MediaType,
}
supportOnlyS1 := []string{
manifest.DockerV2Schema1SignedMediaType,
manifest.DockerV2Schema1MediaType,
}
cases := []struct {
description string
sourceType string
destTypes []string
expectedUpdate string
expectedOtherCandidates []string
}{
// Destination accepts anything — no conversion necessary
{"s1→anything", manifest.DockerV2Schema1SignedMediaType, nil, "", []string{}},
{"s2→anything", manifest.DockerV2Schema2MediaType, nil, "", []string{}},
// Destination accepts the unmodified original
{"s1→s1s2", manifest.DockerV2Schema1SignedMediaType, supportS1S2, "", []string{manifest.DockerV2Schema2MediaType, manifest.DockerV2Schema1MediaType}},
{"s2→s1s2", manifest.DockerV2Schema2MediaType, supportS1S2, "", supportOnlyS1},
{"s1→s1", manifest.DockerV2Schema1SignedMediaType, supportOnlyS1, "", []string{manifest.DockerV2Schema1MediaType}},
// Conversion necessary, a preferred format is acceptable
{"s2→s1", manifest.DockerV2Schema2MediaType, supportOnlyS1, manifest.DockerV2Schema1SignedMediaType, []string{manifest.DockerV2Schema1MediaType}},
// Conversion necessary, a preferred format is not acceptable
{"s2→OCI", manifest.DockerV2Schema2MediaType, []string{v1.MediaTypeImageManifest}, v1.MediaTypeImageManifest, []string{}},
// Conversion necessary, try the preferred formats in order.
{
"special→s2", "this needs conversion", supportS1S2OCI, manifest.DockerV2Schema2MediaType,
[]string{manifest.DockerV2Schema1SignedMediaType, v1.MediaTypeImageManifest, manifest.DockerV2Schema1MediaType},
},
{
"special→s1", "this needs conversion", supportS1OCI, manifest.DockerV2Schema1SignedMediaType,
[]string{v1.MediaTypeImageManifest, manifest.DockerV2Schema1MediaType},
},
{
"special→OCI", "this needs conversion", []string{v1.MediaTypeImageManifest, "other options", "with lower priority"}, v1.MediaTypeImageManifest,
[]string{"other options", "with lower priority"},
},
}
for _, c := range cases {
src := fakeImageSource(c.sourceType)
mu := types.ManifestUpdateOptions{}
preferredMIMEType, otherCandidates, err := determineManifestConversion(&mu, src, c.destTypes, true)
require.NoError(t, err, c.description)
assert.Equal(t, c.expectedUpdate, mu.ManifestMIMEType, c.description)
if c.expectedUpdate == "" {
assert.Equal(t, c.sourceType, preferredMIMEType, c.description)
} else {
assert.Equal(t, c.expectedUpdate, preferredMIMEType, c.description)
}
assert.Equal(t, c.expectedOtherCandidates, otherCandidates, c.description)
}
// Whatever the input is, with !canModifyManifest we return "keep the original as is"
for _, c := range cases {
src := fakeImageSource(c.sourceType)
mu := types.ManifestUpdateOptions{}
preferredMIMEType, otherCandidates, err := determineManifestConversion(&mu, src, c.destTypes, false)
require.NoError(t, err, c.description)
assert.Equal(t, "", mu.ManifestMIMEType, c.description)
assert.Equal(t, c.sourceType, preferredMIMEType, c.description)
assert.Equal(t, []string{}, otherCandidates, c.description)
}
// Error reading the manifest — smoke test only.
mu := types.ManifestUpdateOptions{}
_, _, err := determineManifestConversion(&mu, fakeImageSource(""), supportS1S2, true)
assert.Error(t, err)
}

35
vendor/github.com/containers/image/copy/sign.go generated vendored Normal file
View file

@ -0,0 +1,35 @@
package copy
import (
"fmt"
"io"
"github.com/containers/image/signature"
"github.com/containers/image/transports"
"github.com/containers/image/types"
"github.com/pkg/errors"
)
// createSignature creates a new signature of manifest at (identified by) dest using keyIdentity.
func createSignature(dest types.ImageDestination, manifest []byte, keyIdentity string, reportWriter io.Writer) ([]byte, error) {
mech, err := signature.NewGPGSigningMechanism()
if err != nil {
return nil, errors.Wrap(err, "Error initializing GPG")
}
defer mech.Close()
if err := mech.SupportsSigning(); err != nil {
return nil, errors.Wrap(err, "Signing not supported")
}
dockerReference := dest.Reference().DockerReference()
if dockerReference == nil {
return nil, errors.Errorf("Cannot determine canonical Docker reference for destination %s", transports.ImageName(dest.Reference()))
}
fmt.Fprintf(reportWriter, "Signing manifest\n")
newSig, err := signature.SignDockerManifest(manifest, dockerReference.String(), mech, keyIdentity)
if err != nil {
return nil, errors.Wrap(err, "Error creating signature")
}
return newSig, nil
}

72
vendor/github.com/containers/image/copy/sign_test.go generated vendored Normal file
View file

@ -0,0 +1,72 @@
package copy
import (
"io/ioutil"
"os"
"testing"
"github.com/containers/image/directory"
"github.com/containers/image/docker"
"github.com/containers/image/manifest"
"github.com/containers/image/signature"
"github.com/containers/image/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
testGPGHomeDirectory = "../signature/fixtures"
// TestKeyFingerprint is the fingerprint of the private key in testGPGHomeDirectory.
// Keep this in sync with signature/fixtures_info_test.go
testKeyFingerprint = "1D8230F6CDB6A06716E414C1DB72F2188BB46CC8"
)
func TestCreateSignature(t *testing.T) {
manifestBlob := []byte("Something")
manifestDigest, err := manifest.Digest(manifestBlob)
require.NoError(t, err)
mech, _, err := signature.NewEphemeralGPGSigningMechanism([]byte{})
require.NoError(t, err)
defer mech.Close()
if err := mech.SupportsSigning(); err != nil {
t.Skipf("Signing not supported: %v", err)
}
os.Setenv("GNUPGHOME", testGPGHomeDirectory)
defer os.Unsetenv("GNUPGHOME")
// Signing a directory: reference, which does not have a DockerRefrence(), fails.
tempDir, err := ioutil.TempDir("", "signature-dir-dest")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
dirRef, err := directory.NewReference(tempDir)
require.NoError(t, err)
dirDest, err := dirRef.NewImageDestination(nil)
require.NoError(t, err)
defer dirDest.Close()
_, err = createSignature(dirDest, manifestBlob, testKeyFingerprint, ioutil.Discard)
assert.Error(t, err)
// Set up a docker: reference
dockerRef, err := docker.ParseReference("//busybox")
require.NoError(t, err)
dockerDest, err := dockerRef.NewImageDestination(&types.SystemContext{RegistriesDirPath: "/this/doesnt/exist"})
assert.NoError(t, err)
defer dockerDest.Close()
// Signing with an unknown key fails
_, err = createSignature(dockerDest, manifestBlob, "this key does not exist", ioutil.Discard)
assert.Error(t, err)
// Success
mech, err = signature.NewGPGSigningMechanism()
require.NoError(t, err)
defer mech.Close()
sig, err := createSignature(dockerDest, manifestBlob, testKeyFingerprint, ioutil.Discard)
require.NoError(t, err)
verified, err := signature.VerifyDockerManifestSignature(sig, manifestBlob, "docker.io/library/busybox:latest", mech, testKeyFingerprint)
require.NoError(t, err)
assert.Equal(t, "docker.io/library/busybox:latest", verified.DockerReference)
assert.Equal(t, manifestDigest, verified.DockerManifestDigest)
}

View file

@ -118,6 +118,10 @@ func (d *dirImageDestination) ReapplyBlob(info types.BlobInfo) (types.BlobInfo,
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 *dirImageDestination) PutManifest(manifest []byte) error {
return ioutil.WriteFile(d.ref.manifestPath(), manifest, 0644)
}

View file

@ -16,6 +16,9 @@ import (
"github.com/containers/image/docker/reference"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/docker/distribution/registry/api/errcode"
"github.com/docker/distribution/registry/api/v2"
"github.com/docker/distribution/registry/client"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
@ -209,6 +212,10 @@ func (d *dockerImageDestination) ReapplyBlob(info types.BlobInfo) (types.BlobInf
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 *dockerImageDestination) PutManifest(m []byte) error {
digest, err := manifest.Digest(m)
if err != nil {
@ -233,16 +240,31 @@ func (d *dockerImageDestination) PutManifest(m []byte) error {
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
body, err := ioutil.ReadAll(res.Body)
if err == nil {
logrus.Debugf("Error body %s", string(body))
err = errors.Wrapf(client.HandleErrorResponse(res), "Error uploading manifest to %s", path)
if isManifestInvalidError(errors.Cause(err)) {
err = types.ManifestTypeRejectedError{Err: err}
}
logrus.Debugf("Error uploading manifest, status %d, %#v", res.StatusCode, res)
return errors.Errorf("Error uploading manifest to %s, status %d", path, res.StatusCode)
return err
}
return nil
}
// isManifestInvalidError returns true iff err from client.HandleErrorReponse is a “manifest invalid” error.
func isManifestInvalidError(err error) bool {
errors, ok := err.(errcode.Errors)
if !ok || len(errors) == 0 {
return false
}
ec, ok := errors[0].(errcode.ErrorCoder)
if !ok {
return false
}
// ErrorCodeManifestInvalid is returned by OpenShift with acceptschema2=false.
// ErrorCodeTagInvalid is returned by docker/distribution (at least as of commit ec87e9b6971d831f0eff752ddb54fb64693e51cd)
// when uploading to a tag (because it cant find a matching tag inside the manifest)
return ec.ErrorCode() == v2.ErrorCodeManifestInvalid || ec.ErrorCode() == v2.ErrorCodeTagInvalid
}
func (d *dockerImageDestination) PutSignatures(signatures [][]byte) error {
// Do not fail if we dont really need to support signatures.
if len(signatures) == 0 {

View file

@ -156,10 +156,13 @@ func (d *Destination) ReapplyBlob(info types.BlobInfo) (types.BlobInfo, error) {
return info, nil
}
// PutManifest sends the given manifest blob to the destination.
// FIXME? This should also receive a MIME type if known, to differentiate
// between schema versions.
// 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 *Destination) PutManifest(m []byte) error {
// We do not bother with types.ManifestTypeRejectedError; our .SupportedManifestMIMETypes() above is already providing only one alternative,
// so the caller trying a different manifest kind would be pointless.
var man schema2Manifest
if err := json.Unmarshal(m, &man); err != nil {
return errors.Wrap(err, "Error parsing manifest")

View file

@ -0,0 +1,66 @@
{
"title": "JSON embedded in an atomic container signature",
"description": "This schema is a supplement to atomic-signature.md in this directory.\n\nConsumers of the JSON MUST use the processing rules documented in atomic-signature.md, especially the requirements for the 'critical' subjobject.\n\nWhenever this schema and atomic-signature.md, or the github.com/containers/image/signature implementation, differ,\nit is the atomic-signature.md document, or the github.com/containers/image/signature implementation, which governs.\n\nUsers are STRONGLY RECOMMENDED to use the github.com/containeres/image/signature implementation instead of writing\ntheir own, ESPECIALLY when consuming signatures, so that the policy.json format can be shared by all image consumers.\n",
"type": "object",
"required": [
"critical",
"optional"
],
"additionalProperties": false,
"properties": {
"critical": {
"type": "object",
"required": [
"type",
"image",
"identity"
],
"additionalProperties": false,
"properties": {
"type": {
"type": "string",
"enum": [
"atomic container signature"
]
},
"image": {
"type": "object",
"required": [
"docker-manifest-digest"
],
"additionalProperties": false,
"properties": {
"docker-manifest-digest": {
"type": "string"
}
}
},
"identity": {
"type": "object",
"required": [
"docker-reference"
],
"additionalProperties": false,
"properties": {
"docker-reference": {
"type": "string"
}
}
}
}
},
"optional": {
"type": "object",
"description": "All members are optional, but if they are included, they must be valid.",
"additionalProperties": true,
"properties": {
"creator": {
"type": "string"
},
"timestamp": {
"type": "integer"
}
}
}
}
}

View file

@ -0,0 +1,241 @@
% atomic-signature(5) Atomic signature format
% Miloslav Trmač
% March 2017
# Atomic signature format
This document describes the format of “atomic” container signatures,
as implemented by the `github.com/containers/image/signature` package.
Most users should be able to consume these signatures by using the `github.com/containers/image/signature` package
(preferably through the higher-level `signature.PolicyContext` interface)
without having to care about the details of the format described below.
This documentation exists primarily for maintainers of the package
and to allow independent reimplementations.
## High-level overview
The signature provides an end-to-end authenticated claim that a container image
has been approved by a specific party (e.g. the creator of the image as their work,
an automated build system as a result of an automated build,
a company IT department approving the image for production) under a specified _identity_
(e.g. an OS base image / specific application, with a specific version).
An atomic container signature consists of a cryptographic signature which identifies
and authenticates who signed the image, and carries as a signed payload a JSON document.
The JSON document identifies the image being signed, claims a specific identity of the
image and if applicable, contains other information about the image.
The signatures do not modify the container image (the layers, configuration, manifest, …);
e.g. their presence does not change the manifest digest used to identify the image in
docker/distribution servers; rather, the signatures are associated with an immutable image.
An image can have any number of signatures so signature distribution systems SHOULD support
associating more than one signature with an image.
## The cryptographic signature
As distributed, the atomic container signature is a blob which contains a cryptographic signature
in an industry-standard format, carrying a signed JSON payload (i.e. the blob contains both the
JSON document and a signature of the JSON document; it is not a “detached signature” with
independent blobs containing the JSON document and a cryptographic signature).
Currently the only defined cryptographic signature format is an OpenPGP signature (RFC 4880),
but others may be added in the future. (The blob does not contain metadata identifying the
cryptographic signature format. It is expected that most formats are sufficiently self-describing
that this is not necessary and the configured expected public key provides another indication
of the expected cryptographic signature format. Such metadata may be added in the future for
newly added cryptographic signature formats, if necessary.)
Consumers of atomic container signatures SHOULD verify the cryptographic signature
against one or more trusted public keys
(e.g. defined in a [policy.json signature verification policy file](policy.json.md))
before parsing or processing the JSON payload in _any_ way,
in particular they SHOULD stop processing the container signature
if the cryptographic signature verification fails, without even starting to process the JSON payload.
(Consumers MAY extract identification of the signing key and other metadata from the cryptographic signature,
and the JSON payload, without verifying the signature, if the purpose is to allow managing the signature blobs,
e.g. to list the authors and image identities of signatures associated with a single container image;
if so, they SHOULD design the output of such processing to minimize the risk of users considering the output trusted
or in any way usable for making policy decisions about the image.)
### OpenPGP signature verification
When verifying a cryptographic signature in the OpenPGP format,
the consumer MUST verify at least the following aspects of the signature
(like the `github.com/containers/image/signature` package does):
- The blob MUST be a “Signed Message” as defined RFC 4880 section 11.3.
(e.g. it MUST NOT be an unsigned “Literal Message”, or any other non-signature format).
- The signature MUST have been made by an expected key trusted for the purpose (and the specific container image).
- The signature MUST be correctly formed and pass the cryptographic validation.
- The signature MUST correctly authenticate the included JSON payload
(in particular, the parsing of the JSON payload MUST NOT start before the complete payload has been cryptographically authenticated).
- The signature MUST NOT be expired.
The consumer SHOULD have tests for its verification code which verify that signatures failing any of the above are rejected.
## JSON processing and forward compatibility
The payload of the cryptographic signature is a JSON document (RFC 7159).
Consumers SHOULD parse it very strictly,
refusing any signature which violates the expected format (e.g. missing members, incorrect member types)
or can be interpreted ambiguously (e.g. a duplicated member in a JSON object).
Any violations of the JSON format or of other requirements in this document MAY be accepted if the JSON document can be recognized
to have been created by a known-incorrect implementation (see [`optional.creator`](#optionalcreator) below)
and if the semantics of the invalid document, as created by such an implementation, is clear.
The top-level value of the JSON document MUST be a JSON object with exactly two members, `critical` and `optional`,
each a JSON object.
The `critical` object MUST contain a `type` member identifying the document as an atomic container signature
(as defined [below](#criticaltype))
and signature consumers MUST reject signatures which do not have this member or in which this member does not have the expected value.
To ensure forward compatibility (allowing older signature consumers to correctly
accept or reject signatures created at a later date, with possible extensions to this format),
consumers MUST reject the signature if the `critical` object, or _any_ of its subobjects,
contain _any_ member or data value which is unrecognized, unsupported, invalid, or in any other way unexpected.
At a minimum, this includes unrecognized members in a JSON object, or incorrect types of expected members.
For the same reason, consumers SHOULD accept any members with unrecognized names in the `optional` object,
and MAY accept signatures where the object member is recognized but unsupported, or the value of the member is unsupported.
Consumers still SHOULD reject signatures where a member of an `optional` object is supported but the value is recognized as invalid.
## JSON data format
An example of the full format follows, with detailed description below.
To reiterate, consumers of the signature SHOULD perform successful cryptographic verification,
and MUST reject unexpected data in the `critical` object, or in the top-level object, as described above.
```json
{
"critical": {
"type": "atomic container signature",
"image": {
"docker-manifest-digest": "sha256:817a12c32a39bbe394944ba49de563e085f1d3c5266eb8e9723256bc4448680e"
},
"identity": {
"docker-reference": "docker.io/library/busybox:latest"
}
},
"optional": {
"creator": "some software package v1.0.1-35",
"timestamp": 1483228800,
}
}
```
### `critical`
This MUST be a JSON object which contains data critical to correctly evaluating the validity of a signature.
Consumers MUST reject any signature where the `critical` object contains any unrecognized, unsupported, invalid or in any other way unexpected member or data.
### `critical.type`
This MUST be a string with a string value exactly equal to `atomic container signature` (three words, including the spaces).
Signature consumers MUST reject signatures which do not have this member or this member does not have exactly the expected value.
(The consumers MAY support signatures with a different value of the `type` member, if any is defined in the future;
if so, the rest of the JSON document is interpreted according to rules defining that value of `critical.type`,
not by this document.)
### `critical.image`
This MUST be a JSON object which identifies the container image this signature applies to.
Consumers MUST reject any signature where the `critical.image` object contains any unrecognized, unsupported, invalid or in any other way unexpected member or data.
(Currently only the `docker-manifest-digest` way of identifying a container image is defined;
alternatives to this may be defined in the future,
but existing consumers are required to reject signatures which use formats they do not support.)
### `critical.image.docker-manifest-digest`
This MUST be a JSON string, in the `github.com/opencontainers/go-digest.Digest` string format.
The value of this member MUST match the manifest of the signed container image, as implemented in the docker/distribution manifest addressing system.
The consumer of the signature SHOULD verify the manifest digest against a fully verified signature before processing the contents of the image manifest in any other way
(e.g. parsing the manifest further or downloading layers of the image).
Implementation notes:
* A single container image manifest may have several valid manifest digest values, using different algorithms.
* For “signed” [docker/distribution schema 1](https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-1.md) manifests,
the manifest digest applies to the payload of the JSON web signature, not to the raw manifest blob.
### `critical.identity`
This MUST be a JSON object which identifies the claimed identity of the image (usually the purpose of the image, or the application, along with a version information),
as asserted by the author of the signature.
Consumers MUST reject any signature where the `critical.identity` object contains any unrecognized, unsupported, invalid or in any other way unexpected member or data.
(Currently only the `docker-reference` way of claiming an image identity/purpose is defined;
alternatives to this may be defined in the future,
but existing consumers are required to reject signatures which use formats they do not support.)
### `critical.identity.docker-reference`
This MUST be a JSON string, in the `github.com/docker/distribution/reference` string format,
and using the same normalization semantics (where e.g. `busybox:latest` is equivalent to `docker.io/library/busybox:latest`).
If the normalization semantics allows multiple string representations of the claimed identity with equivalent meaning,
the `critical.identity.docker-reference` member SHOULD use the fully explicit form (including the full host name and namespaces).
The value of this member MUST match the image identity/purpose expected by the consumer of the image signature and the image
(again, accounting for the `docker/distribution/reference` normalization semantics).
In the most common case, this means that the `critical.identity.docker-reference` value must be equal to the docker/distribution reference used to refer to or download the image.
However, depending on the specific application, users or system administrators may accept less specific matches
(e.g. ignoring the tag value in the signature when pulling the `:latest` tag or when referencing an image by digest),
or they may require `critical.identity.docker-reference` values with a completely different namespace to the reference used to refer to/download the image
(e.g. requiring a `critical.identity.docker-reference` value which identifies the image as coming from a supplier when fetching it from a company-internal mirror of approved images).
The software performing this verification SHOULD allow the users to define such a policy using the [policy.json signature verification policy file format](policy.json.md).
The `critical.identity.docker-reference` value SHOULD contain either a tag or digest;
in most cases, it SHOULD use a tag rather than a digest. (See also the default [`matchRepoDigestOrExact` matching semantics in `policy.json`](policy.json.md#signedby).)
### `optional`
This MUST be a JSON object.
Consumers SHOULD accept any members with unrecognized names in the `optional` object,
and MAY accept a signature where the object member is recognized but unsupported, or the value of the member is valid but unsupported.
Consumers still SHOULD reject any signature where a member of an `optional` object is supported but the value is recognized as invalid.
### `optional.creator`
If present, this MUST be a JSON string, identifying the name and version of the software which has created the signature.
The contents of this string is not defined in detail; however each implementation creating atomic container signatures:
- SHOULD define the contents to unambiguously define the software in practice (e.g. it SHOULD contain the name of the software, not only the version number)
- SHOULD use a build and versioning process which ensures that the contents of this string (e.g. an included version number)
changes whenever the format or semantics of the generated signature changes in any way;
it SHOULD not be possible for two implementations which use a different format or semantics to have the same `optional.creator` value
- SHOULD use a format which is reasonably easy to parse in software (perhaps using a regexp),
and which makes it easy enough to recognize a range of versions of a specific implementation
(e.g. the version of the implementation SHOULD NOT be only a git hash, because they dont have an easily defined ordering;
the string should contain a version number, or at least a date of the commit).
Consumers of atomic container signatures MAY recognize specific values or sets of values of `optional.creator`
(perhaps augmented with `optional.timestamp`),
and MAY change their processing of the signature based on these values
(usually to acommodate violations of this specification in past versions of the signing software which cannot be fixed retroactively),
as long as the semantics of the invalid document, as created by such an implementation, is clear.
If consumers of signatures do change their behavior based on the `optional.creator` value,
they SHOULD take care that the way they process the signatures is not inconsistent with
strictly validating signature consumers.
(I.e. it is acceptable for a consumer to accept a signature based on a specific `optional.creator` value
if other implementations would completely reject the signature,
but it would be very undesirable for the two kinds of implementations to accept the signature in different
and inconsistent situations.)
### `optional.timestamp`
If present, this MUST be a JSON number, which is representable as a 64-bit integer, and identifies the time when the signature was created
as the number of seconds since the UNIX epoch (Jan 1 1970 00:00 UTC).

View file

@ -135,6 +135,27 @@ func (m *manifestSchema1) LayerInfos() []types.BlobInfo {
return layers
}
// EmbeddedDockerReferenceConflicts whether a Docker reference embedded in the manifest, if any, conflicts with destination ref.
// It returns false if the manifest does not embed a Docker reference.
// (This embedding unfortunately happens for Docker schema1, please do not add support for this in any new formats.)
func (m *manifestSchema1) EmbeddedDockerReferenceConflicts(ref reference.Named) bool {
// This is a bit convoluted: We cant just have a "get embedded docker reference" method
// and have the “does it conflict” logic in the generic copy code, because the manifest does not actually
// embed a full docker/distribution reference, but only the repo name and tag (without the host name).
// So we would have to provide a “return repo without host name, and tag” getter for the generic code,
// which would be very awkward. Instead, we do the matching here in schema1-specific code, and all the
// generic copy code needs to know about is reference.Named and that a manifest may need updating
// for some destinations.
name := reference.Path(ref)
var tag string
if tagged, isTagged := ref.(reference.NamedTagged); isTagged {
tag = tagged.Tag()
} else {
tag = ""
}
return m.Name != name || m.Tag != tag
}
func (m *manifestSchema1) imageInspectInfo() (*types.ImageInspectInfo, error) {
v1 := &v1Image{}
if err := json.Unmarshal([]byte(m.History[0].V1Compatibility), v1); err != nil {
@ -173,6 +194,14 @@ func (m *manifestSchema1) UpdatedImage(options types.ManifestUpdateOptions) (typ
copy.FSLayers[(len(options.LayerInfos)-1)-i].BlobSum = info.Digest
}
}
if options.EmbeddedDockerReference != nil {
copy.Name = reference.Path(options.EmbeddedDockerReference)
if tagged, isTagged := options.EmbeddedDockerReference.(reference.NamedTagged); isTagged {
copy.Tag = tagged.Tag()
} else {
copy.Tag = ""
}
}
switch options.ManifestMIMEType {
case "": // No conversion, OK

View file

@ -9,6 +9,7 @@ import (
"strings"
"github.com/Sirupsen/logrus"
"github.com/containers/image/docker/reference"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/opencontainers/go-digest"
@ -140,6 +141,13 @@ func (m *manifestSchema2) LayerInfos() []types.BlobInfo {
return blobs
}
// EmbeddedDockerReferenceConflicts whether a Docker reference embedded in the manifest, if any, conflicts with destination ref.
// It returns false if the manifest does not embed a Docker reference.
// (This embedding unfortunately happens for Docker schema1, please do not add support for this in any new formats.)
func (m *manifestSchema2) EmbeddedDockerReferenceConflicts(ref reference.Named) bool {
return false
}
func (m *manifestSchema2) imageInspectInfo() (*types.ImageInspectInfo, error) {
config, err := m.ConfigBlob()
if err != nil {
@ -180,6 +188,7 @@ func (m *manifestSchema2) UpdatedImage(options types.ManifestUpdateOptions) (typ
copy.LayersDescriptors[i].URLs = info.URLs
}
}
// Ignore options.EmbeddedDockerReference: it may be set when converting from schema1 to schema2, but we really don't care.
switch options.ManifestMIMEType {
case "": // No conversion, OK

View file

@ -242,6 +242,20 @@ func TestManifestSchema2LayerInfo(t *testing.T) {
}
}
func TestManifestSchema2EmbeddedDockerReferenceConflicts(t *testing.T) {
for _, m := range []genericManifest{
manifestSchema2FromFixture(t, unusedImageSource{}, "schema2.json"),
manifestSchema2FromComponentsLikeFixture(nil),
} {
for _, name := range []string{"busybox", "example.com:5555/ns/repo:tag"} {
ref, err := reference.ParseNormalizedNamed(name)
require.NoError(t, err)
conflicts := m.EmbeddedDockerReferenceConflicts(ref)
assert.False(t, conflicts)
}
}
}
func TestManifestSchema2ImageInspectInfo(t *testing.T) {
configJSON, err := ioutil.ReadFile("fixtures/schema2-config.json")
require.NoError(t, err)
@ -407,6 +421,19 @@ func TestManifestSchema2UpdatedImage(t *testing.T) {
})
assert.Error(t, err)
// EmbeddedDockerReference:
// … is ignored
embeddedRef, err := reference.ParseNormalizedNamed("busybox")
require.NoError(t, err)
res, err = original.UpdatedImage(types.ManifestUpdateOptions{
EmbeddedDockerReference: embeddedRef,
})
require.NoError(t, err)
nonEmbeddedRef, err := reference.ParseNormalizedNamed("notbusybox:notlatest")
require.NoError(t, err)
conflicts := res.EmbeddedDockerReferenceConflicts(nonEmbeddedRef)
assert.False(t, conflicts)
// ManifestMIMEType:
// Only smoke-test the valid conversions, detailed tests are below. (This also verifies that “original” is not affected.)
for _, mime := range []string{

View file

@ -3,6 +3,7 @@ package image
import (
"time"
"github.com/containers/image/docker/reference"
"github.com/containers/image/manifest"
"github.com/containers/image/pkg/strslice"
"github.com/containers/image/types"
@ -72,6 +73,10 @@ type genericManifest interface {
// The Digest field is guaranteed to be provided; Size may be -1.
// WARNING: The list may contain duplicates, and they are semantically relevant.
LayerInfos() []types.BlobInfo
// EmbeddedDockerReferenceConflicts whether a Docker reference embedded in the manifest, if any, conflicts with destination ref.
// It returns false if the manifest does not embed a Docker reference.
// (This embedding unfortunately happens for Docker schema1, please do not add support for this in any new formats.)
EmbeddedDockerReferenceConflicts(ref reference.Named) bool
imageInspectInfo() (*types.ImageInspectInfo, error) // To be called by inspectManifest
// UpdatedImageNeedsLayerDiffIDs returns true iff UpdatedImage(options) needs InformationOnly.LayerDiffIDs.
// This is a horribly specific interface, but computing InformationOnly.LayerDiffIDs can be very expensive to compute

View file

@ -4,6 +4,7 @@ import (
"encoding/json"
"io/ioutil"
"github.com/containers/image/docker/reference"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/opencontainers/go-digest"
@ -107,6 +108,13 @@ func (m *manifestOCI1) LayerInfos() []types.BlobInfo {
return blobs
}
// EmbeddedDockerReferenceConflicts whether a Docker reference embedded in the manifest, if any, conflicts with destination ref.
// It returns false if the manifest does not embed a Docker reference.
// (This embedding unfortunately happens for Docker schema1, please do not add support for this in any new formats.)
func (m *manifestOCI1) EmbeddedDockerReferenceConflicts(ref reference.Named) bool {
return false
}
func (m *manifestOCI1) imageInspectInfo() (*types.ImageInspectInfo, error) {
config, err := m.ConfigBlob()
if err != nil {
@ -146,6 +154,7 @@ func (m *manifestOCI1) UpdatedImage(options types.ManifestUpdateOptions) (types.
copy.LayersDescriptors[i].Size = info.Size
}
}
// Ignore options.EmbeddedDockerReference: it may be set when converting from schema1, but we really don't care.
switch options.ManifestMIMEType {
case "": // No conversion, OK

View file

@ -207,6 +207,20 @@ func TestManifestOCI1LayerInfo(t *testing.T) {
}
}
func TestManifestOCI1EmbeddedDockerReferenceConflicts(t *testing.T) {
for _, m := range []genericManifest{
manifestOCI1FromFixture(t, unusedImageSource{}, "oci1.json"),
manifestOCI1FromComponentsLikeFixture(nil),
} {
for _, name := range []string{"busybox", "example.com:5555/ns/repo:tag"} {
ref, err := reference.ParseNormalizedNamed(name)
require.NoError(t, err)
conflicts := m.EmbeddedDockerReferenceConflicts(ref)
assert.False(t, conflicts)
}
}
}
func TestManifestOCI1ImageInspectInfo(t *testing.T) {
configJSON, err := ioutil.ReadFile("fixtures/oci1-config.json")
require.NoError(t, err)
@ -288,6 +302,19 @@ func TestManifestOCI1UpdatedImage(t *testing.T) {
})
assert.Error(t, err)
// EmbeddedDockerReference:
// … is ignored
embeddedRef, err := reference.ParseNormalizedNamed("busybox")
require.NoError(t, err)
res, err = original.UpdatedImage(types.ManifestUpdateOptions{
EmbeddedDockerReference: embeddedRef,
})
require.NoError(t, err)
nonEmbeddedRef, err := reference.ParseNormalizedNamed("notbusybox:notlatest")
require.NoError(t, err)
conflicts := res.EmbeddedDockerReferenceConflicts(nonEmbeddedRef)
assert.False(t, conflicts)
// ManifestMIMEType:
// Only smoke-test the valid conversions, detailed tests are below. (This also verifies that “original” is not affected.)
for _, mime := range []string{

View file

@ -0,0 +1,30 @@
{
"schemaVersion": 2,
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 7143,
"digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
"platform": {
"architecture": "ppc64le",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 7682,
"digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
"platform": {
"architecture": "amd64",
"os": "linux",
"os.features": [
"sse4"
]
}
}
],
"annotations": {
"com.example.key1": "value1",
"com.example.key2": "value2"
}
}

View file

@ -1,26 +1,29 @@
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"config": {
"mediaType": "application/vnd.oci.image.serialization.config.v1+json",
"size": 7023,
"digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7"
"schemaVersion": 2,
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"size": 7023,
"digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7"
},
"layers": [
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"size": 32654,
"digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f"
},
"layers": [
{
"mediaType": "application/vnd.oci.image.serialization.rootfs.tar.gzip",
"size": 32654,
"digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f"
},
{
"mediaType": "application/vnd.oci.image.serialization.rootfs.tar.gzip",
"size": 16724,
"digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b"
},
{
"mediaType": "application/vnd.oci.image.serialization.rootfs.tar.gzip",
"size": 73109,
"digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736"
}
]
}
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"size": 16724,
"digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b"
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"size": 73109,
"digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736"
}
],
"annotations": {
"com.example.key1": "value1",
"com.example.key2": "value2"
}
}

View file

@ -1,56 +0,0 @@
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.list.v1+json",
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 2094,
"digest": "sha256:7820f9a86d4ad15a2c4f0c0e5479298df2aa7c2f6871288e2ef8546f3e7b6783",
"platform": {
"architecture": "ppc64le",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 1922,
"digest": "sha256:ae1b0e06e8ade3a11267564a26e750585ba2259c0ecab59ab165ad1af41d1bdd",
"platform": {
"architecture": "amd64",
"os": "linux",
"features": [
"sse"
]
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 2084,
"digest": "sha256:e4c0df75810b953d6717b8f8f28298d73870e8aa2a0d5e77b8391f16fdfbbbe2",
"platform": {
"architecture": "s390x",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 2084,
"digest": "sha256:07ebe243465ef4a667b78154ae6c3ea46fdb1582936aac3ac899ea311a701b40",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "armv7"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 2090,
"digest": "sha256:fb2fc0707b86dafa9959fe3d29e66af8787aee4d9a23581714be65db4265ad8a",
"platform": {
"architecture": "arm64",
"os": "linux",
"variant": "armv8"
}
}
]
}

View file

@ -54,7 +54,7 @@ func GuessMIMEType(manifest []byte) string {
}
switch meta.MediaType {
case DockerV2Schema2MediaType, DockerV2ListMediaType, imgspecv1.MediaTypeImageManifest, imgspecv1.MediaTypeImageManifestList: // A recognized type.
case DockerV2Schema2MediaType, DockerV2ListMediaType: // A recognized type.
return meta.MediaType
}
// this is the only way the function can return DockerV2Schema1MediaType, and recognizing that is essential for stripping the JWS signatures = computing the correct manifest digest.
@ -64,7 +64,31 @@ func GuessMIMEType(manifest []byte) string {
return DockerV2Schema1SignedMediaType
}
return DockerV2Schema1MediaType
case 2: // Really should not happen, meta.MediaType should have been set. But given the data, this is our best guess.
case 2:
// best effort to understand if this is an OCI image since mediaType
// isn't in the manifest for OCI anymore
// for docker v2s2 meta.MediaType should have been set. But given the data, this is our best guess.
ociMan := struct {
Config struct {
MediaType string `json:"mediaType"`
} `json:"config"`
Layers []imgspecv1.Descriptor `json:"layers"`
}{}
if err := json.Unmarshal(manifest, &ociMan); err != nil {
return ""
}
if ociMan.Config.MediaType == imgspecv1.MediaTypeImageConfig && len(ociMan.Layers) != 0 {
return imgspecv1.MediaTypeImageManifest
}
ociIndex := struct {
Manifests []imgspecv1.Descriptor `json:"manifests"`
}{}
if err := json.Unmarshal(manifest, &ociIndex); err != nil {
return ""
}
if len(ociIndex.Manifests) != 0 && ociIndex.Manifests[0].MediaType == imgspecv1.MediaTypeImageManifest {
return imgspecv1.MediaTypeImageIndex
}
return DockerV2Schema2MediaType
}
return ""

View file

@ -21,8 +21,6 @@ func TestGuessMIMEType(t *testing.T) {
path string
mimeType string
}{
{"ociv1.manifest.json", imgspecv1.MediaTypeImageManifest},
{"ociv1list.manifest.json", imgspecv1.MediaTypeImageManifestList},
{"v2s2.manifest.json", DockerV2Schema2MediaType},
{"v2list.manifest.json", DockerV2ListMediaType},
{"v2s1.manifest.json", DockerV2Schema1SignedMediaType},
@ -31,6 +29,8 @@ func TestGuessMIMEType(t *testing.T) {
{"v2s2nomime.manifest.json", DockerV2Schema2MediaType}, // It is unclear whether this one is legal, but we should guess v2s2 if anything at all.
{"unknown-version.manifest.json", ""},
{"non-json.manifest.json", ""}, // Not a manifest (nor JSON) at all
{"ociv1.manifest.json", imgspecv1.MediaTypeImageManifest},
{"ociv1.image.index.json", imgspecv1.MediaTypeImageIndex},
}
for _, c := range cases {

View file

@ -6,22 +6,30 @@ import (
"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
ref ociReference
index imgspecv1.ImageIndex
}
// newImageDestination returns an ImageDestination for writing to an existing directory.
func newImageDestination(ref ociReference) types.ImageDestination {
return &ociImageDestination{ref: ref}
index := imgspecv1.ImageIndex{
Versioned: imgspec.Versioned{
SchemaVersion: 2,
},
}
return &ociImageDestination{ref: ref, index: index}
}
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
@ -138,6 +146,10 @@ func (d *ociImageDestination) ReapplyBlob(info types.BlobInfo) (types.BlobInfo,
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 {
@ -148,10 +160,6 @@ func (d *ociImageDestination) PutManifest(m []byte) error {
// TODO(runcom): beaware and add support for OCI manifest list
desc.MediaType = imgspecv1.MediaTypeImageManifest
desc.Size = int64(len(m))
data, err := json.Marshal(desc)
if err != nil {
return err
}
blobPath, err := d.ref.blobPath(digest)
if err != nil {
@ -163,15 +171,19 @@ func (d *ociImageDestination) PutManifest(m []byte) error {
if err := ioutil.WriteFile(blobPath, m, 0644); err != nil {
return err
}
// TODO(runcom): ugly here?
if err := ioutil.WriteFile(d.ref.ociLayoutPath(), []byte(`{"imageLayoutVersion": "1.0.0"}`), 0644); err != nil {
return err
}
descriptorPath := d.ref.descriptorPath(d.ref.tag)
if err := ensureParentDirectoryExists(descriptorPath); err != nil {
return err
}
return ioutil.WriteFile(descriptorPath, data, 0644)
annotations := make(map[string]string)
annotations["org.opencontainers.ref.name"] = d.ref.tag
desc.Annotations = annotations
d.index.Manifests = append(d.index.Manifests, imgspecv1.ManifestDescriptor{
Descriptor: desc,
Platform: imgspecv1.Platform{
Architecture: runtime.GOARCH,
OS: runtime.GOOS,
},
})
return nil
}
func ensureDirectoryExists(path string) error {
@ -200,5 +212,12 @@ func (d *ociImageDestination) PutSignatures(signatures [][]byte) error {
// - 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 {
return nil
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)
}

View file

@ -1,7 +1,6 @@
package layout
import (
"encoding/json"
"io"
"io/ioutil"
"os"
@ -12,12 +11,17 @@ import (
)
type ociImageSource struct {
ref ociReference
ref ociReference
descriptor imgspecv1.ManifestDescriptor
}
// newImageSource returns an ImageSource for reading from an existing directory.
func newImageSource(ref ociReference) types.ImageSource {
return &ociImageSource{ref: ref}
func newImageSource(ref ociReference) (types.ImageSource, error) {
descriptor, err := ref.getManifestDescriptor()
if err != nil {
return nil, err
}
return &ociImageSource{ref: ref, descriptor: descriptor}, nil
}
// Reference returns the reference used to set up this source.
@ -33,19 +37,7 @@ func (s *ociImageSource) Close() error {
// 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.
func (s *ociImageSource) GetManifest() ([]byte, string, error) {
descriptorPath := s.ref.descriptorPath(s.ref.tag)
data, err := ioutil.ReadFile(descriptorPath)
if err != nil {
return nil, "", err
}
desc := imgspecv1.Descriptor{}
err = json.Unmarshal(data, &desc)
if err != nil {
return nil, "", err
}
manifestPath, err := s.ref.blobPath(digest.Digest(desc.Digest))
manifestPath, err := s.ref.blobPath(digest.Digest(s.descriptor.Digest))
if err != nil {
return nil, "", err
}
@ -54,7 +46,7 @@ func (s *ociImageSource) GetManifest() ([]byte, string, error) {
return nil, "", err
}
return m, desc.MediaType, nil
return m, s.descriptor.MediaType, nil
}
func (s *ociImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {

View file

@ -1,7 +1,9 @@
package layout
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
@ -12,6 +14,7 @@ import (
"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"
)
@ -176,16 +179,49 @@ func (ref ociReference) PolicyConfigurationNamespaces() []string {
// 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.
func (ref ociReference) NewImage(ctx *types.SystemContext) (types.Image, error) {
src := newImageSource(ref)
src, err := newImageSource(ref)
if err != nil {
return nil, err
}
return image.FromSource(src)
}
func (ref ociReference) getManifestDescriptor() (imgspecv1.ManifestDescriptor, error) {
indexJSON, err := os.Open(ref.indexPath())
if err != nil {
return imgspecv1.ManifestDescriptor{}, err
}
defer indexJSON.Close()
index := imgspecv1.ImageIndex{}
if err := json.NewDecoder(indexJSON).Decode(&index); err != nil {
return imgspecv1.ManifestDescriptor{}, err
}
var d *imgspecv1.ManifestDescriptor
for _, md := range index.Manifests {
if md.MediaType != imgspecv1.MediaTypeImageManifest {
continue
}
refName, ok := md.Annotations["org.opencontainers.ref.name"]
if !ok {
continue
}
if refName == ref.tag {
d = &md
break
}
}
if d == nil {
return imgspecv1.ManifestDescriptor{}, fmt.Errorf("no descriptor found for reference %q", ref.tag)
}
return *d, nil
}
// NewImageSource returns a types.ImageSource for this reference,
// asking the backend to use a manifest from requestedManifestMIMETypes if possible.
// nil requestedManifestMIMETypes means manifest.DefaultRequestedManifestMIMETypes.
// The caller must call .Close() on the returned ImageSource.
func (ref ociReference) NewImageSource(ctx *types.SystemContext, requestedManifestMIMETypes []string) (types.ImageSource, error) {
return newImageSource(ref), nil
return newImageSource(ref)
}
// NewImageDestination returns a types.ImageDestination for this reference.
@ -199,11 +235,16 @@ func (ref ociReference) DeleteImage(ctx *types.SystemContext) error {
return errors.Errorf("Deleting images not implemented for oci: images")
}
// ociLayoutPathPath returns a path for the oci-layout within a directory using OCI conventions.
// 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) (string, error) {
if err := digest.Validate(); err != nil {
@ -211,8 +252,3 @@ func (ref ociReference) blobPath(digest digest.Digest) (string, error) {
}
return filepath.Join(ref.dir, "blobs", digest.Algorithm().String(), digest.Hex()), nil
}
// descriptorPath returns a path for the manifest within a directory using OCI conventions.
func (ref ociReference) descriptorPath(digest string) string {
return filepath.Join(ref.dir, "refs", digest)
}

View file

@ -115,6 +115,25 @@ func TestNewReference(t *testing.T) {
func refToTempOCI(t *testing.T) (ref types.ImageReference, tmpDir string) {
tmpDir, err := ioutil.TempDir("", "oci-transport-test")
require.NoError(t, err)
m := `{
"schemaVersion": 2,
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 7143,
"digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
"platform": {
"architecture": "ppc64le",
"os": "linux"
},
"annotations": {
"org.opencontainers.ref.name": "tagValue"
}
}
]
}
`
ioutil.WriteFile(filepath.Join(tmpDir, "index.json"), []byte(m), 0644)
ref, err = NewReference(tmpDir, "tagValue")
require.NoError(t, err)
return ref, tmpDir
@ -239,6 +258,14 @@ func TestReferenceOCILayoutPath(t *testing.T) {
assert.Equal(t, tmpDir+"/oci-layout", ociRef.ociLayoutPath())
}
func TestReferenceIndexPath(t *testing.T) {
ref, tmpDir := refToTempOCI(t)
defer os.RemoveAll(tmpDir)
ociRef, ok := ref.(ociReference)
require.True(t, ok)
assert.Equal(t, tmpDir+"/index.json", ociRef.indexPath())
}
func TestReferenceBlobPath(t *testing.T) {
const hex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
@ -262,11 +289,3 @@ func TestReferenceBlobPathInvalid(t *testing.T) {
assert.Error(t, err)
assert.Contains(t, err.Error(), "unexpected digest reference "+hex)
}
func TestReferenceDescriptorPath(t *testing.T) {
ref, tmpDir := refToTempOCI(t)
defer os.RemoveAll(tmpDir)
ociRef, ok := ref.(ociReference)
require.True(t, ok)
assert.Equal(t, tmpDir+"/refs/notlatest", ociRef.descriptorPath("notlatest"))
}

View file

@ -338,10 +338,7 @@ func (d *openshiftImageDestination) Close() error {
}
func (d *openshiftImageDestination) SupportedManifestMIMETypes() []string {
return []string{
manifest.DockerV2Schema1SignedMediaType,
manifest.DockerV2Schema1MediaType,
}
return d.docker.SupportedManifestMIMETypes()
}
// SupportsSignatures returns an error (to be displayed to the user) if the destination certainly can't store signatures.
@ -383,6 +380,10 @@ func (d *openshiftImageDestination) ReapplyBlob(info types.BlobInfo) (types.Blob
return d.docker.ReapplyBlob(info)
}
// 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 *openshiftImageDestination) PutManifest(m []byte) error {
manifestDigest, err := manifest.Digest(m)
if err != nil {

View file

@ -119,7 +119,7 @@ func (d *ostreeImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobI
return types.BlobInfo{Digest: computedDigest, Size: size}, nil
}
func fixUsermodeFiles(dir string) error {
func fixFiles(dir string, usermode bool) error {
entries, err := ioutil.ReadDir(dir)
if err != nil {
return err
@ -127,15 +127,23 @@ func fixUsermodeFiles(dir string) error {
for _, info := range entries {
fullpath := filepath.Join(dir, info.Name())
if info.IsDir() {
if err := os.Chmod(dir, info.Mode()|0700); err != nil {
if info.Mode()&(os.ModeNamedPipe|os.ModeSocket|os.ModeDevice) != 0 {
if err := os.Remove(fullpath); err != nil {
return err
}
err = fixUsermodeFiles(fullpath)
continue
}
if info.IsDir() {
if usermode {
if err := os.Chmod(fullpath, info.Mode()|0700); err != nil {
return err
}
}
err = fixFiles(fullpath, usermode)
if err != nil {
return err
}
} else if info.Mode().IsRegular() {
} else if usermode && (info.Mode().IsRegular() || (info.Mode()&os.ModeSymlink) != 0) {
if err := os.Chmod(fullpath, info.Mode()|0600); err != nil {
return err
}
@ -160,13 +168,16 @@ func (d *ostreeImageDestination) importBlob(blob *blobToImport) error {
if err := archive.UntarPath(blob.BlobPath, destinationPath); err != nil {
return err
}
if err := fixFiles(destinationPath, false); err != nil {
return err
}
} else {
os.MkdirAll(destinationPath, 0755)
if err := exec.Command("tar", "-C", destinationPath, "--no-same-owner", "--no-same-permissions", "--delay-directory-restore", "-xf", blob.BlobPath).Run(); err != nil {
return err
}
if err := fixUsermodeFiles(destinationPath); err != nil {
if err := fixFiles(destinationPath, true); err != nil {
return err
}
}
@ -207,6 +218,10 @@ func (d *ostreeImageDestination) ReapplyBlob(info types.BlobInfo) (types.BlobInf
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 *ostreeImageDestination) PutManifest(manifest []byte) error {
d.manifest = string(manifest)

View file

@ -1,5 +1,7 @@
// Note: Consider the API unstable until the code supports at least three different image formats or transports.
// NOTE: Keep this in sync with docs/atomic-signature.md and docs/atomic-signature-embedded.json!
package signature
import (

View file

@ -3,6 +3,7 @@ package signature
import (
"encoding/json"
"io/ioutil"
"path/filepath"
"testing"
"time"
@ -11,6 +12,7 @@ import (
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/xeipuuv/gojsonschema"
)
func TestInvalidSignatureError(t *testing.T) {
@ -78,33 +80,63 @@ func TestMarshalJSON(t *testing.T) {
}
}
// Return the result of modifying validJSON with fn and unmarshaling it into *sig
func tryUnmarshalModifiedSignature(t *testing.T, sig *untrustedSignature, validJSON []byte, modifyFn func(mSI)) error {
// Return the result of modifying validJSON with fn
func modifiedUntrustedSignatureJSON(t *testing.T, validJSON []byte, modifyFn func(mSI)) []byte {
var tmp mSI
err := json.Unmarshal(validJSON, &tmp)
require.NoError(t, err)
modifyFn(tmp)
testJSON, err := json.Marshal(tmp)
modifiedJSON, err := json.Marshal(tmp)
require.NoError(t, err)
return modifiedJSON
}
*sig = untrustedSignature{}
return json.Unmarshal(testJSON, sig)
// Verify that input can be unmarshaled as an untrustedSignature, and that it passes JSON schema validation, and return the unmarshaled untrustedSignature.
func succesfullyUnmarshalUntrustedSignature(t *testing.T, schemaLoader gojsonschema.JSONLoader, input []byte) untrustedSignature {
inputString := string(input)
var s untrustedSignature
err := json.Unmarshal(input, &s)
require.NoError(t, err, inputString)
res, err := gojsonschema.Validate(schemaLoader, gojsonschema.NewStringLoader(inputString))
assert.True(t, err == nil, inputString)
assert.True(t, res.Valid(), inputString)
return s
}
// Verify that input can't be unmashaled as an untrusted signature, and that it fails JSON schema validation.
func assertUnmarshalUntrustedSignatureFails(t *testing.T, schemaLoader gojsonschema.JSONLoader, input []byte) {
inputString := string(input)
var s untrustedSignature
err := json.Unmarshal(input, &s)
assert.Error(t, err, inputString)
res, err := gojsonschema.Validate(schemaLoader, gojsonschema.NewStringLoader(inputString))
assert.True(t, err != nil || !res.Valid(), inputString)
}
func TestUnmarshalJSON(t *testing.T) {
var s untrustedSignature
// NOTE: The schema at schemaPath is NOT authoritative; docs/atomic-signature.json and the code is, rather!
// The schemaPath references are not testing that the code follows the behavior declared by the schema,
// they are testing that the schema follows the behavior of the code!
schemaPath, err := filepath.Abs("../docs/atomic-signature-embedded-json.json")
require.NoError(t, err)
schemaLoader := gojsonschema.NewReferenceLoader("file://" + schemaPath)
// Invalid input. Note that json.Unmarshal is guaranteed to validate input before calling our
// UnmarshalJSON implementation; so test that first, then test our error handling for completeness.
err := json.Unmarshal([]byte("&"), &s)
assert.Error(t, err)
assertUnmarshalUntrustedSignatureFails(t, schemaLoader, []byte("&"))
var s untrustedSignature
err = s.UnmarshalJSON([]byte("&"))
assert.Error(t, err)
// Not an object
err = json.Unmarshal([]byte("1"), &s)
assert.Error(t, err)
assertUnmarshalUntrustedSignatureFails(t, schemaLoader, []byte("1"))
// Start with a valid JSON.
validSig := newUntrustedSignature("digest!@#", "reference#@!")
@ -112,9 +144,7 @@ func TestUnmarshalJSON(t *testing.T) {
require.NoError(t, err)
// Success
s = untrustedSignature{}
err = json.Unmarshal(validJSON, &s)
require.NoError(t, err)
s = succesfullyUnmarshalUntrustedSignature(t, schemaLoader, validJSON)
assert.Equal(t, validSig, s)
// Various ways to corrupt the JSON
@ -156,8 +186,8 @@ func TestUnmarshalJSON(t *testing.T) {
func(v mSI) { x(v, "optional")["timestamp"] = 0.5 }, // Fractional input
}
for _, fn := range breakFns {
err = tryUnmarshalModifiedSignature(t, &s, validJSON, fn)
assert.Error(t, err)
testJSON := modifiedUntrustedSignatureJSON(t, validJSON, fn)
assertUnmarshalUntrustedSignatureFails(t, schemaLoader, testJSON)
}
// Modifications to unrecognized fields in "optional" are allowed and ignored
@ -166,8 +196,8 @@ func TestUnmarshalJSON(t *testing.T) {
func(v mSI) { x(v, "optional")["unexpected"] = 1 },
}
for _, fn := range allowedModificationFns {
err = tryUnmarshalModifiedSignature(t, &s, validJSON, fn)
require.NoError(t, err)
testJSON := modifiedUntrustedSignatureJSON(t, validJSON, fn)
s := succesfullyUnmarshalUntrustedSignature(t, schemaLoader, testJSON)
assert.Equal(t, validSig, s)
}
@ -180,9 +210,7 @@ func TestUnmarshalJSON(t *testing.T) {
}
validJSON, err = validSig.MarshalJSON()
require.NoError(t, err)
s = untrustedSignature{}
err = json.Unmarshal(validJSON, &s)
require.NoError(t, err)
s = succesfullyUnmarshalUntrustedSignature(t, schemaLoader, validJSON)
assert.Equal(t, validSig, s)
}

View file

@ -13,9 +13,9 @@ import (
"github.com/containers/image/image"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/containers/storage"
"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/ioutils"
"github.com/containers/storage/storage"
ddigest "github.com/opencontainers/go-digest"
)
@ -138,9 +138,9 @@ func (s *storageImageDestination) putBlob(stream io.Reader, blobinfo types.BlobI
Size: -1,
}
// Try to read an initial snippet of the blob.
header := make([]byte, 10240)
n, err := stream.Read(header)
if err != nil && err != io.EOF {
buf := [archive.HeaderSize]byte{}
n, err := io.ReadAtLeast(stream, buf[:], len(buf))
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
return errorBlobInfo, err
}
// Set up to read the whole blob (the initial snippet, plus the rest)
@ -154,9 +154,9 @@ func (s *storageImageDestination) putBlob(stream io.Reader, blobinfo types.BlobI
}
hash := ""
counter := ioutils.NewWriteCounter(hasher.Hash())
defragmented := io.MultiReader(bytes.NewBuffer(header[:n]), stream)
defragmented := io.MultiReader(bytes.NewBuffer(buf[:n]), stream)
multi := io.TeeReader(defragmented, counter)
if (n > 0) && archive.IsArchive(header[:n]) {
if (n > 0) && archive.IsArchive(buf[:n]) {
// It's a filesystem layer. If it's not the first one in the
// image, we assume that the most recently added layer is its
// parent.
@ -307,7 +307,7 @@ func (s *storageImageDestination) ReapplyBlob(blobinfo types.BlobInfo) (types.Bl
return types.BlobInfo{}, err
}
if layerList, ok := s.Layers[blobinfo.Digest]; !ok || len(layerList) < 1 {
b, err := s.imageRef.transport.store.GetImageBigData(s.ID, blobinfo.Digest.String())
b, err := s.imageRef.transport.store.ImageBigData(s.ID, blobinfo.Digest.String())
if err != nil {
return types.BlobInfo{}, err
}
@ -335,7 +335,7 @@ func (s *storageImageDestination) Commit() error {
logrus.Debugf("error creating image: %q", err)
return errors.Wrapf(err, "error creating image %q", s.ID)
}
img, err = s.imageRef.transport.store.GetImage(s.ID)
img, err = s.imageRef.transport.store.Image(s.ID)
if err != nil {
return errors.Wrapf(err, "error reading image %q", s.ID)
}
@ -420,6 +420,10 @@ func (s *storageImageDestination) SupportedManifestMIMETypes() []string {
return 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 (s *storageImageDestination) PutManifest(manifest []byte) error {
s.Manifest = make([]byte, len(manifest))
copy(s.Manifest, manifest)
@ -464,7 +468,7 @@ func (s *storageImageSource) getBlobAndLayerID(info types.BlobInfo) (rc io.ReadC
return nil, -1, "", err
}
if layerList, ok := s.Layers[info.Digest]; !ok || len(layerList) < 1 {
b, err := s.imageRef.transport.store.GetImageBigData(s.ID, info.Digest.String())
b, err := s.imageRef.transport.store.ImageBigData(s.ID, info.Digest.String())
if err != nil {
return nil, -1, "", err
}
@ -488,7 +492,7 @@ func (s *storageImageSource) getBlobAndLayerID(info types.BlobInfo) (rc io.ReadC
}
func diffLayer(store storage.Store, layerID string) (rc io.ReadCloser, n int64, err error) {
layer, err := store.GetLayer(layerID)
layer, err := store.Layer(layerID)
if err != nil {
return nil, -1, err
}
@ -513,7 +517,7 @@ func diffLayer(store storage.Store, layerID string) (rc io.ReadCloser, n int64,
}
func (s *storageImageSource) GetManifest() (manifestBlob []byte, MIMEType string, err error) {
manifestBlob, err = s.imageRef.transport.store.GetImageBigData(s.ID, "manifest")
manifestBlob, err = s.imageRef.transport.store.ImageBigData(s.ID, "manifest")
return manifestBlob, manifest.GuessMIMEType(manifestBlob), err
}
@ -523,7 +527,7 @@ func (s *storageImageSource) GetTargetManifest(digest ddigest.Digest) (manifestB
func (s *storageImageSource) GetSignatures() (signatures [][]byte, err error) {
var offset int
signature, err := s.imageRef.transport.store.GetImageBigData(s.ID, "signatures")
signature, err := s.imageRef.transport.store.ImageBigData(s.ID, "signatures")
if err != nil {
return nil, err
}
@ -545,7 +549,7 @@ func (s *storageImageSource) getSize() (int64, error) {
return -1, errors.Wrapf(err, "error reading image %q", s.imageRef.id)
}
for _, name := range names {
bigSize, err := s.imageRef.transport.store.GetImageBigDataSize(s.imageRef.id, name)
bigSize, err := s.imageRef.transport.store.ImageBigDataSize(s.imageRef.id, name)
if err != nil {
return -1, errors.Wrapf(err, "error reading data blob size %q for %q", name, s.imageRef.id)
}
@ -556,7 +560,7 @@ func (s *storageImageSource) getSize() (int64, error) {
}
for _, layerList := range s.Layers {
for _, layerID := range layerList {
layer, err := s.imageRef.transport.store.GetLayer(layerID)
layer, err := s.imageRef.transport.store.Layer(layerID)
if err != nil {
return -1, err
}

View file

@ -6,7 +6,7 @@ import (
"github.com/Sirupsen/logrus"
"github.com/containers/image/docker/reference"
"github.com/containers/image/types"
"github.com/containers/storage/storage"
"github.com/containers/storage"
"github.com/pkg/errors"
)
@ -37,7 +37,7 @@ func newReference(transport storageTransport, reference, id string, name referen
// one present with the same name or ID, and return the image.
func (s *storageReference) resolveImage() (*storage.Image, error) {
if s.id == "" {
image, err := s.transport.store.GetImage(s.reference)
image, err := s.transport.store.Image(s.reference)
if image != nil && err == nil {
s.id = image.ID
}
@ -46,7 +46,7 @@ func (s *storageReference) resolveImage() (*storage.Image, error) {
logrus.Errorf("reference %q does not resolve to an image ID", s.StringWithinTransport())
return nil, ErrNoSuchImage
}
img, err := s.transport.store.GetImage(s.id)
img, err := s.transport.store.Image(s.id)
if err != nil {
return nil, errors.Wrapf(err, "error reading image %q", s.id)
}
@ -83,7 +83,7 @@ func (s storageReference) DockerReference() reference.Named {
// disambiguate between images which may be present in multiple stores and
// share only their names.
func (s storageReference) StringWithinTransport() string {
storeSpec := "[" + s.transport.store.GetGraphDriverName() + "@" + s.transport.store.GetGraphRoot() + "]"
storeSpec := "[" + s.transport.store.GraphDriverName() + "@" + s.transport.store.GraphRoot() + "]"
if s.name == nil {
return storeSpec + "@" + s.id
}
@ -102,8 +102,8 @@ func (s storageReference) PolicyConfigurationIdentity() string {
// graph root, in case we're using multiple drivers in the same directory for
// some reason.
func (s storageReference) PolicyConfigurationNamespaces() []string {
storeSpec := "[" + s.transport.store.GetGraphDriverName() + "@" + s.transport.store.GetGraphRoot() + "]"
driverlessStoreSpec := "[" + s.transport.store.GetGraphRoot() + "]"
storeSpec := "[" + s.transport.store.GraphDriverName() + "@" + s.transport.store.GraphRoot() + "]"
driverlessStoreSpec := "[" + s.transport.store.GraphRoot() + "]"
namespaces := []string{}
if s.name != nil {
if s.id != "" {

View file

@ -57,7 +57,7 @@ var validReferenceTestCases = []struct {
func TestStorageReferenceStringWithinTransport(t *testing.T) {
store := newStore(t)
storeSpec := fmt.Sprintf("[%s@%s]", store.GetGraphDriverName(), store.GetGraphRoot())
storeSpec := fmt.Sprintf("[%s@%s]", store.GraphDriverName(), store.GraphRoot())
for _, c := range validReferenceTestCases {
ref, err := Transport.ParseReference(c.input)
@ -68,7 +68,7 @@ func TestStorageReferenceStringWithinTransport(t *testing.T) {
func TestStorageReferencePolicyConfigurationIdentity(t *testing.T) {
store := newStore(t)
storeSpec := fmt.Sprintf("[%s@%s]", store.GetGraphDriverName(), store.GetGraphRoot())
storeSpec := fmt.Sprintf("[%s@%s]", store.GraphDriverName(), store.GraphRoot())
for _, c := range validReferenceTestCases {
ref, err := Transport.ParseReference(c.input)
@ -79,7 +79,7 @@ func TestStorageReferencePolicyConfigurationIdentity(t *testing.T) {
func TestStorageReferencePolicyConfigurationNamespaces(t *testing.T) {
store := newStore(t)
storeSpec := fmt.Sprintf("[%s@%s]", store.GetGraphDriverName(), store.GetGraphRoot())
storeSpec := fmt.Sprintf("[%s@%s]", store.GraphDriverName(), store.GraphRoot())
for _, c := range validReferenceTestCases {
ref, err := Transport.ParseReference(c.input)
@ -89,7 +89,7 @@ func TestStorageReferencePolicyConfigurationNamespaces(t *testing.T) {
expectedNS = append(expectedNS, storeSpec+ns)
}
expectedNS = append(expectedNS, storeSpec)
expectedNS = append(expectedNS, fmt.Sprintf("[%s]", store.GetGraphRoot()))
expectedNS = append(expectedNS, fmt.Sprintf("[%s]", store.GraphRoot()))
assert.Equal(t, expectedNS, ref.PolicyConfigurationNamespaces())
}
}

View file

@ -17,11 +17,11 @@ import (
"github.com/Sirupsen/logrus"
"github.com/containers/image/types"
"github.com/containers/storage"
"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/idtools"
"github.com/containers/storage/pkg/ioutils"
"github.com/containers/storage/pkg/reexec"
"github.com/containers/storage/storage"
ddigest "github.com/opencontainers/go-digest"
)

View file

@ -10,7 +10,7 @@ import (
"github.com/containers/image/docker/reference"
"github.com/containers/image/transports"
"github.com/containers/image/types"
"github.com/containers/storage/storage"
"github.com/containers/storage"
"github.com/opencontainers/go-digest"
ddigest "github.com/opencontainers/go-digest"
)
@ -110,7 +110,7 @@ func (s storageTransport) ParseStoreReference(store storage.Store, ref string) (
// recognize.
return nil, ErrInvalidReference
}
storeSpec := "[" + store.GetGraphDriverName() + "@" + store.GetGraphRoot() + "]"
storeSpec := "[" + store.GraphDriverName() + "@" + store.GraphRoot() + "]"
id := ""
if sum.Validate() == nil {
id = sum.Hex()
@ -205,14 +205,14 @@ func (s storageTransport) GetStoreImage(store storage.Store, ref types.ImageRefe
if dref == nil {
if sref, ok := ref.(*storageReference); ok {
if sref.id != "" {
if img, err := store.GetImage(sref.id); err == nil {
if img, err := store.Image(sref.id); err == nil {
return img, nil
}
}
}
return nil, ErrInvalidReference
}
return store.GetImage(verboseName(dref))
return store.Image(verboseName(dref))
}
func (s *storageTransport) GetImage(ref types.ImageReference) (*storage.Image, error) {

View file

@ -65,8 +65,8 @@ func TestTransportParseStoreReference(t *testing.T) {
func TestTransportParseReference(t *testing.T) {
store := newStore(t)
driver := store.GetGraphDriverName()
root := store.GetGraphRoot()
driver := store.GraphDriverName()
root := store.GraphRoot()
for _, c := range []struct{ prefix, expectedDriver, expectedRoot string }{
{"", driver, root}, // Implicit store location prefix
@ -93,16 +93,16 @@ func TestTransportParseReference(t *testing.T) {
require.NoError(t, err, c.prefix)
storageRef, ok := ref.(*storageReference)
require.True(t, ok, c.prefix)
assert.Equal(t, c.expectedDriver, storageRef.transport.store.GetGraphDriverName(), c.prefix)
assert.Equal(t, c.expectedRoot, storageRef.transport.store.GetGraphRoot(), c.prefix)
assert.Equal(t, c.expectedDriver, storageRef.transport.store.GraphDriverName(), c.prefix)
assert.Equal(t, c.expectedRoot, storageRef.transport.store.GraphRoot(), c.prefix)
}
}
}
func TestTransportValidatePolicyConfigurationScope(t *testing.T) {
store := newStore(t)
driver := store.GetGraphDriverName()
root := store.GetGraphRoot()
driver := store.GraphDriverName()
root := store.GraphRoot()
storeSpec := fmt.Sprintf("[%s@%s]", driver, root) // As computed in PolicyConfigurationNamespaces
// Valid inputs

View file

@ -167,8 +167,11 @@ type ImageDestination interface {
HasBlob(info BlobInfo) (bool, int64, error)
// ReapplyBlob informs the image destination that a blob for which HasBlob previously returned true would have been passed to PutBlob if it had returned false. Like HasBlob and unlike PutBlob, the digest can not be empty. If the blob is a filesystem layer, this signifies that the changes it describes need to be applied again when composing a filesystem tree.
ReapplyBlob(info BlobInfo) (BlobInfo, error)
// PutManifest writes manifest to the destination.
// FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
PutManifest([]byte) error
// 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.
PutManifest(manifest []byte) error
PutSignatures(signatures [][]byte) error
// 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:
@ -177,6 +180,16 @@ type ImageDestination interface {
Commit() error
}
// ManifestTypeRejectedError is returned by ImageDestination.PutManifest if the destination is in principle available,
// refuses specifically this manifest type, but may accept a different manifest type.
type ManifestTypeRejectedError struct { // We only use a struct to allow a type assertion, without limiting the contents of the error otherwise.
Err error
}
func (e ManifestTypeRejectedError) Error() string {
return e.Err.Error()
}
// UnparsedImage is an Image-to-be; until it is verified and accepted, it only caries its identity and caches manifest and signature blobs.
// Thus, an UnparsedImage can be created from an ImageSource simply by fetching blobs without interpreting them,
// allowing cryptographic signature verification to happen first, before even fetching the manifest, or parsing anything else.
@ -213,6 +226,10 @@ type Image interface {
// The Digest field is guaranteed to be provided; Size may be -1.
// WARNING: The list may contain duplicates, and they are semantically relevant.
LayerInfos() []BlobInfo
// EmbeddedDockerReferenceConflicts whether a Docker reference embedded in the manifest, if any, conflicts with destination ref.
// It returns false if the manifest does not embed a Docker reference.
// (This embedding unfortunately happens for Docker schema1, please do not add support for this in any new formats.)
EmbeddedDockerReferenceConflicts(ref reference.Named) bool
// Inspect returns various information for (skopeo inspect) parsed from the manifest and configuration.
Inspect() (*ImageInspectInfo, error)
// UpdatedImageNeedsLayerDiffIDs returns true iff UpdatedImage(options) needs InformationOnly.LayerDiffIDs.
@ -232,8 +249,9 @@ type Image interface {
// ManifestUpdateOptions is a way to pass named optional arguments to Image.UpdatedManifest
type ManifestUpdateOptions struct {
LayerInfos []BlobInfo // Complete BlobInfos (size+digest+urls) which should replace the originals, in order (the root layer first, and then successive layered layers)
ManifestMIMEType string
LayerInfos []BlobInfo // Complete BlobInfos (size+digest+urls) which should replace the originals, in order (the root layer first, and then successive layered layers)
EmbeddedDockerReference reference.Named
ManifestMIMEType string
// The values below are NOT requests to modify the image; they provide optional context which may or may not be used.
InformationOnly ManifestUpdateInformation
}

View file

@ -1,5 +1,5 @@
github.com/Sirupsen/logrus 7f4b1adc791766938c29457bed0703fb9134421a
github.com/containers/storage 5cbbc6bafb45bd7ef10486b673deb3b81bb3b787
github.com/containers/storage master
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
github.com/docker/distribution df5327f76fb6468b84a87771e361762b8be23fdb
github.com/docker/docker 75843d36aa5c3eaade50da005f9e0ff2602f3d5e
@ -15,7 +15,7 @@ github.com/mattn/go-shellwords 005a0944d84452842197c2108bd9168ced206f78
github.com/mistifyio/go-zfs c0224de804d438efd11ea6e52ada8014537d6062
github.com/mtrmac/gpgme b2432428689ca58c2b8e8dea9449d3295cf96fc9
github.com/opencontainers/go-digest aa2ec055abd10d26d539eb630a92241b781ce4bc
github.com/opencontainers/image-spec v1.0.0-rc4
github.com/opencontainers/image-spec v1.0.0-rc5
github.com/opencontainers/runc 6b1d0e76f239ffb435445e5ae316d2676c07c6e3
github.com/pborman/uuid 1b00554d822231195d1babd97ff4a781231955c9
github.com/pkg/errors 248dadf4e9068a0b3e79f02ed0a610d935de5302
@ -29,3 +29,7 @@ gopkg.in/cheggaaa/pb.v1 d7e6ca3010b6f084d8056847f55d7f572f180678
gopkg.in/yaml.v2 a3f3340b5840cee44f372bddb5880fcbc419b46a
k8s.io/client-go bcde30fb7eaed76fd98a36b4120321b94995ffb6
github.com/xeipuuv/gojsonschema master
github.com/xeipuuv/gojsonreference master
github.com/xeipuuv/gojsonpointer master
github.com/tchap/go-patricia v2.2.6
github.com/opencontainers/selinux ba1aefe8057f1d0cfb8e88d0ec1dc85925ef987d

View file

@ -6,8 +6,8 @@ import (
"io/ioutil"
"os"
"github.com/containers/storage"
"github.com/containers/storage/pkg/mflag"
"github.com/containers/storage/storage"
)
var (
@ -22,7 +22,7 @@ func container(flags *mflag.FlagSet, action string, m storage.Store, args []stri
}
matches := []*storage.Container{}
for _, arg := range args {
if container, err := m.GetContainer(arg); err == nil {
if container, err := m.Container(arg); err == nil {
matches = append(matches, container)
}
}
@ -56,7 +56,7 @@ func container(flags *mflag.FlagSet, action string, m storage.Store, args []stri
}
func listContainerBigData(flags *mflag.FlagSet, action string, m storage.Store, args []string) int {
container, err := m.GetContainer(args[0])
container, err := m.Container(args[0])
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
@ -73,7 +73,7 @@ func listContainerBigData(flags *mflag.FlagSet, action string, m storage.Store,
}
func getContainerBigData(flags *mflag.FlagSet, action string, m storage.Store, args []string) int {
container, err := m.GetContainer(args[0])
container, err := m.Container(args[0])
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
@ -87,7 +87,7 @@ func getContainerBigData(flags *mflag.FlagSet, action string, m storage.Store, a
}
output = f
}
b, err := m.GetContainerBigData(container.ID, args[1])
b, err := m.ContainerBigData(container.ID, args[1])
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
@ -98,7 +98,7 @@ func getContainerBigData(flags *mflag.FlagSet, action string, m storage.Store, a
}
func setContainerBigData(flags *mflag.FlagSet, action string, m storage.Store, args []string) int {
container, err := m.GetContainer(args[0])
container, err := m.Container(args[0])
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
@ -126,7 +126,7 @@ func setContainerBigData(flags *mflag.FlagSet, action string, m storage.Store, a
}
func getContainerDir(flags *mflag.FlagSet, action string, m storage.Store, args []string) int {
path, err := m.GetContainerDirectory(args[0])
path, err := m.ContainerDirectory(args[0])
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
@ -136,7 +136,7 @@ func getContainerDir(flags *mflag.FlagSet, action string, m storage.Store, args
}
func getContainerRunDir(flags *mflag.FlagSet, action string, m storage.Store, args []string) int {
path, err := m.GetContainerRunDirectory(args[0])
path, err := m.ContainerRunDirectory(args[0])
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1

View file

@ -5,8 +5,8 @@ import (
"fmt"
"os"
"github.com/containers/storage"
"github.com/containers/storage/pkg/mflag"
"github.com/containers/storage/storage"
)
func containers(flags *mflag.FlagSet, action string, m storage.Store, args []string) int {

View file

@ -7,9 +7,9 @@ import (
"io/ioutil"
"os"
"github.com/containers/storage"
"github.com/containers/storage/opts"
"github.com/containers/storage/pkg/mflag"
"github.com/containers/storage/storage"
)
var (
@ -51,13 +51,13 @@ func importLayer(flags *mflag.FlagSet, action string, m storage.Store, args []st
}
diffStream := io.Reader(os.Stdin)
if applyDiffFile != "" {
if f, err := os.Open(applyDiffFile); err != nil {
f, err := os.Open(applyDiffFile)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
} else {
diffStream = f
defer f.Close()
}
diffStream = f
defer f.Close()
}
layer, _, err := m.PutLayer(paramID, parent, paramNames, paramMountLabel, !paramCreateRO, diffStream)
if err != nil {

View file

@ -5,8 +5,8 @@ import (
"fmt"
"os"
"github.com/containers/storage"
"github.com/containers/storage/pkg/mflag"
"github.com/containers/storage/storage"
)
var testDeleteImage = false

View file

@ -6,9 +6,9 @@ import (
"io"
"os"
"github.com/containers/storage"
"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/mflag"
"github.com/containers/storage/storage"
)
var (
@ -63,13 +63,13 @@ func diff(flags *mflag.FlagSet, action string, m storage.Store, args []string) i
}
diffStream := io.Writer(os.Stdout)
if diffFile != "" {
if f, err := os.Create(diffFile); err != nil {
f, err := os.Create(diffFile)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
} else {
diffStream = f
defer f.Close()
}
diffStream = f
defer f.Close()
}
reader, err := m.Diff(from, to)
if err != nil {
@ -108,13 +108,13 @@ func applyDiff(flags *mflag.FlagSet, action string, m storage.Store, args []stri
}
diffStream := io.Reader(os.Stdin)
if applyDiffFile != "" {
if f, err := os.Open(applyDiffFile); err != nil {
f, err := os.Open(applyDiffFile)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
} else {
diffStream = f
defer f.Close()
}
diffStream = f
defer f.Close()
}
_, err := m.ApplyDiff(args[0], diffStream)
if err != nil {

View file

@ -5,8 +5,8 @@ import (
"fmt"
"os"
"github.com/containers/storage"
"github.com/containers/storage/pkg/mflag"
"github.com/containers/storage/storage"
)
var (
@ -26,17 +26,17 @@ func exist(flags *mflag.FlagSet, action string, m storage.Store, args []string)
exists := m.Exists(what)
existDict[what] = exists
if existContainer {
if c, err := m.GetContainer(what); c == nil || err != nil {
if c, err := m.Container(what); c == nil || err != nil {
exists = false
}
}
if existImage {
if i, err := m.GetImage(what); i == nil || err != nil {
if i, err := m.Image(what); i == nil || err != nil {
exists = false
}
}
if existLayer {
if l, err := m.GetLayer(what); l == nil || err != nil {
if l, err := m.Layer(what); l == nil || err != nil {
exists = false
}
}

View file

@ -6,8 +6,8 @@ import (
"io/ioutil"
"os"
"github.com/containers/storage"
"github.com/containers/storage/pkg/mflag"
"github.com/containers/storage/storage"
)
var (
@ -17,7 +17,7 @@ var (
func image(flags *mflag.FlagSet, action string, m storage.Store, args []string) int {
matched := []*storage.Image{}
for _, arg := range args {
if image, err := m.GetImage(arg); err == nil {
if image, err := m.Image(arg); err == nil {
matched = append(matched, image)
}
}
@ -42,7 +42,7 @@ func image(flags *mflag.FlagSet, action string, m storage.Store, args []string)
}
func listImageBigData(flags *mflag.FlagSet, action string, m storage.Store, args []string) int {
image, err := m.GetImage(args[0])
image, err := m.Image(args[0])
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
@ -59,7 +59,7 @@ func listImageBigData(flags *mflag.FlagSet, action string, m storage.Store, args
}
func getImageBigData(flags *mflag.FlagSet, action string, m storage.Store, args []string) int {
image, err := m.GetImage(args[0])
image, err := m.Image(args[0])
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
@ -73,7 +73,7 @@ func getImageBigData(flags *mflag.FlagSet, action string, m storage.Store, args
}
output = f
}
b, err := m.GetImageBigData(image.ID, args[1])
b, err := m.ImageBigData(image.ID, args[1])
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
@ -84,7 +84,7 @@ func getImageBigData(flags *mflag.FlagSet, action string, m storage.Store, args
}
func setImageBigData(flags *mflag.FlagSet, action string, m storage.Store, args []string) int {
image, err := m.GetImage(args[0])
image, err := m.Image(args[0])
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1

View file

@ -5,8 +5,8 @@ import (
"fmt"
"os"
"github.com/containers/storage"
"github.com/containers/storage/pkg/mflag"
"github.com/containers/storage/storage"
)
func images(flags *mflag.FlagSet, action string, m storage.Store, args []string) int {

View file

@ -5,8 +5,8 @@ import (
"fmt"
"os"
"github.com/containers/storage"
"github.com/containers/storage/pkg/mflag"
"github.com/containers/storage/storage"
)
var listLayersTree = false

View file

@ -5,10 +5,10 @@ import (
"os"
"github.com/Sirupsen/logrus"
"github.com/containers/storage"
"github.com/containers/storage/opts"
"github.com/containers/storage/pkg/mflag"
"github.com/containers/storage/pkg/reexec"
"github.com/containers/storage/storage"
)
type command struct {

View file

@ -7,8 +7,8 @@ import (
"os"
"strings"
"github.com/containers/storage"
"github.com/containers/storage/pkg/mflag"
"github.com/containers/storage/storage"
)
var metadataQuiet = false
@ -20,7 +20,7 @@ func metadata(flags *mflag.FlagSet, action string, m storage.Store, args []strin
metadataDict := make(map[string]string)
missingAny := false
for _, what := range args {
if metadata, err := m.GetMetadata(what); err == nil {
if metadata, err := m.Metadata(what); err == nil {
metadataDict[what] = strings.TrimSuffix(metadata, "\n")
} else {
missingAny = true

View file

@ -5,8 +5,8 @@ import (
"fmt"
"os"
"github.com/containers/storage"
"github.com/containers/storage/pkg/mflag"
"github.com/containers/storage/storage"
)
type mountPointOrError struct {

View file

@ -5,9 +5,9 @@ import (
"fmt"
"os"
"github.com/containers/storage"
"github.com/containers/storage/opts"
"github.com/containers/storage/pkg/mflag"
"github.com/containers/storage/storage"
)
func addNames(flags *mflag.FlagSet, action string, m storage.Store, args []string) int {
@ -19,7 +19,7 @@ func addNames(flags *mflag.FlagSet, action string, m storage.Store, args []strin
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
}
oldnames, err := m.GetNames(id)
oldnames, err := m.Names(id)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
@ -35,7 +35,7 @@ func addNames(flags *mflag.FlagSet, action string, m storage.Store, args []strin
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
}
names, err := m.GetNames(id)
names, err := m.Names(id)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
@ -59,7 +59,7 @@ func setNames(flags *mflag.FlagSet, action string, m storage.Store, args []strin
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
}
names, err := m.GetNames(id)
names, err := m.Names(id)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1

View file

@ -5,8 +5,8 @@ import (
"fmt"
"os"
"github.com/containers/storage"
"github.com/containers/storage/pkg/mflag"
"github.com/containers/storage/storage"
)
var (

View file

@ -5,8 +5,8 @@ import (
"fmt"
"os"
"github.com/containers/storage"
"github.com/containers/storage/pkg/mflag"
"github.com/containers/storage/storage"
)
func status(flags *mflag.FlagSet, action string, m storage.Store, args []string) int {

View file

@ -5,8 +5,8 @@ import (
"fmt"
"os"
"github.com/containers/storage"
"github.com/containers/storage/pkg/mflag"
"github.com/containers/storage/storage"
)
func version(flags *mflag.FlagSet, action string, m storage.Store, args []string) int {

View file

@ -5,8 +5,8 @@ import (
"fmt"
"os"
"github.com/containers/storage"
"github.com/containers/storage/pkg/mflag"
"github.com/containers/storage/storage"
)
func wipe(flags *mflag.FlagSet, action string, m storage.Store, args []string) int {

View file

@ -264,7 +264,7 @@ func (r *containerStore) Create(id string, names []string, image, layer, metadat
return container, err
}
func (r *containerStore) GetMetadata(id string) (string, error) {
func (r *containerStore) Metadata(id string) (string, error) {
if container, ok := r.lookup(id); ok {
return container.Metadata, nil
}
@ -347,7 +347,7 @@ func (r *containerStore) Exists(id string) bool {
return ok
}
func (r *containerStore) GetBigData(id, key string) ([]byte, error) {
func (r *containerStore) BigData(id, key string) ([]byte, error) {
c, ok := r.lookup(id)
if !ok {
return nil, ErrContainerUnknown
@ -355,7 +355,7 @@ func (r *containerStore) GetBigData(id, key string) ([]byte, error) {
return ioutil.ReadFile(r.datapath(c.ID, key))
}
func (r *containerStore) GetBigDataSize(id, key string) (int64, error) {
func (r *containerStore) BigDataSize(id, key string) (int64, error) {
c, ok := r.lookup(id)
if !ok {
return -1, ErrContainerUnknown
@ -366,7 +366,7 @@ func (r *containerStore) GetBigDataSize(id, key string) (int64, error) {
return -1, ErrSizeUnknown
}
func (r *containerStore) GetBigDataNames(id string) ([]string, error) {
func (r *containerStore) BigDataNames(id string) ([]string, error) {
c, ok := r.lookup(id)
if !ok {
return nil, ErrContainerUnknown

View file

@ -185,8 +185,8 @@ func (a *Driver) Status() [][2]string {
}
}
// GetMetadata not implemented
func (a *Driver) GetMetadata(id string) (map[string]string, error) {
// Metadata not implemented
func (a *Driver) Metadata(id string) (map[string]string, error) {
return nil, nil
}

View file

@ -143,8 +143,8 @@ func (d *Driver) Status() [][2]string {
return status
}
// GetMetadata returns empty metadata for this driver.
func (d *Driver) GetMetadata(id string) (map[string]string, error) {
// Metadata returns empty metadata for this driver.
func (d *Driver) Metadata(id string) (map[string]string, error) {
return nil, nil
}

View file

@ -94,8 +94,8 @@ func (d *Driver) Status() [][2]string {
return status
}
// GetMetadata returns a map of information about the device.
func (d *Driver) GetMetadata(id string) (map[string]string, error) {
// Metadata returns a map of information about the device.
func (d *Driver) Metadata(id string) (map[string]string, error) {
m, err := d.DeviceSet.exportDeviceMetadata(id)
if err != nil {

View file

@ -69,7 +69,7 @@ type ProtoDriver interface {
Status() [][2]string
// Returns a set of key-value pairs which give low level information
// about the image/container driver is managing.
GetMetadata(id string) (map[string]string, error)
Metadata(id string) (map[string]string, error)
// Cleanup performs necessary tasks to release resources
// held by the driver, e.g., unmounting all layered filesystems
// known to this driver.

View file

@ -226,9 +226,9 @@ func (d *Driver) Status() [][2]string {
}
}
// GetMetadata returns meta data about the overlay driver such as
// Metadata returns meta data about the overlay driver such as
// LowerDir, UpperDir, WorkDir and MergeDir used to store data.
func (d *Driver) GetMetadata(id string) (map[string]string, error) {
func (d *Driver) Metadata(id string) (map[string]string, error) {
dir := d.dir(id)
if _, err := os.Stat(dir); err != nil {
return nil, err

View file

@ -144,12 +144,12 @@ func (d *graphDriverProxy) Status() [][2]string {
return ret.Status
}
func (d *graphDriverProxy) GetMetadata(id string) (map[string]string, error) {
func (d *graphDriverProxy) Metadata(id string) (map[string]string, error) {
args := &graphDriverRequest{
ID: id,
}
var ret graphDriverResponse
if err := d.client.Call("GraphDriver.GetMetadata", args, &ret); err != nil {
if err := d.client.Call("GraphDriver.Metadata", args, &ret); err != nil {
return nil, err
}
if ret.Err != "" {

View file

@ -58,8 +58,8 @@ func (d *Driver) Status() [][2]string {
return nil
}
// GetMetadata is used for implementing the graphdriver.ProtoDriver interface. VFS does not currently have any meta data.
func (d *Driver) GetMetadata(id string) (map[string]string, error) {
// Metadata is used for implementing the graphdriver.ProtoDriver interface. VFS does not currently have any meta data.
func (d *Driver) Metadata(id string) (map[string]string, error) {
return nil, nil
}

View file

@ -133,7 +133,7 @@ func (d *Driver) create(id, parent, mountLabel string, readOnly bool, storageOpt
var layerChain []string
if rPId != "" {
parentPath, err := hcsshim.GetLayerMountPath(d.info, rPId)
parentPath, err := hcsshim.LayerMountPath(d.info, rPId)
if err != nil {
return err
}
@ -248,7 +248,7 @@ func (d *Driver) Get(id, mountLabel string) (string, error) {
return "", err
}
mountPath, err := hcsshim.GetLayerMountPath(d.info, rID)
mountPath, err := hcsshim.LayerMountPath(d.info, rID)
if err != nil {
d.ctr.Decrement(rID)
if err2 := hcsshim.DeactivateLayer(d.info, rID); err2 != nil {
@ -403,7 +403,7 @@ func (d *Driver) ApplyDiff(id, parent string, diff archive.Reader) (int64, error
if err != nil {
return 0, err
}
parentPath, err := hcsshim.GetLayerMountPath(d.info, rPId)
parentPath, err := hcsshim.LayerMountPath(d.info, rPId)
if err != nil {
return 0, err
}
@ -446,8 +446,8 @@ func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
return archive.ChangesSize(layerFs, changes), nil
}
// GetMetadata returns custom driver information.
func (d *Driver) GetMetadata(id string) (map[string]string, error) {
// Metadata returns custom driver information.
func (d *Driver) Metadata(id string) (map[string]string, error) {
m := make(map[string]string)
m["dir"] = d.dir(id)
return m, nil

View file

@ -210,8 +210,8 @@ func (d *Driver) Status() [][2]string {
}
}
// GetMetadata returns image/container metadata related to graph driver
func (d *Driver) GetMetadata(id string) (map[string]string, error) {
// Metadata returns image/container metadata related to graph driver
func (d *Driver) Metadata(id string) (map[string]string, error) {
return nil, nil
}

View file

@ -249,7 +249,7 @@ func (r *imageStore) Create(id string, names []string, layer, metadata string) (
return image, err
}
func (r *imageStore) GetMetadata(id string) (string, error) {
func (r *imageStore) Metadata(id string) (string, error) {
if image, ok := r.lookup(id); ok {
return image.Metadata, nil
}
@ -331,7 +331,7 @@ func (r *imageStore) Exists(id string) bool {
return ok
}
func (r *imageStore) GetBigData(id, key string) ([]byte, error) {
func (r *imageStore) BigData(id, key string) ([]byte, error) {
image, ok := r.lookup(id)
if !ok {
return nil, ErrImageUnknown
@ -339,7 +339,7 @@ func (r *imageStore) GetBigData(id, key string) ([]byte, error) {
return ioutil.ReadFile(r.datapath(image.ID, key))
}
func (r *imageStore) GetBigDataSize(id, key string) (int64, error) {
func (r *imageStore) BigDataSize(id, key string) (int64, error) {
image, ok := r.lookup(id)
if !ok {
return -1, ErrImageUnknown
@ -350,7 +350,7 @@ func (r *imageStore) GetBigDataSize(id, key string) (int64, error) {
return -1, ErrSizeUnknown
}
func (r *imageStore) GetBigDataNames(id string) ([]string, error) {
func (r *imageStore) BigDataNames(id string) ([]string, error) {
image, ok := r.lookup(id)
if !ok {
return nil, ErrImageUnknown

View file

@ -163,7 +163,6 @@ type layerStore struct {
idindex *truncindex.TruncIndex
byid map[string]*Layer
byname map[string]*Layer
byparent map[string][]*Layer
bymount map[string]*Layer
}
@ -231,7 +230,6 @@ func (r *layerStore) Load() error {
r.idindex = truncindex.NewTruncIndex(idlist)
r.byid = ids
r.byname = names
r.byparent = parents
r.bymount = mounts
err = nil
// Last step: try to remove anything that a previous user of this
@ -309,7 +307,6 @@ func newLayerStore(rundir string, layerdir string, driver drivers.Driver) (Layer
byid: make(map[string]*Layer),
bymount: make(map[string]*Layer),
byname: make(map[string]*Layer),
byparent: make(map[string][]*Layer),
}
if err := rlstore.Load(); err != nil {
return nil, err
@ -400,12 +397,6 @@ func (r *layerStore) Put(id, parent string, names []string, mountLabel string, o
for _, name := range names {
r.byname[name] = layer
}
if pslice, ok := r.byparent[parent]; ok {
pslice = append(pslice, layer)
r.byparent[parent] = pslice
} else {
r.byparent[parent] = []*Layer{layer}
}
for flag, value := range flags {
layer.Flags[flag] = value
}
@ -520,7 +511,7 @@ func (r *layerStore) SetNames(id string, names []string) error {
return ErrLayerUnknown
}
func (r *layerStore) GetMetadata(id string) (string, error) {
func (r *layerStore) Metadata(id string) (string, error) {
if layer, ok := r.lookup(id); ok {
return layer.Metadata, nil
}
@ -553,23 +544,8 @@ func (r *layerStore) Delete(id string) error {
err := r.driver.Remove(id)
if err == nil {
os.Remove(r.tspath(id))
pslice := r.byparent[layer.Parent]
newPslice := []*Layer{}
for _, candidate := range pslice {
if candidate.ID != id {
newPslice = append(newPslice, candidate)
}
}
delete(r.byid, id)
r.idindex.Delete(id)
if len(newPslice) > 0 {
r.byparent[layer.Parent] = newPslice
} else {
delete(r.byparent, layer.Parent)
}
for _, name := range layer.Names {
delete(r.byname, name)
}
if layer.MountPoint != "" {
delete(r.bymount, layer.MountPoint)
}
@ -619,11 +595,12 @@ func (r *layerStore) Wipe() error {
return nil
}
func (r *layerStore) findParentAndLayer(from, to string) (fromID string, toID string, fromLayer *Layer, toLayer *Layer, err error) {
func (r *layerStore) findParentAndLayer(from, to string) (fromID string, toID string, toLayer *Layer, err error) {
var ok bool
var fromLayer *Layer
toLayer, ok = r.lookup(to)
if !ok {
return "", "", nil, nil, ErrLayerUnknown
return "", "", nil, ErrLayerUnknown
}
to = toLayer.ID
if from == "" {
@ -631,19 +608,20 @@ func (r *layerStore) findParentAndLayer(from, to string) (fromID string, toID st
}
if from != "" {
fromLayer, ok = r.lookup(from)
if !ok {
if ok {
from = fromLayer.ID
} else {
fromLayer, ok = r.lookup(toLayer.Parent)
if !ok {
return "", "", nil, nil, ErrParentUnknown
if ok {
from = fromLayer.ID
}
}
from = fromLayer.ID
}
return from, to, fromLayer, toLayer, nil
return from, to, toLayer, nil
}
func (r *layerStore) Changes(from, to string) ([]archive.Change, error) {
from, to, _, _, err := r.findParentAndLayer(from, to)
from, to, _, err := r.findParentAndLayer(from, to)
if err != nil {
return nil, ErrLayerUnknown
}
@ -682,7 +660,7 @@ func (r *layerStore) newFileGetter(id string) (drivers.FileGetCloser, error) {
func (r *layerStore) Diff(from, to string) (io.ReadCloser, error) {
var metadata storage.Unpacker
from, to, _, toLayer, err := r.findParentAndLayer(from, to)
from, to, toLayer, err := r.findParentAndLayer(from, to)
if err != nil {
return nil, ErrLayerUnknown
}
@ -772,7 +750,7 @@ func (r *layerStore) Diff(from, to string) (io.ReadCloser, error) {
}
func (r *layerStore) DiffSize(from, to string) (size int64, err error) {
from, to, _, _, err = r.findParentAndLayer(from, to)
from, to, _, err = r.findParentAndLayer(from, to)
if err != nil {
return -1, ErrLayerUnknown
}

View file

@ -26,7 +26,7 @@ DOC_FILES := \
descriptor.md \
image-layout.md \
manifest.md \
manifest-list.md \
image-index.md \
layer.md \
config.md \
annotations.md \
@ -40,7 +40,7 @@ DOC_FILENAME ?= oci-image-spec
EPOCH_TEST_COMMIT ?= v0.2.0
default: help
default: check-license lint test
help:
@echo "Usage: make <target>"
@ -75,12 +75,14 @@ $(OUTPUT_DIRNAME)/$(DOC_FILENAME).html: $(DOC_FILES) $(FIGURE_FILES)
ls -sh $(shell readlink -f $@)
endif
validate-examples:
validate-examples: schema/fs.go
go test -run TestValidate ./schema
schema-fs:
schema/fs.go: $(wildcard schema/*.json) schema/gen.go
cd schema && printf "%s\n\n%s\n" "$$(cat ../.header)" "$$(go generate)" > fs.go
schema-fs: schema/fs.go
@echo "generating schema fs"
@cd schema && printf "%s\n\n%s\n" "$$(cat ../.header)" "$$(go generate)" > fs.go
check-license:
@echo "checking license headers"
@ -90,7 +92,7 @@ lint:
@echo "checking lint"
@./.tool/lint
test:
test: schema/fs.go
go test -race -cover $(shell go list ./... | grep -v /vendor/)
img/%.png: img/%.dot
@ -129,4 +131,5 @@ clean:
clean \
lint \
docs \
test
test \
schema-fs

View file

@ -19,7 +19,7 @@ Additional documentation about how this group operates:
- [Releases](RELEASES.md)
- [Project Documentation](project.md)
The _optional_ and _base_ layers of all OCI projects are tracked in the [OCI Scope Table](https://www.opencontainers.org/governance/oci-scope-table).
The _optional_ and _base_ layers of all OCI projects are tracked in the [OCI Scope Table](https://www.opencontainers.org/about/oci-scope-table).
## Running an OCI Image
@ -39,7 +39,7 @@ To support this UX the OCI Image Format contains sufficient information to launc
**Q: Why doesn't this project mention distribution?**
A: Distribution, for example using HTTP as both Docker v2.2 and AppC do today, is currently out of scope on the [OCI Scope Table](https://www.opencontainers.org/governance/oci-scope-table).
A: Distribution, for example using HTTP as both Docker v2.2 and AppC do today, is currently out of scope on the [OCI Scope Table](https://www.opencontainers.org/about/oci-scope-table).
There has been [some discussion on the TOB mailing list](https://groups.google.com/a/opencontainers.org/d/msg/tob/A3JnmI-D-6Y/tLuptPDHAgAJ) to make distribution an optional layer, but this topic is a work in progress.
**Q: Why a new project?**

View file

@ -15,9 +15,10 @@ Consumers MUST NOT generate an error if they encounter an unknown annotation key
## Pre-Defined Annotation Keys
This specification defines the following annotation keys, intended for but not limited to manifest list and image manifest authors:
This specification defines the following annotation keys, intended for but not limited to [image index](image-index.md) and image [manifest](manifest.md) authors:
* **org.opencontainers.created** date on which the image was built (string, date-time as defined by [RFC 3339](https://tools.ietf.org/html/rfc3339#section-5.6)).
* **org.opencontainers.authors** contact details of the people or organization responsible for the image (freeform string)
* **org.opencontainers.homepage** URL to find more information on the image (string, a URL with scheme HTTP or HTTPS)
* **org.opencontainers.documentation** URL to get documentation on the image (string, a URL with scheme HTTP or HTTPS)
* **org.opencontainers.source** URL to get source code for the binary files in the image (string, a URL with scheme HTTP or HTTPS)
* **org.opencontainers.ref.name** Name of the reference (string)

View file

@ -229,4 +229,4 @@ Here is an example image configuration JSON document:
```
[rfc3339-s5.6]: https://tools.ietf.org/html/rfc3339#section-5.6
[runtime-platform]: https://github.com/opencontainers/runtime-spec/blob/v1.0.0-rc2/config.md#platform
[runtime-platform]: https://github.com/opencontainers/runtime-spec/blob/v1.0.0-rc3/config.md#platform

View file

@ -1,6 +1,6 @@
# Extensibility
Implementations that are reading/processing [manifests](manifest.md) or [manifest lists](manifest-list.md) MUST NOT generate an error if they encounter an unknown property.
Implementations that are reading/processing [manifests](manifest.md) or [image indexes](image-index.md) MUST NOT generate an error if they encounter an unknown property.
Instead they MUST ignore unknown properties.
# Canonicalization

View file

@ -1,17 +1,19 @@
# OCI Image Manifest List Specification
# OCI Image Index Specification
The manifest list is a higher-level manifest which points to specific [image manifests](manifest.md) for one or more platforms.
While the use of a manifest list is OPTIONAL for image providers, image consumers SHOULD be prepared to process them.
The image index is a higher-level manifest which points to specific [image manifests](manifest.md), ideal for one or more platforms.
While the use of an image index is OPTIONAL for image providers, image consumers SHOULD be prepared to process them.
This section defines the `application/vnd.oci.image.manifest.list.v1+json` [media type](media-types.md).
This section defines the `application/vnd.oci.image.index.v1+json` [media type](media-types.md).
For the media type(s) that this document is compatible with, see the [matrix][matrix].
## *Manifest List* Property Descriptions
## *Image Index* Property Descriptions
- **`schemaVersion`** *int*
This REQUIRED property specifies the image manifest schema version.
For this version of the specification, this MUST be `2` to ensure backward compatibility with older versions of Docker. The value of this field will not change. This field MAY be removed in a future version of the specification.
For this version of the specification, this MUST be `2` to ensure backward compatibility with older versions of Docker.
The value of this field will not change.
This field MAY be removed in a future version of the specification.
- **`mediaType`** *string*
@ -20,10 +22,10 @@ For the media type(s) that this document is compatible with, see the [matrix][ma
- **`manifests`** *array of objects*
This REQUIRED property contains a list of manifests for specific platforms.
While the property MUST be present, the size of the array MAY be zero.
This REQUIRED property contains a list of [manifests](manifest.md) for specific platforms.
While this property MUST be present, the size of the array MAY be zero.
Each object in `manifests` is a [descriptor](descriptor.md) with the following additional properties and restrictions:
Each object in `manifests` has the base properties of [descriptor](descriptor.md) with the following additional properties and restrictions:
- **`mediaType`** *string*
@ -32,21 +34,24 @@ For the media type(s) that this document is compatible with, see the [matrix][ma
- [`application/vnd.oci.image.manifest.v1+json`](manifest.md)
Manifest lists concerned with portability SHOULD use one of the above media types.
Image indexes concerned with portability SHOULD use one of the above media types.
Future versions of the spec MAY use a different mediatype (i.e. a new versioned format).
An encountered `mediaType` that is unknown SHOULD be safely ignored.
- **`platform`** *object*
This REQUIRED property describes the platform which the image in the manifest runs on.
This OPTIONAL property describes the platform which the image in the manifest runs on.
This property SHOULD be present if its target is platform-specific.
- **`architecture`** *string*
This REQUIRED property specifies the CPU architecture.
Manifest lists SHOULD use, and implementations SHOULD understand, values [supported by runtime-spec's `platform.arch`][runtime-platform2].
Image indexes SHOULD use, and implementations SHOULD understand, values [supported by runtime-spec's `platform.arch`][runtime-platform2].
- **`os`** *string*
This REQUIRED property specifies the operating system.
Manifest lists SHOULD use, and implementations SHOULD understand, values [supported by runtime-spec's `platform.os`][runtime-platform2].
Image indexes SHOULD use, and implementations SHOULD understand, values [supported by runtime-spec's `platform.os`][runtime-platform2].
- **`os.version`** *string*
@ -66,15 +71,15 @@ For the media type(s) that this document is compatible with, see the [matrix][ma
- **`annotations`** *string-string map*
This OPTIONAL property contains arbitrary metadata for the manifest list.
This OPTIONAL property contains arbitrary metadata for the image index.
This OPTIONAL property MUST use the [annotation rules](annotations.md#rules).
See [Pre-Defined Annotation Keys](annotations.md#pre-defined-annotation-keys).
## Example Manifest List
## Example Image Index
*Example showing a simple manifest list pointing to image manifests for two platforms:*
```json,title=Manifest%20List&mediatype=application/vnd.oci.image.manifest.list.v1%2Bjson
*Example showing a simple image index pointing to image manifests for two platforms:*
```json,title=Image%20Index&mediatype=application/vnd.oci.image.index.v1%2Bjson
{
"schemaVersion": 2,
"manifests": [
@ -107,5 +112,5 @@ For the media type(s) that this document is compatible with, see the [matrix][ma
}
```
[runtime-platform2]: https://github.com/opencontainers/runtime-spec/blob/v1.0.0-rc2/config.md#platform
[runtime-platform2]: https://github.com/opencontainers/runtime-spec/blob/v1.0.0-rc3/config.md#platform
[matrix]: media-types.md#compatibility-matrix

View file

@ -3,29 +3,32 @@
The OCI Image Layout is a slash separated layout of OCI content-addressable blobs and [location-addressable](https://en.wikipedia.org/wiki/Content-addressable_storage#Content-addressed_vs._location-addressed) references (refs).
This layout MAY be used in a variety of different transport mechanisms: archive formats (e.g. tar, zip), shared filesystem environments (e.g. nfs), or networked file fetching (e.g. http, ftp, rsync).
Given an image layout and a ref, a tool can create an [OCI Runtime Specification bundle](https://github.com/opencontainers/runtime-spec/blob/v1.0.0-rc2/bundle.md) by:
Given an image layout and a ref, a tool can create an [OCI Runtime Specification bundle](https://github.com/opencontainers/runtime-spec/blob/v1.0.0-rc3/bundle.md) by:
* Following the ref to find a [manifest](manifest.md#image-manifest), possibly via a [manifest list](manifest-list.md#manifest-list)
* Following the ref to find a [manifest](manifest.md#image-manifest), possibly via an [image index](image-index.md)
* [Applying the filesystem layers](layer.md#applying) in the specified order
* Converting the [image configuration](config.md) into an [OCI Runtime Specification `config.json`](https://github.com/opencontainers/runtime-spec/blob/v1.0.0-rc2/config.md)
* Converting the [image configuration](config.md) into an [OCI Runtime Specification `config.json`](https://github.com/opencontainers/runtime-spec/blob/v1.0.0-rc3/config.md)
# Content
The image layout MUST contain two top level directories:
The image layout is as follows:
- `blobs` contains content-addressable blobs.
A blob has no schema and should be considered opaque.
- `refs` contains [descriptors][descriptors].
Commonly pointing to an [image manifest](manifest.md#image-manifest) or an [image manifest list](manifest-list.md#oci-image-manifest-list-specification).
Both `blobs` and `refs` MAY be empty.
The image layout MUST also contain an `oci-layout` file:
- It MUST be a JSON object
- It MUST contain an `imageLayoutVersion` field
- The `imageLayoutVersion` value will align with the OCI Image Specification version at the time changes to the layout are made, and will pin a given version until changes to the layout are required
- It MAY include additional fields
- `blobs` directory
- Contains content-addressable blobs
- A blob has no schema and should be considered opaque
- Directory MUST exist and MAY be empty
- See [blobs](#blobs) section
- `oci-layout` file
- It MUST exist
- It MUST be a JSON object
- It MUST contain an `imageLayoutVersion` field
- See [oci-layout file](#oci-layout-file) section
- It MAY include additional fields
- `index.json` file
- It MUST exist
- It MUST be a JSON object
- It MUST have the base properties of an [image index](image-index.md).
- See [index.json](#indexjson-file) section
## Example Layout
@ -33,17 +36,12 @@ This is an example image layout:
```
$ cd example.com/app/
$ find .
.
./blobs
./blobs/sha256/e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f
./blobs/sha256/afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51
./blobs/sha256/9b97579de92b1c195b85bb42a11011378ee549b02d7fe9c17bf2a6b35d5cb079
./blobs/sha256/5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270
$ find . -type f
./index.json
./oci-layout
./refs
./refs/v1.0
./refs/stable-release
./blobs/sha256/3588d02542238316759cbf24502f4344ffcc8a60c803870022f335d1390c13b4
./blobs/sha256/4b0bc1c4050b03c95ef2a8e36e25feac42fd31283e8c30b3ee5df6b043155d3c
./blobs/sha256/7968321274dc6b6171697c33df7815310468e694ac5be0ec03ff053bb135e768
```
Blobs are named by their contents:
@ -53,33 +51,6 @@ $ shasum -a 256 ./blobs/sha256/afff3924849e458c5ef237db5f89539274d5e609db5db935e
afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 ./blobs/sha256/afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51
```
## Refs
Object names in the `refs` subdirectories MUST NOT include characters outside of the set of "A" to "Z", "a" to "z", "0" to "9", the hyphen `-`, the dot `.`, and the underscore `_`.
No semantic restriction is given for object names in the `refs` subdirectory.
Each object in the `refs` subdirectory MUST be of type `application/vnd.oci.descriptor.v1+json`.
In general the `mediaType` of this [descriptor][descriptors] object will be either `application/vnd.oci.image.manifest.list.v1+json` or `application/vnd.oci.image.manifest.v1+json` although future versions of the spec MAY use a different mediatype.
**Implementor's Note:**
A common use case of refs is representing "tags" for a container image.
For example, an image may have a tag for different versions or builds of the software.
In the wild you often see "tags" like "v1.0.0-vendor.0", "2.0.0-debug", etc.
Those tags will often be represented in an image-layout repository with matching refs names like "v1.0.0-vendor.0", "2.0.0-debug", etc.
### Example Ref
This is an example `v1.0` ref with a manifest-list descriptor:
```
$ cat ./refs/v1.0 | jq
{
"size": 4096,
"digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
"mediaType": "application/vnd.oci.image.manifest.list.v1+json"
}
```
## Blobs
Object names in the `blobs` subdirectories are composed of a directory for each hash algorithm, the children of which will contain the actual content.
@ -87,7 +58,7 @@ A blob, referenced with digest `<alg>:<hex>` (per [descriptor](descriptor.md#dig
The character set of the entry name for `<hex>` and `<alg>` MUST match the respective grammar elements described in [descriptor](descriptor.md#digests-and-verification).
For example `sha256:5b` will map to the layout `blobs/sha256/5b`.
The blobs directory MAY contain blobs which are not referenced by any of the refs.
The blobs directory MAY contain blobs which are not referenced by any of the [refs](#indexjson-file).
The blobs directory MAY be missing referenced blobs, in which case the missing blobs SHOULD be fulfilled by an external blob store.
@ -156,4 +127,82 @@ $ cat ./blobs/sha256/e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7f
[tar stream]
```
## oci-layout file
This JSON object serves as a marker for the base of an Open Container Image Layout and to provide the version of the image-layout in use.
The `imageLayoutVersion` value will align with the OCI Image Specification version at the time changes to the layout are made, and will pin a given version until changes to the image layout are required.
### oci-layout Example
```json
{
"imageLayoutVersion": "1.0.0"
}
```
## index.json file
This REQUIRED file is the entry point for references and descriptors of the image-layout.
The [image index](image-index.md) is a multi-descriptor entry point.
This index provides an established path (`/index.json`) to have an entry point for an image-layout and to discover auxiliary descriptors.
No semantic restriction is given for the "org.opencontainers.ref.name" annotation of descriptors.
In general the `mediaType` of each [descriptor][descriptors] object in the `manifests` field will be either `application/vnd.oci.image.index.v1+json` or `application/vnd.oci.image.manifest.v1+json`.
Future versions of the spec MAY use a different mediatype (i.e. a new versioned format).
An encountered `mediaType` that is unknown SHOULD be safely ignored.
**Implementor's Note:**
A common use case of descriptors with a "org.opencontainers.ref.name" annotation is representing a "tag" for a container image.
For example, an image may have a tag for different versions or builds of the software.
In the wild you often see "tags" like "v1.0.0-vendor.0", "2.0.0-debug", etc.
Those tags will often be represented in an image-layout repository with matching "org.opencontainers.ref.name" annotations like "v1.0.0-vendor.0", "2.0.0-debug", etc.
### Index Example
```json,title=Image%20Index&mediatype=application/vnd.oci.image.index.v1%2Bjson
{
"schemaVersion": 2,
"manifests": [
{
"mediaType": "application/vnd.oci.image.index.v1+json",
"size": 7143,
"digest": "sha256:0228f90e926ba6b96e4f39cf294b2586d38fbb5a1e385c05cd1ee40ea54fe7fd",
"annotations": {
"org.opencontainers.ref.name": "stable-release"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 7143,
"digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
"platform": {
"architecture": "ppc64le",
"os": "linux"
},
"annotations": {
"org.opencontainers.ref.name": "v1.0"
}
},
{
"mediaType": "application/xml",
"size": 7143,
"digest": "sha256:b3d63d132d21c3ff4c35a061adf23cf43da8ae054247e32faa95494d904a007e",
"annotations": {
"org.freedesktop.specifications.metainfo.version": "1.0",
"org.freedesktop.specifications.metainfo.type": "AppStream"
}
}
],
"annotations": {
"com.example.index.revision": "r124356"
}
}
```
This illustrates an index that provides two named manifest references and an auxiliary mediatype for this image layout.
[descriptors]: ./descriptor.md

View file

@ -1,12 +1,12 @@
digraph G {
{
manifestList [shape=note, label="Manifest list\n<<optional>>\napplication/vnd.oci.image.manifest.list.v1+json"]
imageIndex [shape=note, label="Image Index\n<<optional>>\napplication/vnd.oci.image.index.v1+json"]
manifest [shape=note, label="Image manifest\napplication/vnd.oci.image.manifest.v1+json"]
config [shape=note, label="Image JSON\napplication/vnd.oci.image.config.v1+json"]
layer [shape=note, label="Layer tar archive\napplication/vnd.oci.image.layer.v1.tar\napplication/vnd.oci.image.layer.v1.tar+gzip\napplication/vnd.oci.image.layer.nondistributable.v1.tar\napplication/vnd.oci.image.layer.nondistributable.v1.tar+gzip"]
}
manifestList -> manifest [label="1..*"]
imageIndex -> manifest [label="1..*"]
manifest -> config [label="1..1"]
manifest -> layer [label="1..*"]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View file

@ -3,7 +3,7 @@
There are three main goals of the Image Manifest Specification.
The first goal is content-addressable images, by supporting an image model where the image's configuration can be hashed to generate a unique ID for the image and its components.
The second goal is to allow multi-architecture images, through a "fat manifest" which references image manifests for platform-specific versions of an image.
In OCI, this is codified in a [Manifest List](manifest-list.md).
In OCI, this is codified in an [image index](image-index.md).
The third goal is to be translatable to the [OCI Runtime Specification](https://github.com/opencontainers/runtime-spec).
This section defines the `application/vnd.oci.image.manifest.v1+json` [media type](media-types.md).
@ -11,7 +11,7 @@ For the media type(s) that this is compatible with see the [matrix](media-types.
# Image Manifest
Unlike the [Manifest List](manifest-list.md), which contains information about a set of images that can span a variety of architectures and operating systems, an image manifest provides a configuration and set of layers for a single container image for a specific architecture and operating system.
Unlike the [image index](image-index.md), which contains information about a set of images that can span a variety of architectures and operating systems, an image manifest provides a configuration and set of layers for a single container image for a specific architecture and operating system.
## *Image Manifest* Property Descriptions

View file

@ -3,7 +3,7 @@
The following media types identify the formats described here and their referenced resources:
- `application/vnd.oci.descriptor.v1+json`: [Content Descriptor](descriptor.md)
- `application/vnd.oci.image.manifest.list.v1+json`: [Manifest list](manifest-list.md#manifest-list)
- `application/vnd.oci.image.index.v1+json`: [Image Index](image-index.md)
- `application/vnd.oci.image.manifest.v1+json`: [Image manifest](manifest.md#image-manifest)
- `application/vnd.oci.image.config.v1+json`: [Image config](config.md)
- `application/vnd.oci.image.layer.v1.tar`: ["Layer", as a tar archive](layer.md)
@ -31,7 +31,7 @@ The OCI Image Specification strives to be backwards and forwards compatible when
Breaking compatibility with existing systems creates a burden on users whether they are build systems, distribution systems, container engines, etc.
This section shows where the OCI Image Specification is compatible with formats external to the OCI Image and different versions of this specification.
### application/vnd.oci.image.manifest.list.v1+json
### application/vnd.oci.image.index.v1+json
**Similar/related schema**
@ -62,6 +62,6 @@ The following figure shows how the above media types reference each other:
![](img/media-types.png)
[Descriptors](descriptor.md) are used for all references.
The manifest list being a "fat manifest" references one or more image manifests per target platform. An image manifest references exactly one target configuration and possibly many layers.
The image-index being a "fat manifest" references one or more image manifests per target platform. An image manifest references exactly one target configuration and possibly many layers.
[rfc1952]: https://tools.ietf.org/html/rfc1952

View file

@ -25,7 +25,7 @@ import (
)
var compatMap = map[string]string{
"application/vnd.docker.distribution.manifest.list.v2+json": v1.MediaTypeImageManifestList,
"application/vnd.docker.distribution.manifest.list.v2+json": v1.MediaTypeImageIndex,
"application/vnd.docker.distribution.manifest.v2+json": v1.MediaTypeImageManifest,
"application/vnd.docker.image.rootfs.diff.tar.gzip": v1.MediaTypeImageLayer,
"application/vnd.docker.container.image.v1+json": v1.MediaTypeImageConfig,
@ -42,15 +42,15 @@ func convertFormats(input string) string {
return out
}
func TestBackwardsCompatibilityManifestList(t *testing.T) {
func TestBackwardsCompatibilityImageIndex(t *testing.T) {
for i, tt := range []struct {
manifestlist string
digest digest.Digest
fail bool
imageIndex string
digest digest.Digest
fail bool
}{
{
digest: "sha256:219f4b61132fe9d09b0ec5c15517be2ca712e4744b0e0cc3be71295b35b2a467",
manifestlist: `{
imageIndex: `{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
@ -109,14 +109,14 @@ func TestBackwardsCompatibilityManifestList(t *testing.T) {
fail: false,
},
} {
got := digest.FromString(tt.manifestlist)
got := digest.FromString(tt.imageIndex)
if tt.digest != got {
t.Errorf("test %d: expected digest %s but got %s", i, tt.digest, got)
}
manifestlist := convertFormats(tt.manifestlist)
r := strings.NewReader(manifestlist)
err := schema.MediaTypeManifestList.Validate(r)
imageIndex := convertFormats(tt.imageIndex)
r := strings.NewReader(imageIndex)
err := schema.ValidatorMediaTypeImageIndex.Validate(r)
if got := err != nil; tt.fail != got {
t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err)
@ -178,7 +178,7 @@ func TestBackwardsCompatibilityManifest(t *testing.T) {
manifest := convertFormats(tt.manifest)
r := strings.NewReader(manifest)
err := schema.MediaTypeManifest.Validate(r)
err := schema.ValidatorMediaTypeManifest.Validate(r)
if got := err != nil; tt.fail != got {
t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err)
@ -217,7 +217,7 @@ func TestBackwardsCompatibilityConfig(t *testing.T) {
config := convertFormats(tt.config)
r := strings.NewReader(config)
err := schema.MediaTypeImageConfig.Validate(r)
err := schema.ValidatorMediaTypeImageConfig.Validate(r)
if got := err != nil; tt.fail != got {
t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err)

View file

@ -18,15 +18,114 @@
"type": "string"
},
"config": {
"$ref": "defs-config.json#/definitions/config"
"type": "object",
"properties": {
"User": {
"type": "string"
},
"ExposedPorts": {
"$ref": "defs.json#/definitions/mapStringObject"
},
"Env": {
"type": "array",
"items": {
"type": "string"
}
},
"Entrypoint": {
"oneOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "null"
}
]
},
"Cmd": {
"oneOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "null"
}
]
},
"Volumes": {
"oneOf": [
{
"$ref": "defs.json#/definitions/mapStringObject"
},
{
"type": "null"
}
]
},
"WorkingDir": {
"type": "string"
},
"Labels": {
"oneOf": [
{
"$ref": "defs.json#/definitions/mapStringString"
},
{
"type": "null"
}
]
}
}
},
"rootfs": {
"$ref": "defs-config.json#/definitions/rootfs"
"type": "object",
"properties": {
"diff_ids": {
"type": "array",
"items": {
"type": "string"
}
},
"type": {
"type": "string",
"enum": [
"layers"
]
}
},
"required": [
"diff_ids",
"type"
]
},
"history": {
"type": "array",
"items": {
"$ref": "defs-config.json#/definitions/history"
"type": "object",
"properties": {
"created": {
"type": "string",
"format": "date-time"
},
"author": {
"type": "string"
},
"created_by": {
"type": "string"
},
"comment": {
"type": "string"
},
"empty_layer": {
"type": "boolean"
}
}
}
}
},

View file

@ -211,7 +211,7 @@ func TestConfig(t *testing.T) {
},
} {
r := strings.NewReader(tt.config)
err := schema.MediaTypeImageConfig.Validate(r)
err := schema.ValidatorMediaTypeImageConfig.Validate(r)
if got := err != nil; tt.fail != got {
t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err)

View file

@ -23,6 +23,10 @@
"type": "string",
"format": "uri"
}
},
"annotations": {
"id": "https://opencontainers.org/schema/image/descriptor/annotations",
"$ref": "defs-image.json#/definitions/annotations"
}
},
"required": [

View file

@ -1,113 +0,0 @@
{
"description": "Definitions particular to OpenContainer Config Specification",
"definitions": {
"config": {
"type": "object",
"properties": {
"User": {
"type": "string"
},
"ExposedPorts": {
"$ref": "defs.json#/definitions/mapStringObject"
},
"Env": {
"type": "array",
"items": {
"type": "string"
}
},
"Entrypoint": {
"oneOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "null"
}
]
},
"Cmd": {
"oneOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "null"
}
]
},
"Volumes": {
"oneOf": [
{
"$ref": "defs.json#/definitions/mapStringObject"
},
{
"type": "null"
}
]
},
"WorkingDir": {
"type": "string"
},
"Labels": {
"oneOf": [
{
"$ref": "defs.json#/definitions/mapStringString"
},
{
"type": "null"
}
]
}
}
},
"rootfs": {
"type": "object",
"properties": {
"diff_ids": {
"type": "array",
"items": {
"type": "string"
}
},
"type": {
"type": "string",
"enum": [
"layers"
]
}
},
"required": [
"diff_ids",
"type"
]
},
"history": {
"type": "object",
"properties": {
"created": {
"type": "string",
"format": "date-time"
},
"author": {
"type": "string"
},
"created_by": {
"type": "string"
},
"comment": {
"type": "string"
},
"empty_layer": {
"type": "boolean"
}
}
}
}
}

View file

@ -17,8 +17,7 @@
"required": [
"mediaType",
"size",
"digest",
"platform"
"digest"
],
"properties": {
"mediaType": {
@ -78,6 +77,10 @@
}
}
}
},
"annotations": {
"id": "https://opencontainers.org/schema/image/descriptor/annotations",
"$ref": "#/definitions/annotations"
}
}
},

View file

@ -24,7 +24,7 @@ import (
func TestDescriptor(t *testing.T) {
for i, tt := range []struct {
descriptor string
fail bool
fail bool
}{
// valid descriptor
{
@ -204,7 +204,7 @@ func TestDescriptor(t *testing.T) {
},
} {
r := strings.NewReader(tt.descriptor)
err := schema.MediaTypeDescriptor.Validate(r)
err := schema.ValidatorMediaTypeDescriptor.Validate(r)
if got := err != nil; tt.fail != got {
t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err)

File diff suppressed because it is too large Load diff

View file

@ -18,4 +18,4 @@ package schema
// using esc (https://github.com/mjibson/esc).
// This should generally be invoked with `make schema-fs`
//go:generate esc -private -pkg=schema -ignore=.*go .
//go:generate esc -private -pkg=schema -ignore=.*go -ignore=.*swp .

Some files were not shown because too many files have changed in this diff Show more