Skip to content

Commit 1c3b892

Browse files
authored
Skip building arm64 community images in patches (#47)
# Summary It skips building arm64 docker images when building images in patches and in evergreen. arm64 images takes long time to build probably due to emulation (>10min arm64 vs 10s amd64). The change should not impact any daily build processes. ## Proof of Work Only manual run: [with skipped arm64](https://spruce.mongodb.com/task/mongodb_kubernetes_init_test_run_build_readiness_probe_image_patch_f0050b8942545701e8cb9e42d54d14f0cb58ee6a_680bc0fd99bf4d0007ff73f4_25_04_25_17_06_06/logs?execution=0), total time: 2min ([link to relevant log message](https://parsley.mongodb.com/evergreen/mongodb_kubernetes_init_test_run_build_readiness_probe_image_patch_f0050b8942545701e8cb9e42d54d14f0cb58ee6a_680bc0fd99bf4d0007ff73f4_25_04_25_17_06_06/0/task?bookmarks=0%2C542&selectedLineRange=L505&shareLine=505)) [build from master](https://spruce.mongodb.com/task/mongodb_kubernetes_init_test_run_build_readiness_probe_image_f0050b8942545701e8cb9e42d54d14f0cb58ee6a_25_04_25_12_41_27/logs?execution=1): total time: 12min ## Checklist - [ ] Have you linked a jira ticket and/or is the ticket in the title? - [ ] Have you checked whether your jira ticket required DOCSP changes? - [ ] Have you checked for release_note changes? ## Reminder (Please remove this when merging) - Please try to Approve or Reject Changes the PR, keep PRs in review as short as possible - Our Short Guide for PRs: [Link](https://docs.google.com/document/d/1T93KUtdvONq43vfTfUt8l92uo4e4SEEvFbIEKOxGr44/edit?tab=t.0) - Remember the following Communication Standards - use comment prefixes for clarity: * **blocking**: Must be addressed before approval. * **follow-up**: Can be addressed in a later PR or ticket. * **q**: Clarifying question. * **nit**: Non-blocking suggestions. * **note**: Side-note, non-actionable. Example: Praise * --> no prefix is considered a question
1 parent 8f3c535 commit 1c3b892

File tree

2 files changed

+138
-16
lines changed

2 files changed

+138
-16
lines changed

pipeline.py

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -305,19 +305,19 @@ def copy_into_container(client, src, dst):
305305
# Sonar as an interface to docker. We decided to keep this asymmetry for now, as Sonar will be removed soon.
306306

307307

308-
def create_and_push_manifest(image: str, tag: str) -> None:
308+
def create_and_push_manifest(image: str, tag: str, architectures: list[str]) -> None:
309309
final_manifest = image + ":" + tag
310310

311311
args = [
312312
"docker",
313313
"manifest",
314314
"create",
315315
final_manifest,
316-
"--amend",
317-
final_manifest + "-amd64",
318-
"--amend",
319-
final_manifest + "-arm64",
320316
]
317+
318+
for arch in architectures:
319+
args.extend(["--amend", f"{final_manifest}-{arch}"])
320+
321321
args_str = " ".join(args)
322322
logger.debug(f"creating new manifest: {args_str}")
323323
cp = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@@ -752,6 +752,14 @@ def submit(self, fn, *args, **kwargs):
752752
"""
753753

754754

755+
def should_skip_arm64():
756+
"""
757+
Determines if arm64 builds should be skipped based on environment.
758+
Returns True if running in Evergreen pipeline as a patch.
759+
"""
760+
return is_running_in_evg_pipeline() and is_running_in_patch()
761+
762+
755763
def build_image_daily(
756764
image_name: str, # corresponds to the image_name in the release.json
757765
min_version: str = None,
@@ -766,6 +774,10 @@ def get_architectures_set(build_configuration, args):
766774
if arch_set == {"arm64"}:
767775
raise ValueError("Building for ARM64 only is not supported yet")
768776

777+
if should_skip_arm64():
778+
logger.info("Skipping ARM64 builds as this is running in EVG pipeline as a patch")
779+
return {"amd64"}
780+
769781
# Automatic architecture detection is the default behavior if 'arch' argument isn't specified
770782
if arch_set == set():
771783
if check_multi_arch(
@@ -779,13 +791,13 @@ def get_architectures_set(build_configuration, args):
779791

780792
return arch_set
781793

782-
def create_and_push_manifests(args: dict):
794+
def create_and_push_manifests(args: dict, architectures: list[str]):
783795
"""Create and push manifests for all registries."""
784796
registries = [args["ecr_registry_ubi"], args["quay_registry"]]
785797
tags = [args["release_version"], args["release_version"] + "-b" + args["build_id"]]
786798
for registry in registries:
787799
for tag in tags:
788-
create_and_push_manifest(registry + args["ubi_suffix"], tag)
800+
create_and_push_manifest(registry + args["ubi_suffix"], tag, architectures=architectures)
789801

790802
def sign_image_concurrently(executor, args, futures, arch=None):
791803
v = args["release_version"]
@@ -838,7 +850,7 @@ def inner(build_configuration: BuildConfiguration):
838850
)
839851
if build_configuration.sign:
840852
sign_image_concurrently(executor, copy.deepcopy(args), futures, arch)
841-
create_and_push_manifests(args)
853+
create_and_push_manifests(args, list(arch_set))
842854
for arch in arch_set:
843855
args["architecture_suffix"] = f"-{arch}"
844856
args["platform"] = arch
@@ -985,12 +997,13 @@ def build_image_generic(
985997
if is_multi_arch:
986998
# we only push the manifests of the context images here,
987999
# since daily rebuilds will push the manifests for the proper images later
988-
create_and_push_manifest(registry_address, f"{version}-context")
1000+
architectures = [v["architecture"] for v in multi_arch_args_list]
1001+
create_and_push_manifest(registry_address, f"{version}-context", architectures=architectures)
9891002
if not config.is_release_step_executed():
9901003
# Normally daily rebuild would create and push the manifests for the non-context images.
9911004
# But since we don't run daily rebuilds on ecr image builds, we can do that step instead here.
9921005
# We only need to push manifests for multi-arch images.
993-
create_and_push_manifest(registry_address, version)
1006+
create_and_push_manifest(registry_address, version, architectures=architectures)
9941007
if config.sign and config.is_release_step_executed():
9951008
sign_and_verify_context_image(registry, version)
9961009
if config.is_release_step_executed() and version and QUAY_REGISTRY_URL in registry:
@@ -1044,7 +1057,14 @@ def build_community_image(build_configuration: BuildConfiguration, image_type: s
10441057

10451058
version, is_release = get_git_release_tag()
10461059
golang_version = os.getenv("GOLANG_VERSION", "1.24")
1047-
architectures = build_configuration.architecture or ["amd64", "arm64"]
1060+
1061+
# Use only amd64 if we should skip arm64 builds
1062+
if should_skip_arm64():
1063+
architectures = ["amd64"]
1064+
logger.info("Skipping ARM64 builds for community image as this is running in EVG pipeline as a patch")
1065+
else:
1066+
architectures = build_configuration.architecture or ["amd64", "arm64"]
1067+
10481068
multi_arch_args_list = []
10491069

10501070
for arch in architectures:
@@ -1064,7 +1084,7 @@ def build_community_image(build_configuration: BuildConfiguration, image_type: s
10641084
multi_arch_args_list=multi_arch_args_list,
10651085
inventory_file=inventory_file,
10661086
registry_address=f"{base_repo}/{image_name}",
1067-
is_multi_arch=True,
1087+
is_multi_arch=True, # We for pushing manifest anyway, even if arm64 is skipped in patches
10681088
)
10691089

10701090

@@ -1151,16 +1171,19 @@ def build_multi_arch_agent_in_sonar(
11511171
ecr_registry = os.environ.get("REGISTRY", "268558157000.dkr.ecr.us-east-1.amazonaws.com/dev")
11521172
ecr_agent_registry = ecr_registry + f"/mongodb-agent-ubi"
11531173
quay_agent_registry = QUAY_REGISTRY_URL + f"/mongodb-agent-ubi"
1154-
joined_args = list()
1155-
for arch in [arch_arm, arch_amd]:
1156-
joined_args.append(args | arch)
1174+
joined_args = [arch_amd]
1175+
1176+
# Only include arm64 if we shouldn't skip it
1177+
if not should_skip_arm64():
1178+
joined_args.append(arch_arm)
1179+
11571180
build_image_generic(
11581181
config=build_configuration,
11591182
image_name="mongodb-agent",
11601183
inventory_file="inventories/agent_non_matrix.yaml",
11611184
multi_arch_args_list=joined_args,
11621185
registry_address=quay_agent_registry if is_release else ecr_agent_registry,
1163-
is_multi_arch=True,
1186+
is_multi_arch=True, # We for pushing manifest anyway, even if arm64 is skipped in patches
11641187
is_run_in_parallel=True,
11651188
)
11661189

pipeline_test.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,3 +225,102 @@ def test_all_retries_fail(self, mock_sleep, mock_run):
225225

226226
self.assertEqual(mock_run.call_count, 3)
227227
self.assertEqual(mock_sleep.call_count, 2)
228+
229+
230+
@patch("subprocess.run")
231+
def test_create_and_push_manifest_success(mock_run):
232+
"""Test successful creation and pushing of manifest with multiple architectures."""
233+
# Setup mock to return success for both calls
234+
mock_run.return_value = subprocess.CompletedProcess(args=[], returncode=0, stdout=b"", stderr=b"")
235+
236+
image = "test/image"
237+
tag = "1.0.0"
238+
architectures = ["amd64", "arm64"]
239+
240+
from pipeline import create_and_push_manifest
241+
242+
create_and_push_manifest(image, tag, architectures)
243+
244+
assert mock_run.call_count == 2
245+
246+
# Verify first call - create manifest
247+
create_call_args = mock_run.call_args_list[0][0][0]
248+
assert create_call_args == [
249+
"docker",
250+
"manifest",
251+
"create",
252+
"test/image:1.0.0",
253+
"--amend",
254+
"test/image:1.0.0-amd64",
255+
"--amend",
256+
"test/image:1.0.0-arm64",
257+
]
258+
259+
# Verify second call - push manifest
260+
push_call_args = mock_run.call_args_list[1][0][0]
261+
assert push_call_args == ["docker", "manifest", "push", f"{image}:{tag}"]
262+
263+
264+
@patch("subprocess.run")
265+
def test_create_and_push_manifest_single_arch(mock_run):
266+
"""Test manifest creation with a single architecture."""
267+
# Setup mock to return success for both calls
268+
mock_run.return_value = subprocess.CompletedProcess(args=[], returncode=0, stdout=b"", stderr=b"")
269+
270+
image = "test/image"
271+
tag = "1.0.0"
272+
architectures = ["amd64"]
273+
274+
from pipeline import create_and_push_manifest
275+
276+
create_and_push_manifest(image, tag, architectures)
277+
278+
# Verify first call - create manifest (should only include one architecture)
279+
create_call_args = mock_run.call_args_list[0][0][0]
280+
assert " ".join(create_call_args) == f"docker manifest create {image}:{tag} --amend {image}:{tag}-amd64"
281+
282+
283+
@patch("subprocess.run")
284+
def test_create_and_push_manifest_create_error(mock_run):
285+
"""Test error handling when manifest creation fails."""
286+
# Setup mock to return error for create call
287+
mock_run.return_value = subprocess.CompletedProcess(
288+
args=[], returncode=1, stdout=b"", stderr=b"Error creating manifest"
289+
)
290+
291+
image = "test/image"
292+
tag = "1.0.0"
293+
architectures = ["amd64", "arm64"]
294+
295+
from pipeline import create_and_push_manifest
296+
297+
# Verify exception is raised with the stderr content
298+
with pytest.raises(Exception) as exc_info:
299+
create_and_push_manifest(image, tag, architectures)
300+
301+
assert "Error creating manifest" in str(exc_info.value)
302+
assert mock_run.call_count == 1 # Only the create call, not the push call
303+
304+
305+
@patch("subprocess.run")
306+
def test_create_and_push_manifest_push_error(mock_run):
307+
"""Test error handling when manifest push fails."""
308+
# Setup mock to return success for create but error for push
309+
mock_run.side_effect = [
310+
subprocess.CompletedProcess(args=[], returncode=0, stdout=b"", stderr=b""), # create success
311+
subprocess.CompletedProcess(args=[], returncode=1, stdout=b"", stderr=b"Error pushing manifest"), # push error
312+
]
313+
314+
# Call function with test parameters
315+
image = "test/image"
316+
tag = "1.0.0"
317+
architectures = ["amd64", "arm64"]
318+
319+
from pipeline import create_and_push_manifest
320+
321+
# Verify exception is raised with the stderr content
322+
with pytest.raises(Exception) as exc_info:
323+
create_and_push_manifest(image, tag, architectures)
324+
325+
assert "Error pushing manifest" in str(exc_info.value)
326+
assert mock_run.call_count == 2 # Both create and push calls

0 commit comments

Comments
 (0)