group_manifests: create minimal plugin for group manifests#756
group_manifests: create minimal plugin for group manifests#756twaugh merged 1 commit intocontainerbuildsystem:masterfrom
Conversation
| annotations = BUILD_ANNOTATIONS | ||
| for worker in worker_annotations: | ||
| annotations['worker-builds'][worker] = worker_annotations[worker] | ||
| else: |
| annotations['worker-builds'][worker] = worker_annotations[worker] | ||
| else: | ||
| annotations['worker-builds']['x86_64'] = X86_ANNOTATIONS | ||
|
|
There was a problem hiding this comment.
W293 blank line contains whitespace
| }, | ||
| }] | ||
| tasker, workflow = mock_environment(tmpdir, docker_registry=DOCKER0_REGISTRY, | ||
| primary_images=test_images, |
ebe521b to
5d1fe98
Compare
|
This code is most likely wrong, but it's close enough for review. I suspect that this code: should use the image name in place of the digest, but I'm not 100% sure. I can't get this code to fail if there's an amd64 platform in the annotations I suspect that is not the desired behavior, but I don't understand what goarch is for. |
lcarva
left a comment
There was a problem hiding this comment.
Seems to be going in the right direction.
| import requests.auth | ||
| import json | ||
|
|
||
| try: |
There was a problem hiding this comment.
Let's use six here: from six.moves.urllib.parse import urlparse
It should work for both python 2 and python 3.
Note: We recently added this dependency to atomic-reactor. There are parts of the code that need to be updated to avoid the ImportError try-except approach.
| grouped_manifests = [] | ||
|
|
||
| build_result = self.workflow.buildstep_result.get(OrchestrateBuildPlugin.key) | ||
| all_annotations = build_result.annotations |
There was a problem hiding this comment.
Also available at: self.workflow.build_result.annotations
| key = 'group_manifests' | ||
| is_allowed_to_fail = False | ||
|
|
||
| def __init__(self, tasker, workflow, registries, group=True, goarch={}): |
There was a problem hiding this comment.
Avoid using mutable objects as default values. Instead, set it to None and create a new instance in method body.
..., goarch=None):
goarch = goarch or {}|
|
||
| url = '{}/v2/{}/manifests/{}'.format(registry, repo, digest) | ||
|
|
||
| response = requests.head(url, verify=not insecure, auth=auth) |
There was a problem hiding this comment.
HEAD should be sufficient for checking that the manifest exists, but it also should have response.raise_for_status() to throw exception on HTTP 404.
Anyway, since get is performed later on, its not needed
| url = '{}/v2/{}/manifests/{}'.format(registry, repo, digest) | ||
|
|
||
| response = requests.head(url, verify=not insecure, auth=auth) | ||
| response = requests.get(url, verify=not insecure, auth=auth) |
There was a problem hiding this comment.
Do we need to do this for each iteration in this for-loop? Parameters don't seem to change.
|
|
||
| :param tasker: DockerTasker instance | ||
| :param workflow: DockerBuildWorkflow instance | ||
| :param timeout: int, maximum number of seconds to wait |
| build_result = self.workflow.buildstep_result.get(OrchestrateBuildPlugin.key) | ||
| all_annotations = build_result.annotations | ||
| for platform in all_annotations['worker-builds']: | ||
| if self.goarch.get(platform, platform) == 'amd64': |
There was a problem hiding this comment.
What's the purpose of goarch? A way to control arch to platform mapping?
There was a problem hiding this comment.
There was a problem hiding this comment.
Better examples of goarch added to the tests.
| if self.goarch.get(platform, platform) == 'amd64': | ||
| grouped_manifests = self.get_worker_manifest(all_annotations['worker-builds'][platform]) # noqa | ||
| break | ||
|
|
There was a problem hiding this comment.
We should probably handle the case where the platform mapping to 'amd64' was not found.
There was a problem hiding this comment.
It currently returns an empty list in that case. What else should it do?
There was a problem hiding this comment.
If this plugin ran with group=False, and the amd64 goarch was not found, it indicates an error IMO. @twaugh, thoughts?
owtaylor
left a comment
There was a problem hiding this comment.
Just a few comments - I'm paying attention to this since I'll need to modify this to support OCI images and image indexes (equivalent to manifest lists)
| response = requests.get(url, verify=not insecure, auth=auth) | ||
|
|
||
| image_manifest = json.loads(response.text) | ||
| image_manifest['tag'] = image_tag |
There was a problem hiding this comment.
This is only part of the v1 schema, not part of the v2 schema (or manifest list schema), and in any case, the tag that is applied in the registry is the reference from the URL. If all tags can share the exact same manifest content/digest, that seems preferable.
| This software may be modified and distributed under the terms | ||
| of the BSD license. See the LICENSE file for details. | ||
|
|
||
| create a group of the image manifests from the worker builds |
There was a problem hiding this comment.
This isn't very clear to me at all. "a group of the image manifest" or "group manifest" aren't - as far as I know - standard things. Is the final intent, something like:
Creates a manifest list that includes the manifests from the worker builds and uploads that to the configured registries
? (I think the group_manifests name for the plugin is also perhaps not the best one!) If the final intent is that if there is only one architecture, it creates a single manifest (essentially retagging the worker build), that should also be indicated here.
There was a problem hiding this comment.
The idea behind naming it "group_manifests" is that in the future it may need to group them together in an OCI Image Index, as well as (or instead of) a Docker Manifest List.
It's unfortunate that "group" is a noun as well as a verb. It's intended as a verb in this plugin name: bring together the image manifests in some way.
There was a problem hiding this comment.
If I think of it as a verb, the plugin name seems fine. But this comment needs to be improved to be in line with that and to reference back to concepts (a manifest list) that people reading the code can be expected to know or able to look up.
There was a problem hiding this comment.
fixed. possibly.
Why? I think the assumption can be made that the worker pushed the same image for all the tags, and the digest that the worker returned is the reliable way to get exactly that image for that architecture. As @lcarva said, I think the head can be removed and this can be pulled out of the loop. |
|
What |
c7c3cdd to
1322245
Compare
| if valid: | ||
| return grouped_manifests | ||
| else: | ||
| raise PluginFailedException('failed to find an x86_64 platform') |
There was a problem hiding this comment.
The exception raised here will be wrapped in a PluginFailedException. Let's use something else. Seems like a good case for RuntimeError or ValueError.
| for platform in all_annotations['worker-builds']: | ||
| if self.goarch.get(platform, platform) == 'amd64': | ||
| valid = True | ||
| grouped_manifests = self.get_worker_manifest(all_annotations['worker-builds'][platform]) # noqa |
There was a problem hiding this comment.
line is too long and there's no good place to split it.
There was a problem hiding this comment.
worker_data = all_annotations['worker-builds'][platform]
grouped_manifests = self.get_worker_manifest(worker_data)
1322245 to
2c63309
Compare
| registry = 'https://' + registry | ||
|
|
||
| registry_noschema = urlparse(registry).netloc | ||
| for push_conf_registry in self.workflow.push_conf.docker_registries: |
There was a problem hiding this comment.
self.workflow.push_conf would always return an empty list on orchestrator, as it is being filled by tag_and_push plugin.
Probably workers should also store the registry they've pushed the image to during the sync
There was a problem hiding this comment.
I know delete_from_registry runs on the orchestrator and worker in arrangement 3 and later, and I took the registry handling code from delete_from_registry. But I don't know that the push_conf is the same between worker and orchestrator. I'm going to leave this alone for now.
There was a problem hiding this comment.
But no -- group_manifests will create an object in the registry before pulp_sync is called. We need to clean that up too.
| msg = 'invalid schema from {}'.format(url) | ||
| raise PluginFailedException(msg) | ||
|
|
||
| for image in self.workflow.tag_conf.images: |
There was a problem hiding this comment.
Same here - this property is being set by tag_and_push. I think the plugin should iterate over worker_digest instead
There was a problem hiding this comment.
I think it's right as it is -- tag_from_config will set these tags.
There was a problem hiding this comment.
Right, I've assumed this function will be called on orchestrator - it probably won't, as for manifest list we need just a digest and goarch
There was a problem hiding this comment.
tag_from_config only runs on worker builds, so will the workflow.tag_conf on the orchestrator build inherit the tags?
There was a problem hiding this comment.
In the next arrangement version we'll be running tag_from_config in both the worker build (for the platform-specific unique tag) and the orchestrator build (for all the other tags).
| url = '{}/v2/{}/manifests/{}'.format(registry, repo, digest) | ||
| response = requests.get(url, verify=not insecure, auth=auth) | ||
|
|
||
| image_manifest = json.loads(response.text) |
There was a problem hiding this comment.
Use response.json() here. Note that json.loads(response.text) is actually wrong for subtle reasons relating to encoding (the text property will assume the wrong encoding because an RFC tells it to).
2c63309 to
8d90f62
Compare
ae3123c to
159059d
Compare
|
|
||
| from six.moves.urllib.parse import urlparse | ||
|
|
||
| from atomic_reactor.plugin import PostBuildPlugin, PluginFailedException |
There was a problem hiding this comment.
F401 'atomic_reactor.plugin.PluginFailedException' imported but unused
| from six.moves.urllib.parse import urlparse | ||
|
|
||
| from atomic_reactor.plugin import PostBuildPlugin, PluginFailedException | ||
| from atomic_reactor.util import Dockercfg, get_manifest_digests, get_config_from_registry |
There was a problem hiding this comment.
F401 'atomic_reactor.util.get_config_from_registry' imported but unused
| self.log.debug("getting manifests from %s", registry_noschema) | ||
| digest = worker_digests[0]['digest'] | ||
| repo = worker_digests[0]['repository'] | ||
|
|
There was a problem hiding this comment.
W293 blank line contains whitespace
| manifest_digests = get_manifest_digests(registry_image, registry, | ||
| insecure, secret_path) | ||
| push_conf_registry.digests[image_tag] = manifest_digests | ||
|
|
There was a problem hiding this comment.
W293 blank line contains whitespace
4fe93bc to
78a09ca
Compare
|
|
||
| if registry_image: | ||
| push_conf_reg.config = get_config_from_registry( | ||
| registry_image, registry_noschema, manifest_digests, insecure, secret_path, 'v2') |
There was a problem hiding this comment.
E501 line too long (105 > 100 characters)
57b2e21 to
9aad1f9
Compare
| image_tag = image.to_str(registry=False) | ||
| url = '{0}/v2/{1}/manifests/{2}'.format(registry, repo, image_tag) | ||
| self.log.debug("for image_tag %s, putting at %s", image_tag, url) | ||
| response = requests.put(url, image_manifest, **kwargs) |
There was a problem hiding this comment.
We should call response.raise_for_status() here I think.
|
|
||
| for image in self.workflow.tag_conf.images: | ||
| image_tag = image.to_str(registry=False) | ||
| url = '{0}/v2/{1}/manifests/{2}'.format(registry, repo, image_tag) |
There was a problem hiding this comment.
{2} must be only the suffix part, so image.tag I think.
0e00cde to
2ae1894
Compare
|
tested with brew-pulp-docker01.web.prod.ext.phx2.redhat.com:8888/mlangsdo/buildroot:eng-rhel-7-docker-candidate-94693-20170816174925, which is a build of pkgs.devel.redhat.com/rpms/osbs-buildroot-docker@private-mlangsdorf-incremental that uses: build logs are here: please merge thanks. |
twaugh
left a comment
There was a problem hiding this comment.
Looks great. Added some comments about potential improvements.
|
|
||
| from six.moves.urllib.parse import urlparse | ||
|
|
||
| from atomic_reactor.plugin import PostBuildPlugin, PluginFailedException |
| Params: | ||
| * "secret" optional string - path to the secret, which stores | ||
| login and password for remote registry | ||
| :param group: bool, if true, return the grouped image manifests from the workers |
There was a problem hiding this comment.
Nit: better description might be "if true, create a manifest list; otherwise only add tags to amd64 image manifest"
| super(GroupManifestsPlugin, self).__init__(tasker, workflow) | ||
| self.group = group | ||
| self.goarch = goarch or {} | ||
| self.registries = deepcopy(registries) |
There was a problem hiding this comment.
Not sure what the copy is for. We don't modify it.
|
|
||
| def run(self): | ||
| if self.group: | ||
| raise NotImplementedError('group=True is not supported in group_manifest') |
There was a problem hiding this comment.
Typo: should be "group_manifests"
| if registry in self.worker_registries: | ||
| self.worker_registries[registry].append(registry) | ||
| else: | ||
| self.worker_registries[registry] = [registry] |
There was a problem hiding this comment.
Unnecessary if.
registry = digest['registry']
self.worker_registries.setdefault(registry, [])
self.worker_registries[registry].append(registry)
| if not response.ok: | ||
| msg = "PUT failed: {0},\n manifest was: {1}".format(response.json(), | ||
| image_manifest) | ||
| self.log.error(msg) |
There was a problem hiding this comment.
These lines don't get covered in unit testing.
2ae1894 to
d021c3a
Compare
| of the BSD license. See the LICENSE file for details. | ||
|
|
||
| get the image manifest lists from the worker builders. If possible, group them together | ||
| and return them. if not, return the x86_64/amd64 one after tagging it. |
There was a problem hiding this comment.
Could use clarification to avoid putting 'manifest' and 'lists' together when not talking about 'manifest list' objects.
Get the image manifest digests from the worker builds and group them together into a manifest list.
When told not to group them (group=False), just tag the amd64 image manifest instead.
There was a problem hiding this comment.
Fixed. Also slightly clarified the commit message.
There was a problem hiding this comment.
It still says "image manifest lists" which could be ambiguous.
9ca020a to
3f9a8eb
Compare
|
@mlangsdorf, is this OK to merge? I think it is, just checking. |
|
IIUC #778 supersedes this PR |
|
I'd like to be able to perform testing for arrangement version 4 which requires group_manifests, but does not require group=True to work. |
Add a group_manifests plugin. For now, it fails if asked to create a group of manifests. If asked to return a single manifest, it pulls the x86_64 image manifest from a repository passed as an argument. It then re-uploads the image manifest to a new location for each tag in the tag_conf. It returns the image manifest. Signed-off-by: Mark Langsdorf <mlangsdo@redhat.com>
3f9a8eb to
de25688
Compare
|
Draft release notes updated. |
group_manifests: create plugin for group manifests
Add a group_manifests plugin. For now, it fails if asked to create
a group of manifests.
If asked to create a single manifest, it pulls the x86_64 image manifest
from a repository passed as an argument. It then re-uploads the image
manifest to a new location for each image in the tag_conf. It returns the
image manifest.
Signed-off-by: Mark Langsdorf mlangsdo@redhat.com