Skip to content

Commit fe9f569

Browse files
committed
Handling of fbc-operations for containerized IIB
Assisted by: Gemini, Claude [CLOUDDST-28644]
1 parent 655b700 commit fe9f569

File tree

7 files changed

+662
-4
lines changed

7 files changed

+662
-4
lines changed

iib/web/api_v1.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@
4949
handle_rm_request,
5050
)
5151
from iib.workers.tasks.build_add_deprecations import handle_add_deprecations_request
52-
from iib.workers.tasks.build_fbc_operations import handle_fbc_operation_request
52+
from iib.workers.tasks.build_containerized_fbc_operations import (
53+
handle_containerized_fbc_operation_request,
54+
)
5355
from iib.workers.tasks.build_recursive_related_bundles import (
5456
handle_recursive_related_bundles_request,
5557
)
@@ -1332,7 +1334,7 @@ def fbc_operations() -> Tuple[flask.Response, int]:
13321334
safe_args = _get_safe_args(args, payload)
13331335
error_callback = failed_request_callback.s(request.id)
13341336
try:
1335-
handle_fbc_operation_request.apply_async(
1337+
handle_containerized_fbc_operation_request.apply_async(
13361338
args=args, link_error=error_callback, argsrepr=repr(safe_args), queue=celery_queue
13371339
)
13381340
except kombu.exceptions.OperationalError:
Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
# SPDX-License-Identifier: GPL-3.0-or-later
2+
import logging
3+
import os
4+
import tempfile
5+
from typing import Dict, List, Optional, Set
6+
7+
from iib.common.common_utils import get_binary_versions
8+
from iib.common.tracing import instrument_tracing
9+
from iib.exceptions import IIBError
10+
from iib.workers.api_utils import set_request_state
11+
from iib.workers.tasks.build import (
12+
_cleanup,
13+
_update_index_image_build_state,
14+
_update_index_image_pull_spec,
15+
_skopeo_copy,
16+
)
17+
from iib.workers.tasks.celery import app
18+
from iib.workers.tasks.containerized_utils import (
19+
pull_index_db_artifact,
20+
write_build_metadata,
21+
get_list_of_output_pullspec,
22+
cleanup_on_failure,
23+
push_index_db_artifact,
24+
)
25+
from iib.workers.tasks.git_utils import (
26+
create_mr,
27+
clone_git_repo,
28+
get_git_token,
29+
get_last_commit_sha,
30+
resolve_git_url,
31+
commit_and_push,
32+
close_mr,
33+
)
34+
from iib.workers.tasks.konflux_utils import (
35+
wait_for_pipeline_completion,
36+
find_pipelinerun,
37+
get_pipelinerun_image_url,
38+
)
39+
from iib.workers.tasks.opm_operations import (
40+
Opm,
41+
opm_registry_add_fbc_fragment_containerized,
42+
)
43+
from iib.workers.tasks.utils import (
44+
get_resolved_image,
45+
prepare_request_for_build,
46+
request_logger,
47+
set_registry_token,
48+
RequestConfigFBCOperation,
49+
)
50+
51+
__all__ = ['handle_containerized_fbc_operation_request']
52+
53+
log = logging.getLogger(__name__)
54+
55+
56+
@app.task
57+
@request_logger
58+
@instrument_tracing(
59+
span_name="workers.tasks.build.handle_containerized_fbc_operation_request",
60+
attributes=get_binary_versions(),
61+
)
62+
def handle_containerized_fbc_operation_request(
63+
request_id: int,
64+
fbc_fragments: List[str],
65+
from_index: str,
66+
binary_image: Optional[str] = None,
67+
distribution_scope: str = '',
68+
overwrite_from_index: bool = False,
69+
overwrite_from_index_token: Optional[str] = None,
70+
build_tags: Optional[List[str]] = None,
71+
add_arches: Optional[Set[str]] = None,
72+
binary_image_config: Optional[Dict[str, Dict[str, str]]] = None,
73+
index_to_gitlab_push_map: Dict[str, str] = {},
74+
used_fbc_fragment: bool = False,
75+
) -> None:
76+
"""
77+
Add fbc fragments to an fbc index image.
78+
79+
:param list fbc_fragments: list of fbc fragments that need to be added to final FBC index image
80+
:param int request_id: the ID of the IIB build request
81+
:param str binary_image: the pull specification of the container image where the opm binary
82+
gets copied from.
83+
:param str from_index: the pull specification of the container image containing the index that
84+
the index image build will be based from.
85+
:param set add_arches: the set of arches to build in addition to the arches ``from_index`` is
86+
currently built for; if ``from_index`` is ``None``, then this is used as the list of arches
87+
to build the index image for
88+
:param dict index_to_gitlab_push_map: the dict mapping index images (keys) to GitLab repos
89+
(values) in order to push their catalogs into GitLab.
90+
:param bool used_fbc_fragment: flag indicating if the original request used fbc_fragment
91+
(single) instead of fbc_fragments (array). Used for backward compatibility.
92+
"""
93+
_cleanup()
94+
set_request_state(request_id, 'in_progress', 'Resolving the fbc fragments')
95+
96+
# Resolve all fbc fragments
97+
resolved_fbc_fragments = []
98+
for fbc_fragment in fbc_fragments:
99+
with set_registry_token(overwrite_from_index_token, fbc_fragment, append=True):
100+
resolved_fbc_fragment = get_resolved_image(fbc_fragment)
101+
resolved_fbc_fragments.append(resolved_fbc_fragment)
102+
103+
prebuild_info = prepare_request_for_build(
104+
request_id,
105+
RequestConfigFBCOperation(
106+
_binary_image=binary_image,
107+
from_index=from_index,
108+
overwrite_from_index_token=overwrite_from_index_token,
109+
add_arches=add_arches,
110+
fbc_fragments=fbc_fragments,
111+
distribution_scope=distribution_scope,
112+
binary_image_config=binary_image_config,
113+
),
114+
)
115+
116+
from_index_resolved = prebuild_info['from_index_resolved']
117+
binary_image_resolved = prebuild_info['binary_image_resolved']
118+
arches = prebuild_info['arches']
119+
# Variable mr_details needs to be assigned, otherwise
120+
# cleanup_on_failure() fails when an exception is raised.
121+
mr_details: Optional[Dict[str, str]] = None
122+
123+
with set_registry_token(overwrite_from_index_token, fbc_fragment, append=True):
124+
Opm.set_opm_version(from_index_resolved)
125+
126+
# Store all resolved fragments
127+
prebuild_info['fbc_fragments_resolved'] = resolved_fbc_fragments
128+
129+
# For backward compatibility, only populate old fields if original request used fbc_fragment
130+
# This flag should be passed from the API layer
131+
if used_fbc_fragment and resolved_fbc_fragments:
132+
prebuild_info['fbc_fragment_resolved'] = resolved_fbc_fragments[0]
133+
134+
_update_index_image_build_state(request_id, prebuild_info)
135+
136+
with tempfile.TemporaryDirectory(prefix=f'iib-{request_id}-') as temp_dir:
137+
artifact_dir = pull_index_db_artifact(
138+
from_index,
139+
temp_dir,
140+
)
141+
artifact_index_db_file = os.path.join(artifact_dir, "index.db")
142+
143+
log.debug("Artifact DB path %s", artifact_index_db_file)
144+
if not os.path.exists(artifact_index_db_file):
145+
log.error("Artifact DB file not found at %s", artifact_index_db_file)
146+
raise IIBError(f"Artifact DB file not found at {artifact_index_db_file}")
147+
148+
index_git_repo = resolve_git_url(
149+
from_index=from_index, index_repo_map=index_to_gitlab_push_map
150+
)
151+
if not index_git_repo:
152+
raise IIBError(f"Cannot resolve the git repository for {from_index}")
153+
log.info(
154+
"Git repo for %s: %s",
155+
from_index,
156+
index_git_repo,
157+
)
158+
159+
token_name, git_token = get_git_token(index_git_repo)
160+
branch = prebuild_info['ocp_version']
161+
162+
set_request_state(request_id, 'in_progress', 'Cloning Git repository')
163+
local_git_repo_path = os.path.join(temp_dir, 'git', branch)
164+
os.makedirs(local_git_repo_path, exist_ok=True)
165+
166+
# TODO - GitClone takes time
167+
# can we keep the copy and just pull the difference? (6min on dev-env)
168+
clone_git_repo(index_git_repo, branch, token_name, git_token, local_git_repo_path)
169+
170+
localized_git_catalog_path = os.path.join(local_git_repo_path, 'configs')
171+
if not os.path.exists(localized_git_catalog_path):
172+
raise IIBError(f"Catalogs directory not found in {local_git_repo_path}")
173+
174+
set_request_state(request_id, 'in_progress', 'Adding fbc fragment')
175+
(
176+
updated_catalog_path,
177+
index_db_path,
178+
_,
179+
operators_in_db,
180+
) = opm_registry_add_fbc_fragment_containerized(
181+
request_id=request_id,
182+
temp_dir=temp_dir,
183+
from_index_configs_dir=localized_git_catalog_path,
184+
fbc_fragments=resolved_fbc_fragments,
185+
overwrite_from_index_token=overwrite_from_index_token,
186+
generate_cache=False,
187+
index_db_path=artifact_index_db_file,
188+
)
189+
190+
# Write build metadata to a file to be added with the commit
191+
set_request_state(request_id, 'in_progress', 'Writing build metadata')
192+
write_build_metadata(
193+
local_git_repo_path,
194+
Opm.opm_version,
195+
prebuild_info['ocp_version'],
196+
distribution_scope,
197+
binary_image_resolved,
198+
request_id,
199+
)
200+
201+
try:
202+
# Commit changes and create PR or push directly
203+
set_request_state(request_id, 'in_progress', 'Committing changes to Git repository')
204+
log.info("Committing changes to Git repository. Triggering KONFLUX pipeline.")
205+
206+
# Determine if this is a throw-away request (no overwrite_from_index_token)
207+
if not overwrite_from_index_token:
208+
# Create MR for throw-away requests
209+
mr_details = create_mr(
210+
request_id=request_id,
211+
local_repo_path=local_git_repo_path,
212+
repo_url=index_git_repo,
213+
branch=branch,
214+
commit_message=(
215+
f"IIB: Add data from FBC fragments for request {request_id}\n\n"
216+
f"FBC fragments: {', '.join(fbc_fragments)}"
217+
),
218+
)
219+
log.info("Created merge request: %s", mr_details.get('mr_url'))
220+
else:
221+
# Push directly to the branch
222+
commit_and_push(
223+
request_id=request_id,
224+
local_repo_path=local_git_repo_path,
225+
repo_url=index_git_repo,
226+
branch=branch,
227+
commit_message=(
228+
f"IIB: Add data from FBC fragments for request {request_id}\n\n"
229+
f"FBC fragments: {', '.join(fbc_fragments)}"
230+
),
231+
)
232+
233+
# Get commit SHA before waiting for the pipeline (while the temp directory still exists)
234+
last_commit_sha = get_last_commit_sha(local_repo_path=local_git_repo_path)
235+
236+
# Wait for Konflux pipeline
237+
set_request_state(request_id, 'in_progress', 'Waiting on KONFLUX build')
238+
239+
# find_pipelinerun has retry decorator to handle delays in pipelinerun creation
240+
pipelines = find_pipelinerun(last_commit_sha)
241+
242+
# Get the first pipelinerun (should typically be only one)
243+
pipelinerun = pipelines[0]
244+
pipelinerun_name = pipelinerun.get('metadata', {}).get('name')
245+
if not pipelinerun_name:
246+
raise IIBError("Pipelinerun name not found in pipeline metadata")
247+
248+
run = wait_for_pipeline_completion(pipelinerun_name)
249+
250+
set_request_state(request_id, 'in_progress', 'Copying built index to IIB registry')
251+
# Extract IMAGE_URL from pipelinerun results
252+
image_url = get_pipelinerun_image_url(pipelinerun_name, run)
253+
output_pull_specs = get_list_of_output_pullspec(request_id, build_tags)
254+
# Copy the built index from Konflux to all output pull specs
255+
for spec in output_pull_specs:
256+
_skopeo_copy(
257+
source=f'docker://{image_url}',
258+
destination=f'docker://{spec}',
259+
copy_all=True,
260+
exc_msg=f'Failed to copy built index from Konflux to {spec}',
261+
)
262+
log.info("Successfully copied image to %s", spec)
263+
264+
# Use the first output_pull_spec as the primary one for request updates
265+
output_pull_spec = output_pull_specs[0]
266+
# Update request with final output
267+
if not output_pull_spec:
268+
raise IIBError(
269+
"output_pull_spec was not set. "
270+
"This should not happen if the pipeline completed successfully."
271+
)
272+
273+
_update_index_image_pull_spec(
274+
output_pull_spec=output_pull_spec,
275+
request_id=request_id,
276+
arches=arches,
277+
from_index=from_index,
278+
overwrite_from_index=overwrite_from_index,
279+
overwrite_from_index_token=overwrite_from_index_token,
280+
resolved_prebuild_from_index=from_index_resolved,
281+
add_or_rm=True,
282+
is_image_fbc=True,
283+
# Passing an empty index_repo_map is intentional. In IIB 1.0, if
284+
# the overwrite_from_index token is given, we push to git by default
285+
# at the end of a request. In IIB 2.0, the commit is pushed earlier to trigger
286+
# a Konflux pipelinerun. So the old workflow isn't needed.
287+
index_repo_map={},
288+
)
289+
290+
# Push updated index.db if overwrite_from_index_token is provided
291+
# We can push it directly from temp_dir since we're still inside the
292+
# context manager. Do it as the last step to avoid rolling back the
293+
# index.db file if the pipeline fails.
294+
original_index_db_digest = push_index_db_artifact(
295+
request_id=request_id,
296+
from_index=from_index,
297+
index_db_path=index_db_path,
298+
operators=operators_in_db,
299+
operators_in_db=set(operators_in_db),
300+
overwrite_from_index=overwrite_from_index,
301+
request_type='rm',
302+
)
303+
304+
# Close MR if it was opened
305+
if mr_details and index_git_repo:
306+
try:
307+
close_mr(mr_details, index_git_repo)
308+
log.info("Closed merge request: %s", mr_details.get('mr_url'))
309+
except IIBError as e:
310+
log.warning("Failed to close merge request: %s", e)
311+
312+
set_request_state(
313+
request_id,
314+
'complete',
315+
f"The operator(s) {operators_in_db} were successfully removed "
316+
"from the index image",
317+
)
318+
except Exception as e:
319+
cleanup_on_failure(
320+
mr_details=mr_details,
321+
last_commit_sha=last_commit_sha,
322+
index_git_repo=index_git_repo,
323+
overwrite_from_index=overwrite_from_index,
324+
request_id=request_id,
325+
from_index=from_index,
326+
index_repo_map=index_to_gitlab_push_map or {},
327+
original_index_db_digest=original_index_db_digest,
328+
reason=f"error: {e}",
329+
)
330+
raise IIBError(f"Failed to add FBC fragment: {e}")

0 commit comments

Comments
 (0)