Skip to content

Commit f1735c2

Browse files
committed
Add a containerized version of the ADD API endpoint
Assisted-by: JetBrains AI/Gemini [CLOUDDST-28643]
1 parent eda0745 commit f1735c2

File tree

4 files changed

+879
-0
lines changed

4 files changed

+879
-0
lines changed
Lines changed: 395 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,395 @@
1+
# SPDX-License-Identifier: GPL-3.0-or-later
2+
import logging
3+
import os
4+
import shutil
5+
import stat
6+
import tempfile
7+
from typing import Dict, List, Optional, Set
8+
9+
from iib.common.common_utils import get_binary_versions
10+
from iib.common.tracing import instrument_tracing
11+
from iib.exceptions import IIBError
12+
from iib.workers.api_utils import set_request_state
13+
from iib.workers.config import get_worker_config
14+
from iib.workers.tasks.build import (
15+
inspect_related_images,
16+
_update_index_image_pull_spec,
17+
_update_index_image_build_state,
18+
_get_present_bundles,
19+
_get_missing_bundles,
20+
)
21+
from iib.workers.tasks.celery import app
22+
from iib.workers.tasks.containerized_utils import (
23+
prepare_git_repository_for_build,
24+
fetch_and_verify_index_db_artifact,
25+
write_build_metadata,
26+
git_commit_and_create_mr_or_push,
27+
monitor_pipeline_and_extract_image,
28+
replicate_image_to_tagged_destinations,
29+
push_index_db_artifact,
30+
cleanup_merge_request_if_exists,
31+
cleanup_on_failure,
32+
)
33+
from iib.workers.tasks.fbc_utils import merge_catalogs_dirs
34+
from iib.workers.tasks.iib_static_types import (
35+
BundleImage,
36+
GreenwaveConfig,
37+
)
38+
from iib.workers.tasks.opm_operations import (
39+
opm_migrate,
40+
Opm,
41+
deprecate_bundles_fbc_containerized,
42+
_opm_registry_add,
43+
)
44+
from iib.workers.tasks.utils import (
45+
add_max_ocp_version_property,
46+
chmod_recursively,
47+
get_bundles_from_deprecation_list,
48+
get_resolved_bundles,
49+
request_logger,
50+
reset_docker_config,
51+
set_registry_token,
52+
RequestConfigAddRm,
53+
get_image_label,
54+
verify_labels,
55+
prepare_request_for_build,
56+
)
57+
58+
__all__ = ['handle_containerized_add_request']
59+
60+
log = logging.getLogger(__name__)
61+
worker_config = get_worker_config()
62+
63+
64+
@app.task
65+
@request_logger
66+
@instrument_tracing(span_name="workers.tasks.handle_add_request", attributes=get_binary_versions())
67+
def handle_containerized_add_request(
68+
bundles: List[str],
69+
request_id: int,
70+
binary_image: Optional[str] = None,
71+
from_index: Optional[str] = None,
72+
add_arches: Optional[Set[str]] = None,
73+
cnr_token: Optional[str] = None,
74+
organization: Optional[str] = None,
75+
force_backport: bool = False,
76+
overwrite_from_index: bool = False,
77+
overwrite_from_index_token: Optional[str] = None,
78+
distribution_scope: Optional[str] = None,
79+
greenwave_config: Optional[GreenwaveConfig] = None,
80+
binary_image_config: Optional[Dict[str, Dict[str, str]]] = None,
81+
deprecation_list: Optional[List[str]] = None,
82+
build_tags: Optional[List[str]] = None,
83+
graph_update_mode: Optional[str] = None,
84+
check_related_images: bool = False,
85+
index_to_gitlab_push_map: Optional[Dict[str, str]] = None,
86+
username: Optional[str] = None,
87+
traceparent: Optional[str] = None,
88+
) -> None:
89+
"""
90+
Coordinate the the work needed to build the index image with the input bundles.
91+
92+
:param list bundles: a list of strings representing the pull specifications of the bundles to
93+
add to the index image being built.
94+
:param int request_id: the ID of the IIB build request
95+
:param str binary_image: the pull specification of the container image where the opm binary
96+
gets copied from.
97+
:param str from_index: the pull specification of the container image containing the index that
98+
the index image build will be based from.
99+
:param set add_arches: the set of arches to build in addition to the arches ``from_index`` is
100+
currently built for; if ``from_index`` is ``None``, then this is used as the list of arches
101+
to build the index image for
102+
:param str cnr_token: (deprecated) legacy support was disabled.
103+
the token required to push backported packages to the legacy app registry via OMPS.
104+
:param str organization: (deprecated) legacy support was disabled.
105+
organization name in the legacy app registry to which the backported packages
106+
should be pushed to.
107+
:param bool force_backport: (deprecated) legacy support was disabled.
108+
if True, always export packages to the legacy app registry via OMPS.
109+
:param bool overwrite_from_index: if True, overwrite the input ``from_index`` with the built
110+
index image.
111+
:param str overwrite_from_index_token: the token used for overwriting the input
112+
``from_index`` image. This is required to use ``overwrite_from_index``.
113+
The format of the token must be in the format "user:password".
114+
:param str distribution_scope: the scope for distribution of the index image, defaults to
115+
``None``.
116+
:param dict greenwave_config: the dict of config required to query Greenwave to gate bundles.
117+
:param dict binary_image_config: the dict of config required to identify the appropriate
118+
``binary_image`` to use.
119+
:param list deprecation_list: list of deprecated bundles for the target index image. Defaults
120+
to ``None``.
121+
:param list build_tags: List of tags which will be applied to intermediate index images.
122+
:param str graph_update_mode: Graph update mode that defines how channel graphs are updated
123+
in the index.
124+
:param dict index_to_gitlab_push_map: the dict mapping index images (keys) to GitLab repos
125+
(values) in order to push their catalogs into GitLab.
126+
:param str traceparent: the traceparent header value to be used for tracing the request.
127+
:raises IIBError: if the index image build fails.
128+
"""
129+
reset_docker_config()
130+
# Resolve bundles to their digests
131+
set_request_state(request_id, 'in_progress', 'Resolving the bundles')
132+
133+
with set_registry_token(overwrite_from_index_token, from_index, append=True):
134+
resolved_bundles = get_resolved_bundles(bundles)
135+
verify_labels(resolved_bundles)
136+
if check_related_images:
137+
inspect_related_images(
138+
resolved_bundles,
139+
request_id,
140+
worker_config.iib_related_image_registry_replacement.get(username),
141+
)
142+
143+
prebuild_info = prepare_request_for_build(
144+
request_id,
145+
RequestConfigAddRm(
146+
_binary_image=binary_image,
147+
from_index=from_index,
148+
overwrite_from_index_token=overwrite_from_index_token,
149+
add_arches=add_arches,
150+
bundles=bundles,
151+
distribution_scope=distribution_scope,
152+
binary_image_config=binary_image_config,
153+
),
154+
)
155+
from_index_resolved = prebuild_info['from_index_resolved']
156+
binary_image_resolved = prebuild_info['binary_image_resolved']
157+
arches = prebuild_info['arches']
158+
operators = list(prebuild_info['bundle_mapping'].keys())
159+
160+
index_to_gitlab_push_map = index_to_gitlab_push_map or {}
161+
# Variables mr_details, last_commit_sha and original_index_db_digest
162+
# needs to be assigned; otherwise cleanup_on_failure() fails when an exception is raised.
163+
mr_details: Optional[Dict[str, str]] = None
164+
last_commit_sha: Optional[str] = None
165+
original_index_db_digest: Optional[str] = None
166+
167+
Opm.set_opm_version(from_index_resolved)
168+
169+
# TODO - Are we creating new API (v2.0) or should we keep backward compactibility?
170+
# Asking if I remove this we no longer use cnr_token so it could be removed from API
171+
if (cnr_token and organization) or force_backport:
172+
log.warning(
173+
"Legacy support is deprecated in IIB. "
174+
"cnr_token, organization and force_backport parameters will be ignored."
175+
)
176+
177+
_update_index_image_build_state(request_id, prebuild_info)
178+
present_bundles: List[BundleImage] = []
179+
present_bundles_pull_spec: List[str] = []
180+
with tempfile.TemporaryDirectory(prefix=f'iib-{request_id}-') as temp_dir:
181+
branch = prebuild_info['ocp_version']
182+
183+
# Set up and clone Git repository
184+
(
185+
index_git_repo,
186+
local_git_repo_path,
187+
localized_git_catalog_path,
188+
) = prepare_git_repository_for_build(
189+
request_id=request_id,
190+
from_index=str(from_index),
191+
temp_dir=temp_dir,
192+
branch=branch,
193+
index_to_gitlab_push_map=index_to_gitlab_push_map,
194+
)
195+
196+
# Pull index.db artifact (uses ImageStream cache if configured, otherwise pulls directly)
197+
artifact_index_db_file = fetch_and_verify_index_db_artifact(
198+
from_index=str(from_index),
199+
temp_dir=temp_dir,
200+
)
201+
202+
if from_index:
203+
msg = 'Checking if bundles are already present in index image'
204+
log.info(msg)
205+
set_request_state(request_id, 'in_progress', msg)
206+
207+
with set_registry_token(overwrite_from_index_token, from_index_resolved, append=True):
208+
# TODO - update docstring - accept general input_data
209+
present_bundles, present_bundles_pull_spec = _get_present_bundles(
210+
localized_git_catalog_path, temp_dir
211+
)
212+
213+
filtered_bundles = _get_missing_bundles(present_bundles, resolved_bundles)
214+
excluded_bundles = [
215+
bundle for bundle in resolved_bundles if bundle not in filtered_bundles
216+
]
217+
resolved_bundles = filtered_bundles
218+
219+
if excluded_bundles:
220+
log.info(
221+
'Following bundles are already present in the index image: %s',
222+
' '.join(excluded_bundles),
223+
)
224+
225+
# This is a replacement for opm_registry_add_fbc for a containerized version of IIB.
226+
# Note: only index.db is modified (FBC directory is unchanged)
227+
_opm_registry_add(
228+
base_dir=temp_dir,
229+
index_db=artifact_index_db_file,
230+
bundles=resolved_bundles,
231+
overwrite_csv=(prebuild_info['distribution_scope'] in ['dev', 'stage']),
232+
graph_update_mode=graph_update_mode,
233+
)
234+
235+
# Add the max ocp version property
236+
# We need to ensure that any bundle which has deprecated/removed API(s) in 1.22/ocp 4.9
237+
# will have this property to prevent users from upgrading clusters to 4.9 before upgrading
238+
# the operator installed to a version that is compatible with 4.9
239+
# TODO - I think this is no longer used, right?
240+
if resolved_bundles:
241+
# TODO - modify this code to use correct DB path.
242+
add_max_ocp_version_property(resolved_bundles, temp_dir)
243+
244+
deprecation_bundles = get_bundles_from_deprecation_list(
245+
present_bundles_pull_spec + resolved_bundles, deprecation_list or []
246+
)
247+
248+
arches = prebuild_info['arches']
249+
if deprecation_bundles:
250+
deprecate_bundles_fbc_containerized(
251+
bundles=deprecation_bundles,
252+
base_dir=temp_dir,
253+
binary_image=prebuild_info['binary_image'],
254+
index_db_file=artifact_index_db_file,
255+
)
256+
257+
os.makedirs(os.path.join(temp_dir, 'from_db'), exist_ok=True)
258+
# get catalog from SQLite index.db (hidden db) - not opted in operators
259+
catalog_from_db, _ = opm_migrate(
260+
index_db=artifact_index_db_file,
261+
base_dir=os.path.join(temp_dir, 'from_db'),
262+
generate_cache=False,
263+
)
264+
265+
catalog_from_index = localized_git_catalog_path
266+
267+
# we have to remove all `deprecation_bundles` from `catalog_from_index`
268+
# before merging catalogs otherwise if catalog was deprecated and
269+
# removed from `index.db` it stays on FBC (from_index)
270+
# Therefore we have to remove the directory before merging
271+
for deprecate_bundle_pull_spec in deprecation_bundles:
272+
# remove deprecated operators from FBC stored in index image
273+
deprecate_bundle = get_image_label(
274+
deprecate_bundle_pull_spec, 'operators.operatorframework.io.bundle.package.v1'
275+
)
276+
bundle_from_index = os.path.join(catalog_from_index, deprecate_bundle)
277+
if os.path.exists(bundle_from_index):
278+
log.debug(
279+
"Removing deprecated bundle from catalog before merging: %s",
280+
deprecate_bundle,
281+
)
282+
shutil.rmtree(bundle_from_index)
283+
284+
# overwrite data in `catalog_from_index` by data from `catalog_from_db`
285+
# this adds changes on not opted in operators to final
286+
merge_catalogs_dirs(catalog_from_db, catalog_from_index)
287+
288+
# If the container-tool podman is used in the opm commands above, opm will create temporary
289+
# files and directories without the write permission. This will cause the context manager
290+
# to fail to delete these files. Adjust the file modes to avoid this error.
291+
chmod_recursively(
292+
temp_dir,
293+
dir_mode=(stat.S_IRWXU | stat.S_IRWXG),
294+
file_mode=(stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP),
295+
)
296+
297+
# Write build metadata to a file to be added with the commit
298+
set_request_state(request_id, 'in_progress', 'Writing build metadata')
299+
write_build_metadata(
300+
local_git_repo_path,
301+
Opm.opm_version,
302+
prebuild_info['ocp_version'],
303+
str(distribution_scope),
304+
binary_image_resolved,
305+
request_id,
306+
)
307+
308+
try:
309+
# Commit changes and create MR or push directly
310+
mr_details, last_commit_sha = git_commit_and_create_mr_or_push(
311+
request_id=request_id,
312+
local_git_repo_path=local_git_repo_path,
313+
index_git_repo=index_git_repo,
314+
branch=branch,
315+
commit_message=(
316+
f"IIB: Add bundles for request {request_id}\n\n" f"Bundles: {', '.join(bundles)}"
317+
),
318+
overwrite_from_index=overwrite_from_index,
319+
)
320+
321+
# Wait for Konflux pipeline and extract built image URL
322+
image_url = monitor_pipeline_and_extract_image(
323+
request_id=request_id,
324+
last_commit_sha=last_commit_sha,
325+
)
326+
327+
# Copy built index to all output pull specs
328+
output_pull_specs = replicate_image_to_tagged_destinations(
329+
request_id=request_id,
330+
image_url=image_url,
331+
build_tags=build_tags,
332+
)
333+
334+
# Use the first output_pull_spec as the primary one for request updates
335+
output_pull_spec = output_pull_specs[0]
336+
# Update request with final output
337+
if not output_pull_spec:
338+
raise IIBError(
339+
"output_pull_spec was not set. "
340+
"This should not happen if the pipeline completed successfully."
341+
)
342+
343+
_update_index_image_pull_spec(
344+
output_pull_spec=output_pull_spec,
345+
request_id=request_id,
346+
arches=arches,
347+
from_index=from_index,
348+
overwrite_from_index=overwrite_from_index,
349+
overwrite_from_index_token=overwrite_from_index_token,
350+
resolved_prebuild_from_index=from_index_resolved,
351+
add_or_rm=True,
352+
is_image_fbc=True,
353+
# Passing an empty index_repo_map is intentional. In IIB 1.0, if
354+
# the overwrite_from_index token is given, we push to git by default
355+
# at the end of a request. In IIB 2.0, the commit is pushed earlier to trigger
356+
# a Konflux pipelinerun. So the old workflow isn't needed.
357+
index_repo_map={},
358+
)
359+
360+
# Push updated index.db if overwrite_from_index_token is provided
361+
# We can push it directly from temp_dir since we're still inside the
362+
# context manager. Do it as the last step to avoid rolling back the
363+
# index.db file if the pipeline fails.
364+
original_index_db_digest = push_index_db_artifact(
365+
request_id=request_id,
366+
from_index=str(from_index),
367+
index_db_path=artifact_index_db_file,
368+
operators=operators,
369+
# we are adding operators, we do not check for their existence in the index.db
370+
operators_in_db=set(),
371+
overwrite_from_index=overwrite_from_index,
372+
request_type='add',
373+
)
374+
375+
# Close MR if it was opened
376+
cleanup_merge_request_if_exists(mr_details, index_git_repo)
377+
378+
set_request_state(
379+
request_id,
380+
'complete',
381+
'The operator bundle(s) were successfully added to the index image',
382+
)
383+
except Exception as e:
384+
cleanup_on_failure(
385+
mr_details=mr_details,
386+
last_commit_sha=last_commit_sha,
387+
index_git_repo=index_git_repo,
388+
overwrite_from_index=overwrite_from_index,
389+
request_id=request_id,
390+
from_index=str(from_index),
391+
index_repo_map=index_to_gitlab_push_map or {},
392+
original_index_db_digest=original_index_db_digest,
393+
reason=f"error: {e}",
394+
)
395+
raise IIBError(f"Failed to add FBC fragment: {e}")

0 commit comments

Comments
 (0)