-
Notifications
You must be signed in to change notification settings - Fork 52
ART-23299: update_golang floating-tag support in _get_builder_pullspec #3359
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -43,6 +43,54 @@ | |||||||||||
| ) | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| # Floating-tag format: registry.redhat.io/openshift/golang-builder:golang-builder-v1.22-rhel9 | ||||||||||||
| _FLOATING_TAG_RE = re.compile(r'golang-builder-v(\d+)\.(\d+)-rhel(\d+)$') | ||||||||||||
| # NVR-tag format: registry.redhat.io/openshift/golang-builder:openshift-golang-builder-container-v1.22.12-...el9 | ||||||||||||
| _NVR_TAG_RE = re.compile(r'openshift-golang-builder[^:]*-v(\d+)\.(\d+)\.\d+[^:]*\.el(\d+)') | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| def _parse_pullspec_tuple(pullspec: str) -> tuple[int, int, int]: | ||||||||||||
| """Normalise a golang-builder pullspec (floating or NVR) to (major, minor, rhel_version). | ||||||||||||
|
|
||||||||||||
| Raises ValueError for unrecognised formats. | ||||||||||||
| """ | ||||||||||||
| tag = pullspec.split(':')[-1] | ||||||||||||
| m = _FLOATING_TAG_RE.search(tag) | ||||||||||||
| if m: | ||||||||||||
| return int(m.group(1)), int(m.group(2)), int(m.group(3)) | ||||||||||||
| m = _NVR_TAG_RE.search(tag) | ||||||||||||
| if m: | ||||||||||||
| return int(m.group(1)), int(m.group(2)), int(m.group(3)) | ||||||||||||
| raise ValueError(f"Cannot parse golang-builder pullspec into (major, minor, rhel) tuple: {pullspec!r}") | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| def _branch_uses_floating_tags(streams_content: dict) -> bool: | ||||||||||||
| """Return True when any golang-builder stream entry uses a floating tag format.""" | ||||||||||||
| if not streams_content: | ||||||||||||
| return False | ||||||||||||
| for info in streams_content.values(): | ||||||||||||
| image = info.get('image', '') if isinstance(info, dict) else '' | ||||||||||||
| if _FLOATING_TAG_RE.search(image.split(':')[-1]): | ||||||||||||
| return True | ||||||||||||
| return False | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| def _pullspecs_match(a: str, b: str) -> bool: | ||||||||||||
| """Compare two golang-builder pullspecs via tuple normalisation. | ||||||||||||
|
|
||||||||||||
| When both strings represent recognisable golang-builder pullspecs (floating or NVR), | ||||||||||||
| compares by (major, minor, rhel_version) so a floating tag and an NVR for the same | ||||||||||||
| builder version are treated as equal. | ||||||||||||
|
|
||||||||||||
| Falls back to string equality when either pullspec cannot be parsed (e.g. unrecognised | ||||||||||||
| registry/format in test fixtures or legacy entries), preserving the old behaviour. | ||||||||||||
| """ | ||||||||||||
| try: | ||||||||||||
| return _parse_pullspec_tuple(a) == _parse_pullspec_tuple(b) | ||||||||||||
| except ValueError: | ||||||||||||
| return a == b | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| def is_latest(ocp_version: str, el_v: int, nvr: str, koji_session) -> bool: | ||||||||||||
| build_tag = f'rhaos-{ocp_version}-rhel-{el_v}-build' | ||||||||||||
| parsed_nvr = parse_nvr(nvr) | ||||||||||||
|
|
@@ -806,14 +854,42 @@ async def find_builder(el_v: int) -> KonfluxBuildRecord | None: | |||||||||||
| build_records = await asyncio.gather(*(find_builder(el_v) for el_v in el_nvr_map)) | ||||||||||||
| return {el_v: build_record for el_v, build_record in zip(el_nvr_map, build_records) if build_record is not None} | ||||||||||||
|
|
||||||||||||
| def _get_builder_pullspec(self, builder_nvr: str): | ||||||||||||
| """Generate the published pullspec used in streams.yml for Konflux-built builders.""" | ||||||||||||
| def _get_builder_pullspec(self, builder_nvr: str, streams_content: dict | None = None): | ||||||||||||
| """Generate the published pullspec used in streams.yml for Konflux-built builders. | ||||||||||||
|
|
||||||||||||
| When *streams_content* is provided (or the branch content is already cached on the | ||||||||||||
| instance), and the branch uses floating tags, returns a floating-tag pullspec of the | ||||||||||||
| form ``golang-builder-v{major}.{minor}-rhel{el}``. | ||||||||||||
| Otherwise returns the full NVR pullspec. | ||||||||||||
| """ | ||||||||||||
| parsed_nvr = parse_nvr(builder_nvr) | ||||||||||||
| component_name = parsed_nvr["name"] | ||||||||||||
| if component_name == GOLANG_BUILDER_IMAGE_NAME: | ||||||||||||
| component_name = GOLANG_BUILDER_CVE_COMPONENT | ||||||||||||
| elif component_name != GOLANG_BUILDER_CVE_COMPONENT: | ||||||||||||
| raise ValueError(f"Expected a golang builder image NVR, got: {builder_nvr}") | ||||||||||||
|
|
||||||||||||
| # If not explicitly provided, use the branch content (memoized — no extra fetch). | ||||||||||||
| if streams_content is None: | ||||||||||||
| cached = self._get_branch_content() if self._branch_content is not None else None | ||||||||||||
| if cached is not None: | ||||||||||||
| streams_content = cached.get('streams') | ||||||||||||
|
|
||||||||||||
| if streams_content and _branch_uses_floating_tags(streams_content): | ||||||||||||
| # Extract go major.minor and RHEL version from the NVR, e.g. | ||||||||||||
| # openshift-golang-builder-container-v1.22.12-...el9 → (1, 22, 9) | ||||||||||||
| published_nvr = f'{component_name}-{parsed_nvr["version"]}-{parsed_nvr["release"]}' | ||||||||||||
| full_pullspec = rh_art_images_base_pullspec(published_nvr) | ||||||||||||
| try: | ||||||||||||
| major, minor, el_v = _parse_pullspec_tuple(full_pullspec) | ||||||||||||
| return f"golang-builder-v{major}.{minor}-rhel{el_v}" | ||||||||||||
|
Comment on lines
+884
to
+885
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
# Description: Find how streams.yml `image` values are consumed, to confirm a full pullspec is required.
set -euo pipefail
fd -t f 'streams.py|stream.py' | xargs -r rg -n -C4 "\['image'\]|\.get\('image'|\"image\""
rg -n -C4 --type=py "streams\b.*image|image.*pullspec" doozer/doozerlib | head -80Repository: openshift-eng/art-tools Length of output: 7912 🤖 get_repo_knowledge executed:
Length of output: 6021 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- changed helper and callers ---'
sed -n '35,105p;840,900p' pyartcd/pyartcd/pipelines/update_golang.py
printf '%s\n' '--- update_golang image assignment and nearby flow ---'
rg -n -C6 "update_golang_streams|\\['image'\\]|full_pullspec|_pullspecs_match|_get_builder_pullspec" pyartcd/pyartcd/pipelines/update_golang.py
printf '%s\n' '--- configured stream image examples ---'
rg -n -C2 "golang-builder-v[0-9]+\\.[0-9]+-rhel|image:" pyartcd/tests pyartcd 2>/dev/null | head -160Repository: openshift-eng/art-tools Length of output: 28034 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- stream resolution and builder-image consumer ---'
rg -n -C8 "def resolve_stream|resolve_stream\\(|builder_image_name|builder_image_url|stream.*image" doozer/doozerlib pyartcd/pyartcd | head -220
printf '%s\n' '--- pullspec construction contract ---'
sed -n '45,75p;600,635p' doozer/doozerlib/util.py doozer/doozerlib/image.py
printf '%s\n' '--- update tests and helper contract ---'
sed -n '2615,2685p;2725,2765p;2805,2860p' pyartcd/tests/pipelines/test_update_golang.pyRepository: openshift-eng/art-tools Length of output: 28267 🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '1250,1315p' doozer/doozerlib/runtime.py
rg -n -C8 "resolve_brew_image_url|DELIVERY_IMAGE_REGISTRY|ART_IMAGES_GOLANG_BUILDER_APPLICATION" doozer/doozerlibRepository: openshift-eng/art-tools Length of output: 22711 Return a full pullspec, not a bare tag. The bare tag from line 885 fails the full-pullspec check in 🛠️ Proposed fix try:
major, minor, el_v = _parse_pullspec_tuple(full_pullspec)
- return f"golang-builder-v{major}.{minor}-rhel{el_v}"
+ repository = full_pullspec.rsplit(':', 1)[0]
+ return f"{repository}:golang-builder-v{major}.{minor}-rhel{el_v}"
except ValueError:Update the related assertions and 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||
| except ValueError: | ||||||||||||
| _LOGGER.warning( | ||||||||||||
| "Could not parse golang-builder NVR into floating-tag format: %s — falling back to NVR pullspec", | ||||||||||||
| builder_nvr, | ||||||||||||
| ) | ||||||||||||
| return full_pullspec | ||||||||||||
|
|
||||||||||||
| published_nvr = f'{component_name}-{parsed_nvr["version"]}-{parsed_nvr["release"]}' | ||||||||||||
| return rh_art_images_base_pullspec(published_nvr) | ||||||||||||
|
|
||||||||||||
|
|
@@ -896,7 +972,7 @@ def get_stream(stream_name): | |||||||||||
| latest_go = get_stream(latest_go_stream_name(el_v))['image'] | ||||||||||||
|
|
||||||||||||
| for _, info in streams_content.items(): | ||||||||||||
| if info['image'] == latest_go: | ||||||||||||
| if isinstance(info, dict) and _pullspecs_match(info.get('image', ''), latest_go): | ||||||||||||
| info['image'] = pullspec | ||||||||||||
| update_streams = True | ||||||||||||
| # This is to bump minor golang for GO_PREVIOUS | ||||||||||||
|
|
@@ -906,7 +982,7 @@ def get_stream(stream_name): | |||||||||||
| previous_go = get_stream(previous_go_stream_name(el_v))['image'] | ||||||||||||
|
|
||||||||||||
| for _, info in streams_content.items(): | ||||||||||||
| if info['image'] == previous_go: | ||||||||||||
| if isinstance(info, dict) and _pullspecs_match(info.get('image', ''), previous_go): | ||||||||||||
| info['image'] = pullspec | ||||||||||||
| update_streams = True | ||||||||||||
| # This is to bump minor golang for GO_EXTRA | ||||||||||||
|
|
@@ -926,7 +1002,7 @@ def get_stream(stream_name): | |||||||||||
| extra_go = extra_go_stream['image'] | ||||||||||||
|
|
||||||||||||
| for _, info in streams_content.items(): | ||||||||||||
| if info['image'] == extra_go: | ||||||||||||
| if isinstance(info, dict) and _pullspecs_match(info.get('image', ''), extra_go): | ||||||||||||
| info['image'] = pullspec | ||||||||||||
| update_streams = True | ||||||||||||
| # This is to bump major golang for GO_LATEST and update GO_PREVIOUS to current GO_LATEST | ||||||||||||
|
|
@@ -939,9 +1015,9 @@ def get_stream(stream_name): | |||||||||||
| previous_go = get_stream(previous_go_stream_name(el_v))['image'] if go_previous else None | ||||||||||||
|
|
||||||||||||
| for _, info in streams_content.items(): | ||||||||||||
| if info['image'] == latest_go: | ||||||||||||
| if isinstance(info, dict) and _pullspecs_match(info.get('image', ''), latest_go): | ||||||||||||
| info['image'] = pullspec | ||||||||||||
| if info['image'] == previous_go: | ||||||||||||
| if previous_go and isinstance(info, dict) and _pullspecs_match(info.get('image', ''), previous_go): | ||||||||||||
| info['image'] = latest_go | ||||||||||||
| group_content['vars'][go_latest_var] = build_major_minor | ||||||||||||
| update_streams = update_group = True | ||||||||||||
|
|
||||||||||||
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.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Guard against non-string
imagevalues.info.get('image', '')returns the stored value when the key exists. A streams.yml entry written asimage:with no value loads asNone. Line 73 then calls.splitonNoneand raisesAttributeError, which aborts the pipeline. Coerce the value to a string before splitting.🛠️ Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents