Skip to content

Commit 0d8bb75

Browse files
authored
Merge pull request #148 from cyphar/implement-docker-imagefile-transport
docker: add docker-archive transport
2 parents 9fcd2ba + 9fd09cd commit 0d8bb75

13 files changed

Lines changed: 1067 additions & 536 deletions

File tree

docker/archive/dest.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package archive
2+
3+
import (
4+
"io"
5+
"os"
6+
7+
"github.com/containers/image/docker/tarfile"
8+
"github.com/containers/image/types"
9+
"github.com/pkg/errors"
10+
)
11+
12+
type archiveImageDestination struct {
13+
*tarfile.Destination // Implements most of types.ImageDestination
14+
ref archiveReference
15+
writer io.Closer
16+
}
17+
18+
func newImageDestination(ctx *types.SystemContext, ref archiveReference) (types.ImageDestination, error) {
19+
if ref.destinationRef == nil {
20+
return nil, errors.Errorf("docker-archive: destination reference not supplied (must be of form <path>:<reference:tag>)")
21+
}
22+
fh, err := os.OpenFile(ref.path, os.O_WRONLY|os.O_EXCL|os.O_CREATE, 0644)
23+
if err != nil {
24+
// FIXME: It should be possible to modify archives, but the only really
25+
// sane way of doing it is to create a copy of the image, modify
26+
// it and then do a rename(2).
27+
if os.IsExist(err) {
28+
err = errors.New("docker-archive doesn't support modifying existing images")
29+
}
30+
return nil, err
31+
}
32+
33+
return &archiveImageDestination{
34+
Destination: tarfile.NewDestination(fh, ref.destinationRef),
35+
ref: ref,
36+
writer: fh,
37+
}, nil
38+
}
39+
40+
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
41+
// e.g. it should use the public hostname instead of the result of resolving CNAMEs or following redirects.
42+
func (d *archiveImageDestination) Reference() types.ImageReference {
43+
return d.ref
44+
}
45+
46+
// Close removes resources associated with an initialized ImageDestination, if any.
47+
func (d *archiveImageDestination) Close() error {
48+
return d.writer.Close()
49+
}
50+
51+
// Commit marks the process of storing the image as successful and asks for the image to be persisted.
52+
// WARNING: This does not have any transactional semantics:
53+
// - Uploaded data MAY be visible to others before Commit() is called
54+
// - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
55+
func (d *archiveImageDestination) Commit() error {
56+
return d.Destination.Commit()
57+
}
10.5 KB
Binary file not shown.

docker/archive/src.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package archive
2+
3+
import (
4+
"github.com/Sirupsen/logrus"
5+
"github.com/containers/image/docker/tarfile"
6+
"github.com/containers/image/types"
7+
)
8+
9+
type archiveImageSource struct {
10+
*tarfile.Source // Implements most of types.ImageSource
11+
ref archiveReference
12+
}
13+
14+
// newImageSource returns a types.ImageSource for the specified image reference.
15+
// The caller must call .Close() on the returned ImageSource.
16+
func newImageSource(ctx *types.SystemContext, ref archiveReference) types.ImageSource {
17+
if ref.destinationRef != nil {
18+
logrus.Warnf("docker-archive: references are not supported for sources (ignoring)")
19+
}
20+
src := tarfile.NewSource(ref.path)
21+
return &archiveImageSource{
22+
Source: src,
23+
ref: ref,
24+
}
25+
}
26+
27+
// Reference returns the reference used to set up this source, _as specified by the user_
28+
// (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image.
29+
func (s *archiveImageSource) Reference() types.ImageReference {
30+
return s.ref
31+
}
32+
33+
// Close removes resources associated with an initialized ImageSource, if any.
34+
func (s *archiveImageSource) Close() error {
35+
return nil
36+
}

docker/archive/transport.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package archive
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/containers/image/docker/reference"
8+
ctrImage "github.com/containers/image/image"
9+
"github.com/containers/image/transports"
10+
"github.com/containers/image/types"
11+
"github.com/pkg/errors"
12+
)
13+
14+
func init() {
15+
transports.Register(Transport)
16+
}
17+
18+
// Transport is an ImageTransport for local Docker archives.
19+
var Transport = archiveTransport{}
20+
21+
type archiveTransport struct{}
22+
23+
func (t archiveTransport) Name() string {
24+
return "docker-archive"
25+
}
26+
27+
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an ImageReference.
28+
func (t archiveTransport) ParseReference(reference string) (types.ImageReference, error) {
29+
return ParseReference(reference)
30+
}
31+
32+
// ValidatePolicyConfigurationScope checks that scope is a valid name for a signature.PolicyTransportScopes keys
33+
// (i.e. a valid PolicyConfigurationIdentity() or PolicyConfigurationNamespaces() return value).
34+
// It is acceptable to allow an invalid value which will never be matched, it can "only" cause user confusion.
35+
// scope passed to this function will not be "", that value is always allowed.
36+
func (t archiveTransport) ValidatePolicyConfigurationScope(scope string) error {
37+
// See the explanation in archiveReference.PolicyConfigurationIdentity.
38+
return errors.New(`docker-archive: does not support any scopes except the default "" one`)
39+
}
40+
41+
// archiveReference is an ImageReference for Docker images.
42+
type archiveReference struct {
43+
destinationRef reference.NamedTagged // only used for destinations
44+
path string
45+
}
46+
47+
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an Docker ImageReference.
48+
func ParseReference(refString string) (types.ImageReference, error) {
49+
if refString == "" {
50+
return nil, errors.Errorf("docker-archive reference %s isn't of the form <path>[:<reference>]", refString)
51+
}
52+
53+
parts := strings.SplitN(refString, ":", 2)
54+
path := parts[0]
55+
var destinationRef reference.NamedTagged
56+
57+
// A :tag was specified, which is only necessary for destinations.
58+
if len(parts) == 2 {
59+
ref, err := reference.ParseNormalizedNamed(parts[1])
60+
if err != nil {
61+
return nil, errors.Wrapf(err, "docker-archive parsing reference")
62+
}
63+
ref = reference.TagNameOnly(ref)
64+
65+
if _, isDigest := ref.(reference.Canonical); isDigest {
66+
return nil, errors.Errorf("docker-archive doesn't support digest references: %s", refString)
67+
}
68+
69+
refTagged, isTagged := ref.(reference.NamedTagged)
70+
if !isTagged {
71+
// Really shouldn't be hit...
72+
return nil, errors.Errorf("internal error: reference is not tagged even after reference.TagNameOnly: %s", refString)
73+
}
74+
destinationRef = refTagged
75+
}
76+
77+
return archiveReference{
78+
destinationRef: destinationRef,
79+
path: path,
80+
}, nil
81+
}
82+
83+
func (ref archiveReference) Transport() types.ImageTransport {
84+
return Transport
85+
}
86+
87+
// StringWithinTransport returns a string representation of the reference, which MUST be such that
88+
// reference.Transport().ParseReference(reference.StringWithinTransport()) returns an equivalent reference.
89+
// NOTE: The returned string is not promised to be equal to the original input to ParseReference;
90+
// e.g. default attribute values omitted by the user may be filled in in the return value, or vice versa.
91+
// WARNING: Do not use the return value in the UI to describe an image, it does not contain the Transport().Name() prefix.
92+
func (ref archiveReference) StringWithinTransport() string {
93+
if ref.destinationRef == nil {
94+
return ref.path
95+
}
96+
return fmt.Sprintf("%s:%s", ref.path, ref.destinationRef.String())
97+
}
98+
99+
// DockerReference returns a Docker reference associated with this reference
100+
// (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent,
101+
// not e.g. after redirect or alias processing), or nil if unknown/not applicable.
102+
func (ref archiveReference) DockerReference() reference.Named {
103+
return ref.destinationRef
104+
}
105+
106+
// PolicyConfigurationIdentity returns a string representation of the reference, suitable for policy lookup.
107+
// This MUST reflect user intent, not e.g. after processing of third-party redirects or aliases;
108+
// The value SHOULD be fully explicit about its semantics, with no hidden defaults, AND canonical
109+
// (i.e. various references with exactly the same semantics should return the same configuration identity)
110+
// It is fine for the return value to be equal to StringWithinTransport(), and it is desirable but
111+
// not required/guaranteed that it will be a valid input to Transport().ParseReference().
112+
// Returns "" if configuration identities for these references are not supported.
113+
func (ref archiveReference) PolicyConfigurationIdentity() string {
114+
// Punt, the justification is similar to dockerReference.PolicyConfigurationIdentity.
115+
return ""
116+
}
117+
118+
// PolicyConfigurationNamespaces returns a list of other policy configuration namespaces to search
119+
// for if explicit configuration for PolicyConfigurationIdentity() is not set. The list will be processed
120+
// in order, terminating on first match, and an implicit "" is always checked at the end.
121+
// It is STRONGLY recommended for the first element, if any, to be a prefix of PolicyConfigurationIdentity(),
122+
// and each following element to be a prefix of the element preceding it.
123+
func (ref archiveReference) PolicyConfigurationNamespaces() []string {
124+
// TODO
125+
return []string{}
126+
}
127+
128+
// NewImage returns a types.Image for this reference, possibly specialized for this ImageTransport.
129+
// The caller must call .Close() on the returned Image.
130+
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
131+
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
132+
func (ref archiveReference) NewImage(ctx *types.SystemContext) (types.Image, error) {
133+
src := newImageSource(ctx, ref)
134+
return ctrImage.FromSource(src)
135+
}
136+
137+
// NewImageSource returns a types.ImageSource for this reference,
138+
// asking the backend to use a manifest from requestedManifestMIMETypes if possible.
139+
// nil requestedManifestMIMETypes means manifest.DefaultRequestedManifestMIMETypes.
140+
// The caller must call .Close() on the returned ImageSource.
141+
func (ref archiveReference) NewImageSource(ctx *types.SystemContext, requestedManifestMIMETypes []string) (types.ImageSource, error) {
142+
return newImageSource(ctx, ref), nil
143+
}
144+
145+
// NewImageDestination returns a types.ImageDestination for this reference.
146+
// The caller must call .Close() on the returned ImageDestination.
147+
func (ref archiveReference) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
148+
return newImageDestination(ctx, ref)
149+
}
150+
151+
// DeleteImage deletes the named image from the registry, if supported.
152+
func (ref archiveReference) DeleteImage(ctx *types.SystemContext) error {
153+
// Not really supported, for safety reasons.
154+
return errors.New("Deleting images not implemented for docker-archive: images")
155+
}

0 commit comments

Comments
 (0)