-
Notifications
You must be signed in to change notification settings - Fork 784
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fea(#1619): skopeo sync support use src or dest use oci format #1620
Conversation
Signed-off-by: root <root@guofutan-NB6.tencent.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
For now, just a quick look:
- You have already identified the downside: OCI can’t represent the original format of all images. Still, that might not be an issue for some.
- I feel rather strongly that the code to parse/list images from an OCI repository should exist as a public API in c/image, not here.
- Is there a specific reason to use this layout (one OCI repo per registry repo)? It seems to unnecessary limit data sharing, which was the motivation for this feature in the first place.
cmd/skopeo/sync.go
Outdated
defer indexJSON.Close() | ||
|
||
index := &imgspecv1.Index{} | ||
if err := json.NewDecoder(indexJSON).Decode(index); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This deep knowledge of image formats really isn’t Skopeo’s business.
Instead, this should look like something very similar to containers/image#1381 ’s oci/archive.Reader
: maybe an oci/layout.Reader
that can just return oci/layout
references (but much simpler than containers/image#1381, because it doesn’t need to maintain state for a decompressed temporary directory), and Skopeo should just consume that API to list the images.
cmd/skopeo/sync.go
Outdated
} | ||
|
||
if opts.preserveDigests && opts.destination == oci.Transport.Name() { | ||
return errors.New("oci DESTINATION transports not support --preserve-digests options") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return errors.New("oci DESTINATION transports not support --preserve-digests options") | |
return errors.New("using the oci transport as a destination does not support --preserve-digests") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was removed entirely?!
cmd/skopeo/sync.go
Outdated
} | ||
if !contains(opts.source, []string{docker.Transport.Name(), directory.Transport.Name(), "yaml"}) { | ||
if !contains(opts.source, []string{docker.Transport.Name(), directory.Transport.Name(), "yaml", oci.Transport.Name()}) { | ||
return errors.Errorf("%q is not a valid source transport", opts.source) | ||
} | ||
|
||
if len(opts.destination) == 0 { | ||
return errors.New("A destination transport must be specified") | ||
} | ||
if !contains(opts.destination, []string{docker.Transport.Name(), directory.Transport.Name()}) { | ||
if !contains(opts.destination, []string{docker.Transport.Name(), directory.Transport.Name(), oci.Transport.Name()}) { | ||
return errors.Errorf("%q is not a valid destination transport", opts.destination) | ||
} | ||
|
||
if opts.source == opts.destination && opts.source == directory.Transport.Name() { | ||
return errors.New("sync from 'dir' to 'dir' not implemented, consider using rsync instead") | ||
if opts.source == opts.destination && contains(opts.source, []string{directory.Transport.Name(), oci.Transport.Name()}) { | ||
return errors.Errorf("sync from '%s' to '%s' not implemented, consider using rsync instead", opts.source, opts.destination) | ||
} | ||
|
||
if opts.preserveDigests && opts.destination == oci.Transport.Name() { | ||
return errors.New("oci DESTINATION transports not support --preserve-digests options") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note to self: All of this looks increasingly clumsy; take a deeper look.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(To clarify, I meant the existing code structure — it feels like a collection of special cases disconnected from the actual implementation, and can get out of sync.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, let’s split this up, to remove the transport list duplication, and so that imagesToCopy
and destinationReference
don’t grow very large.
Turn imagesToCopy
into a
var sourceImageListers map[string]func(…)… = {
docker.Transport.Name(): listDockerSourceImages,
…
}
(imagesToCopy
has almost no shared code, so that can be just carved up)
and similarly destinationReference
should turn into
var destinationReferenceConstructors map[string]func(…)… = {
docker.Transport.Name(): createDockerDestinationReference,
…
}
Then these contains
checks turn into
sourceImageLister, ok := sourceImageListers[opts.source]
if !ok { fail }
That feels like a necessary but arguably unrelated cleanup — I can work on that in a separate PR, if you’d like.
I’m not sure what conflict you mean. Note that the OCI image reference annotations are not limited to Docker-like tags;
Docker schema1/schema2 images (i. e. any images not already in the OCI format — most images in the wild, AFAIK) will be converted to OCI. That can preserve all features fine (except for health checks, I think) — but it inevitably changes the binary representation, and the image’s manifest digest. So a Docker schema2 image |
…ll the images within one directory Signed-off-by: root <root@guofutan-NB6.tencent.com>
Signed-off-by: root <root@guofutan-NB6.tencent.com>
@@ -100,6 +102,62 @@ type dockerImageOptions struct { | |||
noCreds bool // Access the registry anonymously | |||
} | |||
|
|||
// ociCryptOptions collects CLI flags specific to the encryptLayer and keys of encryption or decryption, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe
// ociCryptOptions collects CLI flags specific to the encryptLayer and keys of encryption or decryption, | |
// ociCryptOptions collects CLI flags related to OCI image encryption/decryption |
encryptLayer
is defined by this struct, so ociCryptOptions
… related to encryptLayer
is somewhat circular.
// encryption-key and decryption-key cannot be specified together, | ||
// if set encryptLayer then encryptionKeys must be setted. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Absolutely non-blocking: These might be better as comments on the individual fields. It would also be nice to be consistent about usage of --encryption-key
(option name) vs encryptionKeys
(struct field name) references.)
decryptionKeys []string // Keys needed to decrypt the image | ||
} | ||
|
||
func cryptFlags() (pflag.FlagSet, *ociCryptOptions) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
func cryptFlags() (pflag.FlagSet, *ociCryptOptions) { | |
func ociCryptFlags() (pflag.FlagSet, *ociCryptOptions) { |
would be a tiny bit more consistent with the other CLI helpers.
} | ||
// the directory holding the image must be created here | ||
if err = os.MkdirAll(destination, 0755); err != nil { | ||
return errors.Wrapf(err, "Error creating directory for image %s", destination) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given the “Refusing to overwrite” behavior and this text saying “for image %s”, this seems unlikely to be reused for anything else — it is rather specific to the dir:
transport; so I’m not sure that splitting this out is worthwhile.
I guess it depends on if we can find a good clear name for this behavior. checkExistAndMkdir
is not that — that feels like two completely separate operations collected together, and checkExist
doesn’t indicate anything about the consequences. So, for now it seems to me that leaving this in-line is much easier than finding a good name for a separate function, but there might well be a great name / way to express this that I’m missing.
@@ -19,14 +19,15 @@ One of the problems of prefixing a destination with its transport is that, the r | |||
Available _source_ transports: | |||
- _docker_ (i.e. `--src docker`): _source_ is a repository hosted on a container registry (e.g.: `registry.example.com/busybox`). | |||
If no image tag is specified, skopeo sync copies all the tags found in that repository. | |||
- _dir_ (i.e. `--src dir`): _source_ is a local directory path (e.g.: `/media/usb/`). Refer to skopeo(1) **dir:**_path_ for the local image format. | |||
- _dir_ (i.e. `--src dir`): _source_ is a local c path (e.g.: `/media/usb/`). Refer to skopeo(1) **dir:**_path_ for the local image format. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change seems to be a typo.
// docker -> dir,docker,oci, trim domain | ||
dockerRef := ref.DockerReference() | ||
tagref, _ := reference.TagNameOnly(dockerRef).(reference.Tagged) | ||
destSuffix = fmt.Sprintf("%s:%s", reference.Path(dockerRef), tagref.Tag()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is any of this changing at all? We can’t break existing scripts for docker -> dir and docker-> docker copies. Even excepting that, I don’t see why OCI would want to trim the registry domain: skopeo sync --src docker --dest oci quay.io/foo/bar:tag ./oci-archive
can, AFAICS, use a quay.io/foo/bar:tag
name as the annotation name.
(Also, the source could possibly be a digested reference, where the reference.Tagged
cast would crash.)
There are some RFEs about more control of the source/destination name mapping, but that feels quite orthogonal to this PR about OCI support.
@@ -166,36 +176,32 @@ func parseRepositoryReference(input string) (reference.Named, error) { | |||
// destinationReference creates an image reference using the provided transport. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like
// destinationReference creates an image reference using the provided transport. | |
// destinationReference creates an image reference, combining the provided transport, the user-specified destination, and destSuffix containing a part of the source image’s name that should be used in the destination name. |
otherwise the purpose of destSuffix
is not obvious.
Possibly the order of arguments should then be changed to transport, destination, destSuffix
so that it matches the usual order of the output.
if err != nil { | ||
return nil, errors.Wrapf(err, "Cannot obtain a valid image reference for transport %q and reference %q", imageTransport.Name(), destination) | ||
if destRefErr != nil { | ||
return nil, errors.Wrapf(destRefErr, "Cannot obtain a valid image reference for transport %q and reference %q", transport, destString) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
destString
is not actually used on the OCI path … I guess this should list all of transport, destination, destSuffix
individually..
} | ||
|
||
logrus.Debugf("Destination for transport %q: %s", transport, destRef.StringWithinTransport()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can now be just "Destination: %s", transports.ImageName(destRef)
… or maybe this log line can be omitted entirely, because that value is already listed in fromToFields
.
Originally, the log line was showing the input to ParseReference
. The new equivalent would be showing (transport
, destination
, destSuffix
), before the switch
starts to interpret them.
cmd/skopeo/sync.go
Outdated
} | ||
|
||
if opts.preserveDigests && opts.destination == oci.Transport.Name() { | ||
return errors.New("oci DESTINATION transports not support --preserve-digests options") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was removed entirely?!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A full review now; #1620 (comment) is still the major structural blocker.
(Also note that tests are failing; see the logs, something like gofmt -s -w cmd/skopeo/copy.go
might help.)
A friendly reminder that this PR had no activity for 30 days. |
A friendly reminder that this PR had no activity for 30 days. |
No description provided.