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

Fix push command skips service #7430

Closed
wants to merge 3 commits into from

Conversation

musikov
Copy link

@musikov musikov commented May 5, 2020

Resolves #7429

The first service is skipped because it has no build instruction - it's ok.
But his image still was added to unique_images set, so the second service (with same image, but with build instruction) was skipped.
I've fixed this by adding check is service is pushable. Only after this i add it to unique_images

…en't pushed

Signed-off-by: Mykyta Usikov <mykyta.usikov@gmail.com>
Signed-off-by: Mykyta Usikov <mykyta.usikov@gmail.com>
compose/project.py Outdated Show resolved Hide resolved
return 'image' in self.options

def can_be_pushed(self):
return 'build' in self.options and 'image' in self.options
Copy link
Member

Choose a reason for hiding this comment

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

Not sure if there's a validation for this already, but image can also have a digest in it (e.g. busybox@sha256:a8cf7ff6367c2afa2a90acd081b484cbded349a7076e7bdf37a05279f276bc12)

While that's valid for pulling an image, it's not valid for building or pushing an image; I think this function should check if parse_repository_tag returns digest_separator (@) as separator, and if so, return false.

Probably something like this.

def can_be_pushed(self):
    if 'build' not in self.options or 'image' not in self.options:
        return False
    _, _, separator = parse_repository_tag(self.options.get('image'))
    return separator != '@'

Can you also add a test for these?

Copy link
Member

Choose a reason for hiding this comment

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

On a side-note: from my limited Python skills it looks like currently, parse_repository_tag does not support a reference to have both a tag and a digest, for example:

busybox:latest@sha256:a8cf7ff6367c2afa2a90acd081b484cbded349a7076e7bdf37a05279f276bc12 

admittedly, that syntax is mostly is to make the reference "nicer" to read, because digest is leading in that case, but it probably should be looked at

compose/compose/service.py

Lines 1443 to 1445 in 440c94e

if digest_separator in repo_path:
repo, tag = repo_path.rsplit(digest_separator, 1)
return repo, tag, digest_separator

/cc @ulyssessouza

Copy link
Author

Choose a reason for hiding this comment

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

Not sure if there's a validation for this already, but image can also have a digest in it (e.g. busybox@sha256:a8cf7ff6367c2afa2a90acd081b484cbded349a7076e7bdf37a05279f276bc12)

While that's valid for pulling an image, it's not valid for building or pushing an image; I think this function should check if parse_repository_tag returns digest_separator (@) as separator, and if so, return false.

Probably something like this.

def can_be_pushed(self):
    if 'build' not in self.options or 'image' not in self.options:
        return False
    _, _, separator = parse_repository_tag(self.options.get('image'))
    return separator != '@'

Can you also add a test for these?

Well, if someone stated, that service has build phase and image with digest, it's a mistake in config. push handler shouldn't be bothered by it, the client will fail on push with explanation.
The better way is to add such checks in config validation IMHO

Copy link
Author

@musikov musikov May 6, 2020

Choose a reason for hiding this comment

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

(update) suggestion to leave can_be_pushed same, but add exception if trying to push image with digest

    def can_be_pushed(self):
        return 'build' in self.options and 'image' in self.options

...

    def push(self, ignore_push_failures=False):
        if not self.can_be_pushed():
            return

        repo, tag, separator = parse_repository_tag(self.options['image'])
        if separator == '@':
            raise ConfigurationError('Can\'t push image with digest')
        tag = tag or 'latest'
        log.info('Pushing %s (%s%s%s)...' % (self.name, repo, separator, tag))
        output = self.client.push(repo, tag=tag, stream=True)
        ...

Copy link
Author

@musikov musikov May 6, 2020

Choose a reason for hiding this comment

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

On a side-note: from my limited Python skills it looks like currently, parse_repository_tag does not support a reference to have both a tag and a digest, for example:

busybox:latest@sha256:a8cf7ff6367c2afa2a90acd081b484cbded349a7076e7bdf37a05279f276bc12 

admittedly, that syntax is mostly is to make the reference "nicer" to read, because digest is leading in that case, but it probably should be looked at

compose/compose/service.py

Lines 1443 to 1445 in 440c94e

if digest_separator in repo_path:
repo, tag = repo_path.rsplit(digest_separator, 1)
return repo, tag, digest_separator

/cc @ulyssessouza

What is expected behaviour? Jsut ignore tag, so

>>> parse_repository_tag('user/repo:v1@sha256:digest')
('user/repo', 'sha256:digest', '@')

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 pulling with just the digest is ok (digest references can only be used for pulling / running, not for pushing/building). Unless we want this function to return all things separate (which may be more "correct", but would require more changes);

This is "ok" image@sha256:deadbeef

But more "correct" would be

repo, tag, digest = parse_repository_tag('user/repo:v1@sha256:digest')

(return the tag (if any) and/or digest (if any)); perhaps it should return some struct having those properties.

But (as mentioned) for now, just using image@sha256:deadbeef should be ok

Copy link
Author

Choose a reason for hiding this comment

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

But (as mentioned) for now, just using image@sha256:deadbeef should be ok

Added #7432 pull request for it, 'cause it's separate functionality

@@ -695,7 +695,9 @@ def push(self, service_names=None, ignore_push_failures=False):
repo, tag, sep = parse_repository_tag(service.image_name)
service_image_name = sep.join((repo, tag)) if tag else sep.join((repo, 'latest'))
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps service.image_name should be modified to do this (note: I'm not a Python developer, so perhaps there's reasons why that wasn't done 😅);

compose/compose/service.py

Lines 386 to 389 in 440c94e

def image_name(self):
return self.options.get('image', '{project}_{s.name}'.format(
s=self, project=self.project.lstrip('_-')
))

Which could look something like (again, I'm not a python developer, so possibly there's a cleaner way):

def image_name(self):
    image = self.options.get('image', '{project}_{s.name}'.format(
        s=self, project=self.project.lstrip('_-')
    ))
    if image == "":
        return
    repo, tag, separator = parse_repository_tag(image)
    tag = tag or 'latest'
    return repo+separator+tag

Copy link
Author

Choose a reason for hiding this comment

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

Why do we need to do so? Just to ensure tag is not empty?

Copy link
Member

Choose a reason for hiding this comment

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

There's some old behavior where docker push image<no :tag here> pushes all tags for image. In this case though, omitting :tag means :latest, so I think it would make sense to return that here (so that this logic doesn't have to be repeated everywhere); open to suggestions though

Copy link
Author

Choose a reason for hiding this comment

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

If we look through whole code, we can see, that latest is added only after parse_repository_tag call. So maybe we should add it there?
Anyways, think we need separate issue/discussion for it (despite it can simplify solving current issue)

compose/project.py Outdated Show resolved Hide resolved
compose/project.py Outdated Show resolved Hide resolved
'busy1', image=BUSYBOX_IMAGE_WITH_TAG,
client=self.mock_client)
svc_with_build = Service(
'busy2', image=BUSYBOX_IMAGE_WITH_TAG,
Copy link
Member

Choose a reason for hiding this comment

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

See my comment about digests; can you add a test-case that has a service with a digest in the image reference?

Copy link
Author

Choose a reason for hiding this comment

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

If we add config validation for "build + digest image", we won't need this check

@@ -1250,7 +1256,7 @@ def pull(self, ignore_pull_failures=False, silent=False, stream=False):
return progress_stream.get_digest_from_pull(event_stream)

def push(self, ignore_push_failures=False):
if 'image' not in self.options or 'build' not in self.options:
if not self.can_be_pushed():
Copy link
Member

Choose a reason for hiding this comment

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

Wondering; should we log something if the service does have an image: specified, but either doesn't have build: or image has a digest set (to give some clue why the image for a service wasn't pushed)?

Copy link
Author

Choose a reason for hiding this comment

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

It would be great to have logs!
For services without build and|or image just info log, that they were skipped.

if service_image_name not in unique_images:
# Add service to unique if it's pushable
if (service_image_name not in unique_images and
service.can_be_pushed()):
service.push(ignore_push_failures)
Copy link
Member

Choose a reason for hiding this comment

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

Unrelated: actually never given much attention to ignore_push_failures, but I think ignore_push_failures is way too broad; it seems to ignore any push failure (for whatever cause push failed). I think it'd make sense to either (by default?) ignore missing images (e.g., if I didn't build a service yet and the image isn't present), or add / rename this option to --ignore-missing.

I don't think other failures should be ignorable (e.g. authentication failed, registry unreachable, or pushing caused any other failure)

@ulyssessouza WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

Opened #7431 to propose adding an --ignore-missing or --skip-missing flag

Signed-off-by: Mykyta Usikov <mykyta.usikov@gmail.com>
@domibarton
Copy link

Is there anybody working at this MR, because we're running into the same issue :)

@musikov
Copy link
Author

musikov commented Dec 3, 2020

Is there anybody working at this MR, because we're running into the same issue :)

I've changed service order in my compose file and forgot about this issue.

I'll soon return to it, review requested changes

@glours
Copy link
Contributor

glours commented Jul 27, 2022

Thanks for taking the time to create this issue/pull request!

Unfortunately, Docker Compose V1 has reached end-of-life and we are not accepting any more changes (except for security issues). Please try and reproduce your issue with Compose V2 or rewrite your pull request to be based on the v2 branch and create a new issue or PR with the relevant Compose V2 information.

@glours glours closed this Jul 27, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Docker-compose push skips second service if first has same image but no build section
6 participants