Skip to content
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

c8d: Implement push #44963

Merged
merged 2 commits into from
Mar 30, 2023
Merged

c8d: Implement push #44963

merged 2 commits into from
Mar 30, 2023

Conversation

vvoland
Copy link
Contributor

@vvoland vvoland commented Feb 9, 2023

Implements docker push under containerd image store. When
pushing manifest lists that reference a content which is not present in
the local content store, it will attempt to perform the cross-repo mount
the content if possible.

Considering this scenario:

$ docker pull docker.io/library/busybox

This will download manifest list and only host platform-specific
manifest and blobs.

Note, tagging to a different repository (but still the same registry) and pushing:

$ docker tag docker.io/library/busybox docker.io/private-repo/mybusybox
$ docker push docker.io/private-repo/mybusybox

will result in error, because the neither we nor the target repository
doesn't have the manifests that the busybox manifest list references
(because manifests can't be cross-repo mounted).

If for some reason the manifests and configs for all other platforms
would be present in the content store, but only layer blobs were
missing, then the push would work, because the blobs can be cross-repo
mounted (only if we push to the same registry).

The container.io/distribution.source label is taken only from the target blob we are pushing. It's a change from fork's behavior.
In fork for each missing content we did search the entire content store for a manifest that includes it and took it's distribution source label:

// collectSources walks the content store and looks for content which can
// provide a source registry and repository for the provided descriptors from
// the containerd.io/distribution.source labels
func collectSources(ctx context.Context, toCollect []ocispec.Descriptor, store content.Store, sources map[digest.Digest]distributionSource) error {
// Nothing to do.
if len(toCollect) == 0 {
return nil
}
// Make a copy of the missing descriptors as we will be removing
// the descriptors that we found a source for.
missing := make([]ocispec.Descriptor, len(toCollect))
copy(missing, toCollect)
success := errors.New("success, found the source but can't return earlier without an error")
err := store.Walk(ctx, func(i content.Info) error {
source := extractDistributionSource(i.Labels)
log := logrus.
WithField("digest", i.Digest)
log.Debug("walk")
// Nah, we're looking for a parent of this lazy child.
// This one will not provide us with the source.
if source.value == "" {
return nil
}
desc := ocispec.Descriptor{Digest: i.Digest}
// Do a simple peek of the content to avoid big blobs which definitely aren't json.
notJson, err := peekNotJson(ctx, store, desc)
if err != nil {
return err
}
if notJson {
log.Debug("skipping, definitely not a json")
return nil
}
// Read the manifest
blob, err := content.ReadBlob(ctx, store, desc)
if err != nil {
log.WithError(err).Error("error reading blob")
return err
}
// Manifests and indexes have different children.
// Index stores other manifests and manifests store layers.
// To avoid unmarshaling the blob separately as manifest and index
// this holds fields that contains them both and the media type.
var indexOrManifest struct {
MediaType string `json:"mediaType,omitempty"`
Manifests []ocispec.Descriptor `json:"manifests,omitempty"`
Layers []ocispec.Descriptor `json:"layers,omitempty"`
Config ocispec.Descriptor `json:"config,omitempty"`
}
err = json.Unmarshal(blob, &indexOrManifest)
if err != nil {
log.WithError(err).Debug("unmarshal failed")
return nil
}
mediaType := indexOrManifest.MediaType
// Just in case, check if it really is manifest or index.
if !containerdimages.IsManifestType(mediaType) && !containerdimages.IsIndexType(mediaType) {
log.Debug("not a manifest/index")
return nil
}
children := append(indexOrManifest.Layers, indexOrManifest.Manifests...)
if indexOrManifest.Config.Digest != digest.Digest("") {
children = append(children, indexOrManifest.Config)
}
if len(children) == 0 {
log.Debug("empty a manifest/index")
return nil
}
// Look if this manifest/index specifies any of the missing content
for _, layer := range children {
for idx := 0; idx < len(missing); idx += 1 {
wanted := missing[idx]
if layer.Digest == wanted.Digest {
// Found it!
sources[wanted.Digest] = source
log.WithField("wanted", wanted.Digest.String()).Debug("found")
// Don't look for it anymore
if len(missing) > 1 {
lastIdx := len(missing) - 1
missing[idx] = missing[lastIdx]
missing = missing[:lastIdx]
idx -= 1
} else {
// We found all missing, let's end the walk.
missing = missing[:0]
return success
}
}
}
}
return nil
})
if err == success {
err = nil
}
if len(missing) > 0 {
msg := "missing blobs with no source: "
for idx, c := range missing {
if idx != 0 {
msg += ", "
}
msg += c.Digest.String()
}
err = errdefs.NotFound(errors.New(msg))
}
return err
}

This was mostly a workaround for buildkit image exporter not fetching all platform-specific content (only the host's platform). Eg. doing FROM 'ubuntu' | docker buildx build --platform <host>,<other> - would only fetch ubuntu rootfs for <host> platform, but not for <other>. Because the built image didn't have any distribution.source that would point to the original docker.io/library/ubuntu - it was impossible to know where to obtain the blob from, based on the built image alone. This was only possible by traversing all manifests in content store, checking if they have a distribution.source label and then traversing them to check if they happen to define this blob.
Since this is no longer needed as buildkit already does store=true by default, I decided to change this.

- What I did
Implemented push

- How I did it
Use containerd remote to push the content
Some missing content can be cross-repo mounted. In this case fake their existence by wrapping the content.Store to return fake content.Info that includes the containerd.io/distribution.source label with the source repository.
That makes the push not end with NotFound error and allows it to perform cross-repo mount.

**How to verify it [click to expand]**

TODO: New examples

- A picture of a cute animal (not mandatory but encouraged)
mobbunny

@vvoland vvoland added area/api area/images containerd-integration Issues and PRs related to containerd integration labels Feb 9, 2023
daemon/containerd/image_push.go Outdated Show resolved Hide resolved
daemon/containerd/image_push.go Outdated Show resolved Hide resolved
@vvoland
Copy link
Contributor Author

vvoland commented Feb 9, 2023

Arghh, wanted to make the PushImage accept reference.NamedTagged, but forgot about --all-tags case in which there's no tag at all 🤦🏻

=== Failed
=== FAIL: amd64.integration-cli TestDockerRegistrySuite/TestPushMultipleTags (0.16s)
    docker_cli_push_test.go:79: assertion failed: 
        Command:  /usr/local/cli/docker push 127.0.0.1:5000/dockercli/busybox
        ExitCode: 1
        Error:    exit status 1
        Stdout:   The push refers to repository [127.0.0.1:5000/dockercli/busybox]
        
        Stderr:   tag does not exist: 127.0.0.1:5000/dockercli/busybox:latest
        
        
        Failures:
        ExitCode was 1 expected 0
        Expected no error
    check_test.go:384: [deae1ffcfb672] daemon is not started
    --- FAIL: TestDockerRegistrySuite/TestPushMultipleTags (0.16s)

@vvoland vvoland force-pushed the c8d-push-upstream branch 3 times, most recently from 7d7c0c3 to b39819b Compare February 9, 2023 15:10
@AkihiroSuda
Copy link
Member

AkihiroSuda commented Feb 9, 2023

I'm not sure whether it is safe to push a manifest without its layer blobs.
From my comment in nerdctl, some registry doesn't/didn't accept it? (In 2021? containerd/nerdctl@6760ebf)

https://github.com/containerd/nerdctl/blob/b0701ba875880db5a6e7585df148f0eaea36283a/pkg/cmd/image/push.go#L92-L106

// Push fails with "400 Bad Request" when the manifest is multi-platform but we do not locally have multi-platform blobs.
// So we create a tmp reduced-platform image to avoid the error.

return *s, nil
}

const labelDistributionSource = "containerd.io/distribution.source."
Copy link
Member

Choose a reason for hiding this comment

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

nit: The const should be publicized on containerd side: https://github.com/containerd/containerd/blob/97480afdac09c947d48f5e3a134db86c78f4bfa6/remotes/docker/handler.go#L35

Can be done later though

Copy link
Contributor Author

@vvoland vvoland Mar 7, 2023

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

Maybe we could leave a breadcrumb in a comment? Something like this? (I'm sure it could be better, but just a hint as to how to update this in the future)

Suggested change
const labelDistributionSource = "containerd.io/distribution.source."
const labelDistributionSource = "containerd.io/distribution.source." // TODO https://github.com/containerd/containerd/pull/8224 in containerd 1.7+

Copy link
Member

Choose a reason for hiding this comment

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

Does containerd append these labels on push, too? (or only pull?)

If I build something, push it somewhere, then build something else based on that first thing and push it to the same registry, it can use cross-repo blob mounts too. 😅

(see also the related #44757 😂 🙈)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nope, containerd only appends these on pull.
But I just made this code do it and it seems to work!

quick test:

$ docker run --name test -it alpine
# cat /dev/random >/file  # ctr-c after a while
$ docker commit test 172.172.0.2:5000/big-image
$ docker push 172.172.0.2:5000/big-image
$ docker run --name test2 -it 172.172.0.2:5000/big-image
# apk add fish
...
$ docker commit test2 172.172.0.2:5000/big-image-with-small-change
$ docker push 172.172.0.2:5000/big-image-with-small-change
Using default tag: latest
f8d2deb24c5c: Pushed
c41833b44d91: Pushed
8b626a5feef1: Pushed
140adfd2fa47: Pushed
c76cc252a7ff: Pushed # <- pushes only this changed layer

The status is still Pushed though because the containerd doesn't actually expose that information (I'm working on a containerd PR for that one though).

Copy link
Member

Choose a reason for hiding this comment

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

That's really neat!! Thank you for testing/working on it 🙇

(For interested parties, I think containerd/containerd#8330 is the relevant containerd PR 👀)

@vvoland
Copy link
Contributor Author

vvoland commented Feb 10, 2023

I'm not sure whether it is safe to push a manifest without its layer blobs.

Yes, registry (DockerHub at least) will reject the manifest because it references blobs not present in the repository.
The reason why I do prepareMissing only for manifest lists and not manifests, is that I don't expect that image which targets platform-specific manifest will have any blobs missing.

Removing that check for manifests wouldn't hurt though, but if there's a case in which the one-platform image has layers missing then probably there's something wrong going on.

@thaJeztah thaJeztah added this to the v-next milestone Feb 23, 2023
Comment on lines +159 to +161
// Tag is empty only in case ImagePushOptions.All is true.
if tag != "" {
Copy link
Member

@thaJeztah thaJeztah Mar 7, 2023

Choose a reason for hiding this comment

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

I just recalled we still have an open ticket for this to change this at the API level as well;

Maybe we should start working on making that API change 🤔 (we could mention the linked ticket here as a TODO)

Copy link
Member

@tianon tianon left a comment

Choose a reason for hiding this comment

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

Overall this looks pretty good to me -- in the maintainers call where we discussed this, I was concerned about having docker push end up pulling content locally, but just doing as much cross-blob mounting as we can for objects we don't have locally seems 100% sane and honestly really, really clever 👍

I've got a few of what I think are minor nits and would love to hear more maintainer opinions about exposing platform in the API for this endpoint before we've really ironed out the UX (because it's much harder to walk that external API back later if we decide we want something different than it is to just not introduce it for now), but I don't feel so strongly as to block it if I'm alone in my concerns. 🙇 ❤️

return err
}
if tag != "" {
// Push by digest is not supported, so only tags are supported.
Copy link
Member

Choose a reason for hiding this comment

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

Ooooooh, I hadn't fully realized until just now that containerd integration means we could support push-by-digest now! 👀

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 probably requires an API change too though? I'm guessing we don't want the tag to also be able to specify digests? 👀
Possibly, we could think about deprecating separate repo and tag arguments (but still support it), and have ref instead as a preferred way to reference an image?

Copy link
Member

Choose a reason for hiding this comment

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

Doh, yeah, I forgot the API separated those. 😭 Totally something to punt for now, but yeah, I guess we should probably reconsider that as we consider what we want the "better push" API to look like. 🤔

Comment on lines 39 to 41
if _, ok := targetRef.(reference.Tagged); !ok {
return errdefs.NotImplemented(errors.New("push all tags is not implemented"))
}
Copy link
Member

Choose a reason for hiding this comment

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

Longer term, we should probably implement some other means of telling this level of the code that we want to push "all" tags, right? (so we can support push-by-digest? I guess we could implement that by checking for reference.Digested here too? future concerns IMO, but worth raising)

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'm kinda wondering if push "all" tags is still useful with the multi-platform images. I can imagine it being useful when you had to do separate tags for multi-platform images (eg alpine:amd64, alphine:arm64 etc), but with multi-platform images there might be less use cases for it?
Obviously, I might be missing some more context behind it, so happy to hear what others think 😅

Copy link
Member

Choose a reason for hiding this comment

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

I still use it every now and then, but only for very small scale pushes where I've built a bunch of related things locally and want to batch pushing them all. 😅

I wouldn't be bothered one bit by it going away, but I'm sure there are some users who might have different opinions/use cases. 😂

daemon/containerd/image_push.go Outdated Show resolved Hide resolved
}))
defer finishProgress()

var limiter *semaphore.Weighted = nil // TODO: Respect max concurrent downloads/uploads
Copy link
Member

Choose a reason for hiding this comment

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

👀 this one had some upstream blocker, right? could we maybe link to an upstream discussion somewhere about what's stopping us from implementing 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.

Actually we can support it for push just fine because we use the remotes.PushContent directly. It's the pull that uses the c8d's client.Push method that can't use the same limiter for multiple concurrent operations.
For pull, I expect that at some point we would have the need to do something more than the c8d client.Pull provides and we will have to use the lower level functions anyway, which would allow us to support MaxConcurrentDownloads too.
So perhaps it would be fine (at least for now) to only have "MaxConcurrentUploads" implemented?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, that seems reasonable to me, although IMO doesn't have to be part of this PR unless you think the implementation is really straightforward/quick. 🙇

Given this isn't blocked on upstream (as I'd mistakenly crossed the upload/download signals in my head), probably fine to leave the comment as-is too. 👍

Comment on lines +148 to +125
err = errdefs.NotFound(fmt.Errorf(
"missing content: %w\n"+
"Note: You're trying to push a manifest list/index which "+
"references multiple platform specific manifests, but not all of them are available locally "+
"or available to the remote repository.\n"+
"Make sure you have all the referenced content and try again.",
err))
Copy link
Member

Choose a reason for hiding this comment

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

This is really good, IMO 🙇

return *s, nil
}

const labelDistributionSource = "containerd.io/distribution.source."
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we could leave a breadcrumb in a comment? Something like this? (I'm sure it could be better, but just a hint as to how to update this in the future)

Suggested change
const labelDistributionSource = "containerd.io/distribution.source."
const labelDistributionSource = "containerd.io/distribution.source." // TODO https://github.com/containerd/containerd/pull/8224 in containerd 1.7+

return *s, nil
}

const labelDistributionSource = "containerd.io/distribution.source."
Copy link
Member

Choose a reason for hiding this comment

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

Does containerd append these labels on push, too? (or only pull?)

If I build something, push it somewhere, then build something else based on that first thing and push it to the same registry, it can use cross-repo blob mounts too. 😅

(see also the related #44757 😂 🙈)

Comment on lines 263 to 265
if containerdimages.IsConfigType(mediaType) {
return false
}
Copy link
Member

Choose a reason for hiding this comment

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

This one is a little over-zealous -- manifest objects are special (indexes and image manifests), but config objects are just regular blobs (like layers), so if they already exist, they're 100% fair game for cross-blob mounts 👀

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, indeed. I was sure that configs are uploaded as manifests too, but this was probably some mistake when developing the initial fork version. Thanks!

api/swagger.yaml Outdated
Comment on lines 8493 to 8505
- name: "platform"
in: "query"
description: |
Platform in the format `os[/arch[/variant]]` used to select
a platform-specific manifest if the image is an index/manifest list.

If the option is not set, then no specific manifest is select and the
whole image index is pushed.

When specified, the daemon checks if the image has any manifest that
matches the requested platform and pushes it instead of the whole index.
If there's no matching manifest it returns a `404` status.
type: "string"
Copy link
Member

Choose a reason for hiding this comment

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

As I've noted in my other comment, IMO we should spend a bit more time thinking about the UX we want from docker push WRT platforms before we implement this in the API ("no is temporary, yes is forever"), but I seem to recall being in a minority on that in the maintainers call where we discussed this, so I'm not going personally to block this if other maintainers don't agree. 🙇

@tianon
Copy link
Member

tianon commented Mar 27, 2023

Discussed in the maintainers meeting today, and we'd like to hold off on changes to the push API (specifically the addition of platform) until we have the larger UX discussion about how push should look in a world where we have a more complex underlying image store and how we can better take advantage of that. 🙇

I think concretely that means pulling e39998f out for now (and the related code in the previous commit). 👀

Push the reference parsing from repo and tag names into the api and pass
a reference object to the ImageService.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
@vvoland
Copy link
Contributor Author

vvoland commented Mar 30, 2023

Removed platform parameter and related code. Also added the code to append distribution source layers to the pushed content. Can remove it if it's too much though.

@vvoland vvoland marked this pull request as ready for review March 30, 2023 16:10

// shouldDownload returns if the given descriptor can be cross-repo mounted
// when pushing it to a remote reference ref.
func canBeMounted(desc ocispec.Descriptor, targetRef reference.Named, isInsecureFunc func(string) bool, source distributionSource) bool {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
func canBeMounted(desc ocispec.Descriptor, targetRef reference.Named, isInsecureFunc func(string) bool, source distributionSource) bool {
func canBeMounted(mediaType string, targetRef reference.Named, isInsecureFunc func(string) bool, source distributionSource) bool {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks!

return err
}

// prepareMissing will walk the target descriptor recursively and returns up
Copy link
Member

Choose a reason for hiding this comment

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

Looks like the docs doesn't match the function name

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! Fixed.

return reference.WithDigest(source.registryRef, dgst)
}

// shouldDownload returns if the given descriptor can be cross-repo mounted
Copy link
Member

Choose a reason for hiding this comment

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

Same here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! Fixed.

This implements `docker push` under containerd image store.  When
pushing manifest lists that reference a content which is not present in
the local content store, it will attempt to perform the cross-repo mount
the content if possible.

Considering this scenario:

```bash
$ docker pull docker.io/library/busybox
```
This will download manifest list and only host platform-specific
manifest and blobs.

Note, tagging to a different repository (but still the same registry) and pushing:
```bash
$ docker tag docker.io/library/busybox docker.io/private-repo/mybusybox
$ docker push docker.io/private-repo/mybusybox
```

will result in error, because the neither we nor the target repository
doesn't have the manifests that the busybox manifest list references
(because manifests can't be cross-repo mounted).

If for some reason the manifests and configs for all other platforms
would be present in the content store, but only layer blobs were
missing, then the push would work, because the blobs can be cross-repo
mounted (only if we push to the same registry).

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
Copy link
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

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

"LGTM" from a UX perspective (as discussed) I'm good with bringing this one in, but would appreciate a few eyes on the code


if status.Committed && status.Offset >= status.Total {
if p.isMountable(j.Digest) {
progress.Update(out, id, "Mounted")
Copy link
Member

Choose a reason for hiding this comment

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

I think the current output from docker push on a cross-blob mount specifies where it was mounted from; is it possible to surface that data somehow here as well? (is that already in here and I missed it? 🙈)

Copy link
Member

Choose a reason for hiding this comment

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

Aha, found the ref:

progress.Updatef(progressOutput, pd.ID(), "Mounted from %s", err.From.Name())

Copy link
Member

@tianon tianon left a comment

Choose a reason for hiding this comment

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

One minor comment, but probably fine for a follow-up 👍

Copy link
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

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

Looked a bit at the code; overall looks good, but left some nits, and a question about concurrency

if err != nil {
return err
}
defer release(leasedCtx)
Copy link
Member

Choose a reason for hiding this comment

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

Looks like release() can return an error; is this something we should log if it does?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Technically, if it returned an error then the lease couldn't be deleted, so that might be something worthy of logging. However, looking at how containerd itself uses this, it's "common" (I haven't really seen any code that does it differently) to just call it and ignore the error.
Looking at the code, the only case where this could happen if the lease deletion fails. This can happen if the lease no longer exists (which is already the state we want) or the c8d connection is broken, which will pop out in some other place anyway.

Copy link
Member

Choose a reason for hiding this comment

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

Gotcha; yeah, TBH, containerd ignoring things could sometimes be a guideline, but we should definitely check ourselves what makes sense (mistakes are made by everyone, so it's always possible containerd is on the wrong on these, in which case we could contribute there as well).

  • "not found" errors; yup definitely we should ignore
  • more curious; could "something else using the files / mounts / whatever" cause things to not be cleaned up? (in which case we may want to have a log to backtrace we potentially leaked things somewhere); of course that is if that wouldn't trigger "false positives" (race conditions, things being "eventually" cleaned up etc).

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 only deletes a lease - which is created only in this call-site and deleting it shouldn't fail unless the lease was already deleted, containerd connection is broken or containerd state is corrupted. In which case we will have loud errors in other places. Even if we fail to delete the lease and containerd is fully operational with the lease state still being present, the lease will expire after 24h.
It doesn't harm to log it though.. Except it's just bloating a simple one line defer to a deferred anonymous function call with a conditional log invocation 😅 But maybe it can save us from some trouble if the Lease manager has some major future changes from the current state, so I'm not that much against logging it.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks! Yes I didn't know if actual mounts and/or files were involved here. I'm mostly "fine" either way I guess

store := i.client.ContentStore()

resolver, tracker := i.newResolverFromAuthConfig(authConfig)
pushProgress := pushProgress{Tracker: tracker}
Copy link
Member

Choose a reason for hiding this comment

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

pushProgress is shadowing the pushProgress type here. Perhaps we could name it just progress

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in #45243


resolver, tracker := i.newResolverFromAuthConfig(authConfig)
pushProgress := pushProgress{Tracker: tracker}
jobs := newJobs()
Copy link
Member

Choose a reason for hiding this comment

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

Same for jobs here (not sure what a good alternative name is. We could rename one of them to queue perhaps, but 😅)

Comment on lines +94 to +96
for _, c := range children {
jobs.Add(c)
}
Copy link
Member

Choose a reason for hiding this comment

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

heh, now would've been nice if

func (j *jobs) Add(desc ocispec.Descriptor)

Had a ...ocispec.Descriptor as signature. That way we also wouldn't have to lock/unlock the mutex for each item;

// Add adds a descriptor to be tracked
func (j *jobs) Add(desc ...ocispec.Descriptor) {
	j.mu.Lock()
	defer j.mu.Unlock()

	for _, d := range desc {
		if _, ok := j.descs[d.Digest]; ok {
			continue
		}
		j.descs[d.Digest] = d
	}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in #45243


for _, source := range sources {
if canBeMounted(desc.MediaType, targetRef, i.registryService.IsInsecureRegistry, source) {
mountableBlobs[desc.Digest] = source
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible that this handler runs concurrently? In that case, does mountableBlobs need synchronisation?

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, good catch! This is explicitly called concurrently due to Dispatch instead of Walk so there's a data race here.

Comment on lines +204 to +206
registry := strings.TrimPrefix(k, labelDistributionSource)
if registry != k {
ref, err := reference.ParseNamed(registry + "/" + v)
Copy link
Member

Choose a reason for hiding this comment

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

nit: Looks like registry collides with the "github.com/docker/docker/api/types/registry" import. perhaps reg is an option (perhaps also inline it, as it's only used inside the if);

Suggested change
registry := strings.TrimPrefix(k, labelDistributionSource)
if registry != k {
ref, err := reference.ParseNamed(registry + "/" + v)
if reg := strings.TrimPrefix(k, labelDistributionSource); reg != k {
ref, err := reference.ParseNamed(reg + "/" + v)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in: #45243

return false
}

registry := reference.Domain(targetRef)
Copy link
Member

Choose a reason for hiding this comment

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

same here (registry colliding with import)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in: #45243

@thaJeztah
Copy link
Member

(those should be fine for a follow-up though 😂 because it's already in 😂)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/api area/images containerd-integration Issues and PRs related to containerd integration status/2-code-review
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants