Skip to content

Conversation

@smarterclayton
Copy link
Contributor

Creates a command that can be used to bulk promote images from one registry to another, and accepts injected pull secrets.

@smarterclayton smarterclayton force-pushed the pusher branch 2 times, most recently from 0bd3ca6 to 48a1d5b Compare June 5, 2017 04:09
// Conditions is an array of conditions that apply to the tag event list.
Conditions []TagEventCondition
// PendingCopy is set if there is a copy waiting for a layer upload on this image stream tag.
PendingCopy *ImageStreamTagCopy
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't be this condition?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For two step copy (definition then binary layer upload) we need a place to store the pending image metadata. Since the API is tag focused, it's ok to not allow lots of pending images. I don't want to put too much metadata in condition

// Standard object's metadata.
metav1.ObjectMeta

// from is an optional reference to an existing image stream tag or image to copy.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

godoc: uppercase (also the other)

From *kapi.ObjectReference
// image is metadata that will replace the existing metadata of from, or if from
// is empty, will be used to create a new scratch image.
Image *ImageCopyMetadata
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't To better here? (or at least I would expect from->to ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure - this is more declarative than destination. so if you specify it it's "this is what I want".

InfraTemplateServiceBrokerServiceAccountName = "template-service-broker"
TemplateServiceBrokerControllerRoleName = "system:openshift:template-service-broker"

InfraRegistryManagementControllerServiceAccountName = "registry-management-controller"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will make my life in #14317 harder :-) is it possible to base this on top of the refactoring?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't for 3.6, just putting it up while i iterate.


// lazyServiceAccountSecretBasicAuthStore reads the service account token from a service account
// and uses it as basic auth credentials.
type lazyServiceAccountSecretBasicAuthStore struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this master.go good place for this to live?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's the only place it's used right now, I don't think of this as final because it's a really inelegant way for the master to prove auth to the registry. Unfortunately, we have no other way to auth to the registry.

imageStreamRegistry: imageStreamRegistry,
sarRegistry: sarRegistry,
defaultRegistry: defaultRegistry,
gr: schema.GroupResource{Resource: "imagestreamtags", Group: "image.openshift.io"},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imageapi.Resource("imagestreamtags") ?

event := imageapi.LatestTaggedImage(imageStream, tag)
if event == nil || len(event.Image) == 0 {
if !allowEmptyEvent {
glog.V(4).Infof("did not find tag %s in image stream status tags: %#v", tag, imageStream.Status.Tags)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wish we have some pretty-printer for tags (we print the tags in several places and the Status.Tags is pretty big arr)

- name: openshift
options:
acceptschema2: false
acceptschema2: true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

already done in #13428

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i needed it to test because this only supports pushing as v2. That's temporary.


var (
longDesc = templates.LongDesc(`
Push an image to a new location
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you provide some examples?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still draft, but the use case is going to be a controller that runs a pod in your namespace that syncs your images to other repositories. So this is just the first part, still need to add some other bits:

  1. make it easy to inject pull secrets that can be used
  2. the controller itself
  3. figure out how to batch this - I kind of think we want to batch all of the "syncs" in a namespace all at once into one job.

t = r.context.InsecureTransport
}
src := *registry
if len(src.Scheme) == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@miminar check

@mfojtik
Copy link
Contributor

mfojtik commented Jun 5, 2017

@smarterclayton would be nice to provide some examples of this and extended test.

@miminar @dmage for the review.

// try removing the canonical ports
if strings.HasSuffix(target.Host, ":443") || strings.HasSuffix(target.Host, ":80") {
host := strings.SplitN(target.Host, ":", 2)[0]
glog.V(5).Infof("Being asked for %s, trying %s without port", target, host)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should :80 port be stripped for https?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A secret that is only served over http doesn't make much sense, but in this case it's checking whether you defined a more generic secret (i.e. you have "foo.com" but not "foo.com:443" - we should try "foo.com").

@smarterclayton
Copy link
Contributor Author

smarterclayton commented Jun 5, 2017 via email

@openshift-bot
Copy link
Contributor

Origin Action Required: Pull request cannot be automatically merged, please rebase your branch from latest HEAD and push again

@openshift-bot openshift-bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jun 18, 2017
@openshift-merge-robot openshift-merge-robot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label Jul 24, 2017
@openshift-merge-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: smarterclayton

No associated issue. Update pull-request body to add a reference to an issue, or get approval with /approve no-issue

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these OWNERS Files:

You can indicate your approval by writing /approve in a comment
You can cancel your approval by writing /approve cancel in a comment

@openshift-merge-robot openshift-merge-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jul 28, 2017
@openshift-merge-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: smarterclayton

No associated issue. Update pull-request body to add a reference to an issue, or get approval with /approve no-issue

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these OWNERS Files:

You can indicate your approval by writing /approve in a comment
You can cancel your approval by writing /approve cancel in a comment

@openshift-merge-robot openshift-merge-robot removed the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jul 28, 2017
@openshift-merge-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: smarterclayton

No associated issue. Update pull-request body to add a reference to an issue, or get approval with /approve no-issue

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these OWNERS Files:

You can indicate your approval by writing /approve in a comment
You can cancel your approval by writing /approve cancel in a comment

@openshift-merge-robot openshift-merge-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jul 30, 2017
}
var addedScopes bool
for _, scope := range additionalScopes {
if hasScope(scopes, scope) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why you not upstreaming this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's now upstream and merged.

@openshift-merge-robot openshift-merge-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Aug 23, 2017
// load the manifest
srcDigest := digest.Digest(srcDigestString)
// var contentDigest digest.Digest / client.ReturnContentDigest(&contentDigest),
srcManifest, err := manifests.Get(ctx, digest.Digest(srcDigest), schema2ManifestOnly)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Schema1 is served regardless of accept header or WithManifestMediaTypes option if the desired tag points to it.

glog.V(4).Infof("Manifest exists in %s, no need to copy layers without --force", dst.ref)
}
}
if mustCopyLayers {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this go to a function/method?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also a good candidate for parallelization.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've actually prefer the reusing of moby's blob upload code.
But that doesn't handle s3.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely don't want to take any more references to moby code. If the distribution client is insufficient containers/image should take it.

for _, blob := range srcManifest.References() {
// tagging within the same registry is a no-op
if src.ref.Registry == dst.ref.Registry && canonicalFrom.String() == canonicalTo.String() {
continue
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be moved before the blobs loop?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, will change and make clearer

}

// if the destination tag already has this manifest, do nothing
var mustCopyLayers bool
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be set to true if source and destination repository are the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, mustCopyLayers should only be true when the user requests it.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry, I actually meant the opposite. If the source and destination repository are the same, no copy is necessary.

// upload the each manifest
for _, srcManifest := range srcManifests {
switch srcManifest.(type) {
case *schema2.DeserializedManifest:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really see a difference between handling schema2 and schema1.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forcing function to stop propagation of schema1. It's easy enough to add later.


var options []distribution.BlobCreateOption
if !o.SkipMount {
options = append(options, client.WithMountFrom(blobSource), WithDescriptor(blob))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This MountFrom makes sense only when src and dst registries are the same.

As an optimization, pushed/skipped blobs to the same registry could be remembered and then used for MountFrom.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registries must ignore this field when it's not valid, so it doesn't cost us anything to always send it.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please put there at least a comment that this isn't supposed to work in most cases.

// mount successful
if ebm, ok := err.(distribution.ErrBlobMounted); ok {
glog.V(5).Infof("Blob mounted %#v", blob)
if ebm.From.Digest() != blob.Digest {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it's worth it. Docker daemon doesn't check this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are checking it because in a mirroring case we may have incorrect data, thus an admin may need to run --force.

}
return srcManifests, srcDigest.Algorithm().FromBytes(body), nil
default:
return append(srcManifests, srcManifest), srcDigest, nil
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The manifest itself needs to be modified to omit all the references filtered out. Otherwise the manifest Put will fail due to not existing references in the destination repository.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The manifest is modified.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The manifest list is modified.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I was confused by the name srcManifest which is the input parameter. Having different name for the processed manifest list wouldn't hurt.


// upload and tag the manifest
for _, tag := range dst.tags {
toDigest, err := toManifests.Put(ctx, srcManifest, distribution.WithTag(tag))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That won't help. Manifest must not include references that don't exist in the destination repository. Otherwise, verification will fail.

@smarterclayton
Copy link
Contributor Author

Schema1 is served regardless of accept header or WithManifestMediaTypes option if the desired tag points to it.

Not on a registry that does dynamic transformation.

@smarterclayton
Copy link
Contributor Author

That won't help. Manifest must not include references that don't exist in the destination repository. Otherwise, verification will fail.

Not sure what you are referring to?

@smarterclayton
Copy link
Contributor Author

Schema1 is served regardless of accept header or WithManifestMediaTypes option if the desired tag points to it.

Ideally we'd have code for schema1 -> 2 that we would use here, then admins could use this as a migration function. Followup

@smarterclayton
Copy link
Contributor Author

More comments?

%[1]s myregistry.com/myimage:latest s3://s3.amazonaws.com/<region>/<bucket>/image

# Copy image to multiple locations
%[1]s myregistry.com/myimage:latest docker.io/myrepository/myimage:{stable,dev}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👎 for the bash syntax. It's not obvious even to regular bash users.

}

// if the destination tag already has this manifest, do nothing
var mustCopyLayers bool
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry, I actually meant the opposite. If the source and destination repository are the same, no copy is necessary.


var options []distribution.BlobCreateOption
if !o.SkipMount {
options = append(options, client.WithMountFrom(blobSource), WithDescriptor(blob))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please put there at least a comment that this isn't supposed to work in most cases.

}
return srcManifests, srcDigest.Algorithm().FromBytes(body), nil
default:
return append(srcManifests, srcManifest), srcDigest, nil
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I was confused by the name srcManifest which is the input parameter. Having different name for the processed manifest list wouldn't hurt.

func (r *s3Repository) Named() reference.Named {
name, err := reference.ParseNamed(r.repoName)
if err != nil {
panic(err)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can s3Driver.Repository() attempt to parse the repoName and error out to prevent this panic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only valid repo names are allowed. I could pass a named reference I suppose.

@miminar
Copy link

miminar commented Oct 6, 2017

Not on a registry that does dynamic transformation.

Didn't know there was such a registry. Do you have a doc at hand?

Not sure what you are referring to?

Nevermind, you've already answered.

@dmage
Copy link
Contributor

dmage commented Oct 6, 2017

Schema1 is served regardless of accept header or WithManifestMediaTypes option if the desired tag points to it.

Not on a registry that does dynamic transformation.

Schema1 can't be upgraded to schema2. Distribution can dynamically transform schema2 into schema1 for backward compatibility: old clients (which support only schema1 manifests) are able to pull images with schema2 manifests.

@smarterclayton
Copy link
Contributor Author

Schema1 can't be upgraded to schema2. Distribution can dynamically transform schema2 into schema1 for backward compatibility: old clients (which support only schema1 manifests) are able to pull images with schema2 manifests.

What is missing from schema1 that can't be recreated?

@smarterclayton
Copy link
Contributor Author

Updated with latest comments, I also fixed issues with s3 and manifest lists (the wrong file was being pushed).

@smarterclayton
Copy link
Contributor Author

/retest

Copy link

@miminar miminar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nits

pattern = "^" + pattern
}
if !strings.HasSuffix(pattern, "$") {
pattern = pattern + "$"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't find this intuitive. I'd expect --filter-by-os=/amd64/ to match. I can always anchor it if I want to match whole string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Will change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated


// if we aren't forcing upload, skip the blob copy
if !o.Force {
_, err := toBlobs.Stat(ctx, blob.Digest)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To save a few queries, a set of blobs uploaded to particular repo could be created. I'd expect some shared blobs among different manifests of a single manifest list.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I think when we parallelize this we'll want to do it.

Allows tokens that cover the scope to be reused
When Docker-Content-Digest is not specified on the response, fall back
from HEAD to GET so that we can calculate the digest from the manifest
content.
A new command `oc image mirror` uses the Docker registry API to read
from one or more images and copy them to remote registries (without
locally storing those images). The command can also copy to an S3
bucket, creating the necessary structure to form a read-only registry.
@smarterclayton smarterclayton added the lgtm Indicates that a PR is ready to be merged. label Oct 12, 2017
@smarterclayton
Copy link
Contributor Author

Applied labels, will be adding extended tests after I cut a beta release

@openshift-bot
Copy link
Contributor

/retest

Please review the full test history for this PR and help us cut down flakes.

@openshift-merge-robot openshift-merge-robot added the vendor-update Touching vendor dir or related files label Oct 14, 2017
@openshift-bot
Copy link
Contributor

/retest

Please review the full test history for this PR and help us cut down flakes.

@openshift-merge-robot
Copy link
Contributor

Automatic merge from submit-queue.

@openshift-merge-robot openshift-merge-robot merged commit ac23330 into openshift:master Oct 15, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. lgtm Indicates that a PR is ready to be merged. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. vendor-update Touching vendor dir or related files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants