blob: 26539d4c8d01552588f62abbea7752efa46a3d07 [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2018 The ChromiumOS Authors
Alex Klein2966e302019-01-17 13:29:38 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Image API Service.
6
7The image related API endpoints should generally be found here.
8"""
9
Jack Neus5e56fef2021-06-18 16:57:28 +000010import functools
Alex Kleina0421f32022-09-12 14:39:29 -060011import json
Chris McDonald1672ddb2021-07-21 11:48:23 -060012import logging
Alex Klein2966e302019-01-17 13:29:38 -070013import os
Jack Neus94a35be2021-09-10 15:18:06 +000014from pathlib import Path
Alex Kleina0421f32022-09-12 14:39:29 -060015import time
Josiah Hounyode113e52022-11-30 06:30:33 +000016import traceback
Kevin Shelton5b21c8b2022-04-04 17:04:13 -070017from typing import List, NamedTuple, Set, TYPE_CHECKING, Union
Alex Klein2966e302019-01-17 13:29:38 -070018
Alex Klein8cb365a2019-05-15 16:24:53 -060019from chromite.api import controller
Alex Klein076841b2019-08-29 15:19:39 -060020from chromite.api import faux
Alex Klein2b236722019-06-19 15:44:26 -060021from chromite.api import validate
Alex Kleine9a7dbf2020-10-06 18:12:12 -060022from chromite.api.controller import controller_util
David Burgerb171d652019-05-13 16:07:00 -060023from chromite.api.gen.chromiumos import common_pb2
George Engelbrechtc9a8e812021-06-16 18:14:17 -060024from chromite.lib import build_target_lib
25from chromite.lib import chroot_lib
Alex Klein56355682019-02-07 10:36:54 -070026from chromite.lib import constants
Chris McDonald1672ddb2021-07-21 11:48:23 -060027from chromite.lib import cros_build_lib
Alex Klein56355682019-02-07 10:36:54 -070028from chromite.lib import image_lib
George Engelbrechtc9a8e812021-06-16 18:14:17 -060029from chromite.lib import sysroot_lib
Jack Neus761e1842020-12-01 18:20:11 +000030from chromite.scripts import pushimage
Alex Kleinb7cdbe62019-02-22 11:41:32 -070031from chromite.service import image
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040032from chromite.service import packages as packages_service
33
Alex Klein2966e302019-01-17 13:29:38 -070034
Kevin Shelton5b21c8b2022-04-04 17:04:13 -070035if TYPE_CHECKING:
Alex Klein1699fab2022-09-08 08:46:06 -060036 from chromite.api import api_config
37 from chromite.api.gen.chromite.api import image_pb2
Kevin Shelton5b21c8b2022-04-04 17:04:13 -070038
Alex Klein56355682019-02-07 10:36:54 -070039# The image.proto ImageType enum ids.
George Engelbrechtc55d6312021-05-05 12:11:13 -060040_BASE_ID = common_pb2.IMAGE_TYPE_BASE
41_DEV_ID = common_pb2.IMAGE_TYPE_DEV
42_TEST_ID = common_pb2.IMAGE_TYPE_TEST
43_BASE_VM_ID = common_pb2.IMAGE_TYPE_BASE_VM
44_TEST_VM_ID = common_pb2.IMAGE_TYPE_TEST_VM
45_RECOVERY_ID = common_pb2.IMAGE_TYPE_RECOVERY
46_FACTORY_ID = common_pb2.IMAGE_TYPE_FACTORY
47_FIRMWARE_ID = common_pb2.IMAGE_TYPE_FIRMWARE
48_BASE_GUEST_VM_ID = common_pb2.IMAGE_TYPE_BASE_GUEST_VM
49_TEST_GUEST_VM_ID = common_pb2.IMAGE_TYPE_TEST_GUEST_VM
Jack Neus70490b52022-09-12 14:41:45 +000050_NETBOOT_ID = common_pb2.IMAGE_TYPE_NETBOOT
Alex Klein56355682019-02-07 10:36:54 -070051
52# Dict to allow easily translating names to enum ids and vice versa.
53_IMAGE_MAPPING = {
54 _BASE_ID: constants.IMAGE_TYPE_BASE,
55 constants.IMAGE_TYPE_BASE: _BASE_ID,
56 _DEV_ID: constants.IMAGE_TYPE_DEV,
57 constants.IMAGE_TYPE_DEV: _DEV_ID,
58 _TEST_ID: constants.IMAGE_TYPE_TEST,
59 constants.IMAGE_TYPE_TEST: _TEST_ID,
Michael Mortenseneefe8952019-08-12 15:37:15 -060060 _RECOVERY_ID: constants.IMAGE_TYPE_RECOVERY,
61 constants.IMAGE_TYPE_RECOVERY: _RECOVERY_ID,
Alex Klein9039a952021-07-27 13:52:39 -060062 _FACTORY_ID: constants.IMAGE_TYPE_FACTORY_SHIM,
63 constants.IMAGE_TYPE_FACTORY_SHIM: _FACTORY_ID,
Michael Mortenseneefe8952019-08-12 15:37:15 -060064 _FIRMWARE_ID: constants.IMAGE_TYPE_FIRMWARE,
65 constants.IMAGE_TYPE_FIRMWARE: _FIRMWARE_ID,
Jack Neus70490b52022-09-12 14:41:45 +000066 _NETBOOT_ID: constants.IMAGE_TYPE_NETBOOT,
67 constants.IMAGE_TYPE_NETBOOT: _NETBOOT_ID,
Alex Klein56355682019-02-07 10:36:54 -070068}
69
George Engelbrecht9f4f8322021-03-08 12:04:17 -070070# Dict to describe the prerequisite built images for each VM image type.
Alex Klein21b95022019-05-09 14:14:46 -060071_VM_IMAGE_MAPPING = {
72 _BASE_VM_ID: _IMAGE_MAPPING[_BASE_ID],
73 _TEST_VM_ID: _IMAGE_MAPPING[_TEST_ID],
Trent Begin008cade2019-10-31 13:40:59 -060074 _BASE_GUEST_VM_ID: _IMAGE_MAPPING[_BASE_ID],
75 _TEST_GUEST_VM_ID: _IMAGE_MAPPING[_TEST_ID],
Alex Klein21b95022019-05-09 14:14:46 -060076}
77
George Engelbrecht9f4f8322021-03-08 12:04:17 -070078# Dict to describe the prerequisite built images for each mod image type.
79_MOD_IMAGE_MAPPING = {
80 _RECOVERY_ID: _IMAGE_MAPPING[_BASE_ID],
Jack Neus0829c7e2022-09-15 15:41:57 +000081 _NETBOOT_ID: _IMAGE_MAPPING[_FACTORY_ID],
George Engelbrecht9f4f8322021-03-08 12:04:17 -070082}
83
Jack Neus761e1842020-12-01 18:20:11 +000084# Supported image types for PushImage.
85SUPPORTED_IMAGE_TYPES = {
86 common_pb2.IMAGE_TYPE_RECOVERY: constants.IMAGE_TYPE_RECOVERY,
87 common_pb2.IMAGE_TYPE_FACTORY: constants.IMAGE_TYPE_FACTORY,
88 common_pb2.IMAGE_TYPE_FIRMWARE: constants.IMAGE_TYPE_FIRMWARE,
89 common_pb2.IMAGE_TYPE_ACCESSORY_USBPD: constants.IMAGE_TYPE_ACCESSORY_USBPD,
90 common_pb2.IMAGE_TYPE_ACCESSORY_RWSIG: constants.IMAGE_TYPE_ACCESSORY_RWSIG,
Evan Benn4d061102022-02-14 12:50:45 +110091 common_pb2.IMAGE_TYPE_HPS_FIRMWARE: constants.IMAGE_TYPE_HPS_FIRMWARE,
Jack Neus761e1842020-12-01 18:20:11 +000092 common_pb2.IMAGE_TYPE_BASE: constants.IMAGE_TYPE_BASE,
George Engelbrecht9f4f8322021-03-08 12:04:17 -070093 common_pb2.IMAGE_TYPE_GSC_FIRMWARE: constants.IMAGE_TYPE_GSC_FIRMWARE,
Jack Neus761e1842020-12-01 18:20:11 +000094}
95
Alex Klein27978a42021-07-27 14:18:10 -060096# Built image directory symlink names. These names allow specifying a static
97# location for creation to simplify later archival stages. In practice, this
98# sets the symlink argument to build_packages.
99# Core are the build/dev/test images.
100# Use "latest" until we do a better job of passing through image directories,
101# e.g. for artifacts.
Alex Klein1699fab2022-09-08 08:46:06 -0600102LOCATION_CORE = "latest"
Alex Klein27978a42021-07-27 14:18:10 -0600103# The factory_install image.
Alex Klein1699fab2022-09-08 08:46:06 -0600104LOCATION_FACTORY = "factory_shim"
Alex Klein27978a42021-07-27 14:18:10 -0600105
106
107class ImageTypes(NamedTuple):
Alex Klein1699fab2022-09-08 08:46:06 -0600108 """Parsed image types."""
Alex Klein27978a42021-07-27 14:18:10 -0600109
Alex Klein1699fab2022-09-08 08:46:06 -0600110 images: Set[str]
111 vms: Set[int]
112 mod_images: Set[int]
Alex Klein27978a42021-07-27 14:18:10 -0600113
Alex Klein1699fab2022-09-08 08:46:06 -0600114 @property
115 def core_images(self) -> List[str]:
116 """The core images (base/dev/test) as a list."""
117 return list(self.images - {_IMAGE_MAPPING[_FACTORY_ID]}) or []
Alex Klein27978a42021-07-27 14:18:10 -0600118
Alex Klein1699fab2022-09-08 08:46:06 -0600119 @property
120 def has_factory(self) -> bool:
121 """Whether the factory image is present."""
122 return _IMAGE_MAPPING[_FACTORY_ID] in self.images
123
124 @property
125 def factory(self) -> List[str]:
126 """A list with the factory type if set."""
127 return [_IMAGE_MAPPING[_FACTORY_ID]] if self.has_factory else []
Alex Klein27978a42021-07-27 14:18:10 -0600128
Alex Klein56355682019-02-07 10:36:54 -0700129
Alex Klein1699fab2022-09-08 08:46:06 -0600130def _add_image_to_proto(
131 output_proto, path: Union["Path", str], image_type: int, board: str
132):
133 """Quick helper function to add a new image to the output proto."""
134 new_image = output_proto.images.add()
135 new_image.path = str(path)
136 new_image.type = image_type
137 new_image.build_target.name = board
George Engelbrecht9f4f8322021-03-08 12:04:17 -0700138
139
George Engelbrechtc9a8e812021-06-16 18:14:17 -0600140def ExampleGetResponse():
Alex Klein1699fab2022-09-08 08:46:06 -0600141 """Give an example response to assemble upstream in caller artifacts."""
142 uabs = common_pb2.UploadedArtifactsByService
143 cabs = common_pb2.ArtifactsByService
144 return uabs.Sysroot(
145 artifacts=[
146 uabs.Image.ArtifactPaths(
147 artifact_type=cabs.Image.ArtifactType.DLC_IMAGE,
148 paths=[
149 common_pb2.Path(
150 path="/tmp/dlc/dlc.img",
151 location=common_pb2.Path.OUTSIDE,
152 )
153 ],
154 )
155 ]
156 )
George Engelbrechtc9a8e812021-06-16 18:14:17 -0600157
158
Alex Klein1699fab2022-09-08 08:46:06 -0600159def GetArtifacts(
160 in_proto: common_pb2.ArtifactsByService.Image,
161 chroot: chroot_lib.Chroot,
162 sysroot_class: sysroot_lib.Sysroot,
163 build_target: build_target_lib.BuildTarget,
164 output_dir,
165) -> list:
166 """Builds and copies images to specified output_dir.
George Engelbrechtc9a8e812021-06-16 18:14:17 -0600167
Alex Klein1699fab2022-09-08 08:46:06 -0600168 Copies (after optionally bundling) all required images into the output_dir,
169 returning a mapping of image type to a list of (output_dir) paths to
170 the desired files. Note that currently it is only processing one image (DLC),
171 but the future direction is to process all required images. Required images
172 are located within output_artifact.artifact_type.
George Engelbrechtc9a8e812021-06-16 18:14:17 -0600173
Alex Klein1699fab2022-09-08 08:46:06 -0600174 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600175 in_proto: Proto request defining reqs.
176 chroot: The chroot proto used for these artifacts.
177 sysroot_class: The sysroot proto used for these artifacts.
178 build_target: The build target used for these artifacts.
179 output_dir: The path to write artifacts to.
George Engelbrechtc9a8e812021-06-16 18:14:17 -0600180
Alex Klein1699fab2022-09-08 08:46:06 -0600181 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -0600182 A list of dictionary mappings of ArtifactType to list of paths.
Alex Klein1699fab2022-09-08 08:46:06 -0600183 """
184 base_path = chroot.full_path(sysroot_class.path)
185 board = build_target.name
186 factory_shim_location = Path(
187 image_lib.GetLatestImageLink(board, pointer=LOCATION_FACTORY)
188 )
Jack Neus5e56fef2021-06-18 16:57:28 +0000189
Alex Klein1699fab2022-09-08 08:46:06 -0600190 generated = []
191 dlc_func = functools.partial(image.copy_dlc_image, base_path)
192 license_func = functools.partial(
193 image.copy_license_credits, board, symlink=LOCATION_CORE
194 )
195 factory_image_func = functools.partial(
196 image.create_factory_image_zip,
197 chroot,
198 sysroot_class,
199 factory_shim_location,
200 packages_service.determine_full_version(),
201 )
202 stripped_packags_func = functools.partial(
203 image.create_stripped_packages_tar,
204 chroot,
205 build_target,
206 )
Jack Neus91846002022-12-01 23:49:22 +0000207 image_scripts_func = functools.partial(
208 image.create_image_scripts_archive, build_target
209 )
210
Alex Klein1699fab2022-09-08 08:46:06 -0600211 artifact_types = {
212 in_proto.ArtifactType.DLC_IMAGE: dlc_func,
213 in_proto.ArtifactType.LICENSE_CREDITS: license_func,
214 in_proto.ArtifactType.FACTORY_IMAGE: factory_image_func,
215 in_proto.ArtifactType.STRIPPED_PACKAGES: stripped_packags_func,
Jack Neus91846002022-12-01 23:49:22 +0000216 in_proto.ArtifactType.IMAGE_SCRIPTS: image_scripts_func,
Alex Klein1699fab2022-09-08 08:46:06 -0600217 }
George Engelbrechtc9a8e812021-06-16 18:14:17 -0600218
Alex Klein1699fab2022-09-08 08:46:06 -0600219 for output_artifact in in_proto.output_artifacts:
220 for artifact_type, func in artifact_types.items():
221 if artifact_type in output_artifact.artifact_types:
Josiah Hounyode113e52022-11-30 06:30:33 +0000222 try:
223 result = func(output_dir)
224 except Exception as e:
225 generated.append(
226 {
227 "type": artifact_type,
228 "failed": True,
229 "failure_reason": str(e),
230 }
231 )
232 artifact_name = (
233 common_pb2.ArtifactsByService.Image.ArtifactType.Name(
234 artifact_type
235 )
236 )
237 logging.warning(
238 "%s artifact generation failed with exception %s",
239 artifact_name,
240 e,
241 )
242 logging.warning("traceback:\n%s", traceback.format_exc())
243 continue
Alex Klein1699fab2022-09-08 08:46:06 -0600244 if result:
245 generated.append(
246 {
247 "paths": [result]
248 if isinstance(result, str)
249 else result,
250 "type": artifact_type,
251 }
252 )
George Engelbrechtc9a8e812021-06-16 18:14:17 -0600253
Alex Klein1699fab2022-09-08 08:46:06 -0600254 return generated
Pi-Hsun Shih8d9c8e42021-06-30 03:27:28 +0000255
Alex Kleincaace392021-07-26 14:28:13 -0600256
Michael Mortensen10146cf2019-11-19 19:59:22 -0700257def _CreateResponse(_input_proto, output_proto, _config):
Alex Klein1699fab2022-09-08 08:46:06 -0600258 """Set output_proto success field on a successful Create response."""
259 output_proto.success = True
Michael Mortensen10146cf2019-11-19 19:59:22 -0700260
261
262@faux.success(_CreateResponse)
Michael Mortensen85d38402019-12-12 09:50:29 -0700263@faux.empty_completed_unsuccessfully_error
Alex Klein1699fab2022-09-08 08:46:06 -0600264@validate.require("build_target.name")
Alex Klein231d2da2019-07-22 16:44:45 -0600265@validate.validation_complete
Alex Klein1699fab2022-09-08 08:46:06 -0600266def Create(
267 input_proto: "image_pb2.CreateImageRequest",
268 output_proto: "image_pb2.CreateImageResult",
269 _config: "api_config.ApiConfig",
270):
271 """Build images.
Alex Klein56355682019-02-07 10:36:54 -0700272
Alex Klein1699fab2022-09-08 08:46:06 -0600273 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600274 input_proto: The input message.
275 output_proto: The output message.
276 _config: The API call config.
Alex Klein1699fab2022-09-08 08:46:06 -0600277 """
278 board = input_proto.build_target.name
Alex Klein56355682019-02-07 10:36:54 -0700279
Alex Klein1699fab2022-09-08 08:46:06 -0600280 # Build the base image if no images provided.
281 to_build = input_proto.image_types or [_BASE_ID]
Alex Klein56355682019-02-07 10:36:54 -0700282
Alex Klein1699fab2022-09-08 08:46:06 -0600283 image_types = _ParseImagesToCreate(to_build)
284 build_config = _ParseCreateBuildConfig(input_proto)
285 factory_build_config = build_config._replace(
286 symlink=LOCATION_FACTORY, output_dir_suffix=LOCATION_FACTORY
287 )
Alex Klein56355682019-02-07 10:36:54 -0700288
Alex Klein1699fab2022-09-08 08:46:06 -0600289 # Try building the core and factory images.
290 # Sorted isn't really necessary here, but it's much easier to test.
291 core_result = image.Build(
292 board, sorted(image_types.core_images), config=build_config
293 )
294 logging.debug("Core Result Images: %s", core_result.images)
Alex Klein56355682019-02-07 10:36:54 -0700295
Alex Klein1699fab2022-09-08 08:46:06 -0600296 factory_result = image.Build(
297 board, image_types.factory, config=factory_build_config
298 )
299 logging.debug("Factory Result Images: %s", factory_result.images)
Will Bradley29a49c22019-10-21 11:50:08 -0600300
Alex Klein1699fab2022-09-08 08:46:06 -0600301 # A successful run will have no images missing, will have run at least one
302 # of the two image sets, and neither attempt errored. The no error condition
303 # should be redundant with no missing images, but is cheap insurance.
304 all_built = core_result.all_built and factory_result.all_built
305 one_ran = core_result.build_run or factory_result.build_run
306 no_errors = not core_result.run_error and not factory_result.run_error
307 output_proto.success = success = all_built and one_ran and no_errors
Will Bradley29a49c22019-10-21 11:50:08 -0600308
Alex Klein1699fab2022-09-08 08:46:06 -0600309 if success:
310 # Success! We need to record the images we built in the output.
311 all_images = {**core_result.images, **factory_result.images}
312 for img_name, img_path in all_images.items():
313 _add_image_to_proto(
314 output_proto, img_path, _IMAGE_MAPPING[img_name], board
315 )
Alex Klein27978a42021-07-27 14:18:10 -0600316
Alex Klein1699fab2022-09-08 08:46:06 -0600317 # Build and record VMs as necessary.
318 for vm_type in image_types.vms:
319 is_test = vm_type in [_TEST_VM_ID, _TEST_GUEST_VM_ID]
320 img_type = _IMAGE_MAPPING[_TEST_ID if is_test else _BASE_ID]
321 img_dir = core_result.images[img_type].parent.resolve()
322 try:
323 if vm_type in [_BASE_GUEST_VM_ID, _TEST_GUEST_VM_ID]:
324 vm_path = image.CreateGuestVm(
325 board, is_test=is_test, image_dir=img_dir
326 )
327 else:
328 vm_path = image.CreateVm(
329 board,
330 disk_layout=build_config.disk_layout,
331 is_test=is_test,
332 image_dir=img_dir,
333 )
334 except image.ImageToVmError as e:
335 cros_build_lib.Die(e)
Will Bradley29a49c22019-10-21 11:50:08 -0600336
Alex Klein1699fab2022-09-08 08:46:06 -0600337 _add_image_to_proto(output_proto, vm_path, vm_type, board)
George Engelbrecht9f4f8322021-03-08 12:04:17 -0700338
Alex Klein1699fab2022-09-08 08:46:06 -0600339 # Build and record any mod images.
340 for mod_type in image_types.mod_images:
341 if mod_type == _RECOVERY_ID:
342 base_image_path = core_result.images[constants.IMAGE_TYPE_BASE]
343 # For ChromeOS Flex special case.
344 if build_config.base_is_recovery:
345 result = image.CopyBaseToRecovery(
346 board=board, image_path=base_image_path
347 )
348 else:
349 result = image.BuildRecoveryImage(
350 board=board, image_path=base_image_path
351 )
352 if result.all_built:
353 _add_image_to_proto(
354 output_proto,
355 result.images[_IMAGE_MAPPING[mod_type]],
356 mod_type,
357 board,
358 )
359 else:
360 cros_build_lib.Die("Failed to create recovery image.")
Jack Neus70490b52022-09-12 14:41:45 +0000361 elif mod_type == _NETBOOT_ID:
Madeleine Hardtf0a92fc2022-09-19 18:41:12 +0000362 factory_shim_dir = os.path.dirname(
Jack Neus70490b52022-09-12 14:41:45 +0000363 factory_result.images[constants.IMAGE_TYPE_FACTORY_SHIM]
364 )
Alex Klein857b53d2022-10-21 13:53:26 -0600365 try:
366 image.create_netboot_kernel(board, factory_shim_dir)
367 except cros_build_lib.RunCommandError as e:
368 logging.warning(e)
Alex Klein1699fab2022-09-08 08:46:06 -0600369 else:
Jack Neus70490b52022-09-12 14:41:45 +0000370 cros_build_lib.Die(
371 "_RECOVERY_ID and _NETBOOT_ID are the only mod_image_type."
372 )
Will Bradley29a49c22019-10-21 11:50:08 -0600373
Alex Klein1699fab2022-09-08 08:46:06 -0600374 # Read metric events log and pipe them into output_proto.events.
Alex Kleina0421f32022-09-12 14:39:29 -0600375 if core_result.build_run and core_result.output_dir:
376 _parse_img_metrics_to_response(
377 output_proto, board, core_result.output_dir
378 )
Alex Klein1699fab2022-09-08 08:46:06 -0600379 return controller.RETURN_CODE_SUCCESS
Will Bradley29a49c22019-10-21 11:50:08 -0600380
Alex Klein1699fab2022-09-08 08:46:06 -0600381 else:
382 # Failure, include all of the failed packages in the output when available.
383 packages = core_result.failed_packages + factory_result.failed_packages
384 if not packages:
385 return controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY
Alex Klein2557b4f2019-07-11 14:34:00 -0600386
Alex Klein1699fab2022-09-08 08:46:06 -0600387 for package in packages:
388 current = output_proto.failed_packages.add()
389 controller_util.serialize_package_info(package, current)
Alex Klein1bcd9882019-03-19 13:25:24 -0600390
Alex Klein1699fab2022-09-08 08:46:06 -0600391 return controller.RETURN_CODE_UNSUCCESSFUL_RESPONSE_AVAILABLE
Alex Klein1bcd9882019-03-19 13:25:24 -0600392
Alex Kleincaace392021-07-26 14:28:13 -0600393
Alex Kleina0421f32022-09-12 14:39:29 -0600394def _parse_img_metrics_to_response(
395 output: "image_pb2.CreateImageResult", board: str, build_path: Path
396):
397 """Manually translate the package sizes file to the metrics events.
398
399 This is a temporary hack to manually translate the package sizes file to
400 the metrics output because the metrics library does not reliably transmit
401 the data. This can be removed once we switch over to the new metrics
402 pipeline.
403 """
404 filename = build_path / f"{constants.BASE_IMAGE_BIN}-package-sizes.json"
405 if not filename.exists():
406 logging.error("Package sizes file does not exist.")
407 return
408
409 size_data = json.loads(filename.read_text(encoding="utf-8"))
410
411 ts = int(round(time.time() * 1000))
412
413 # Total size event.
414 event = output.events.add()
415 event.gauge = size_data["total_size"]
416 event.timestamp_milliseconds = ts
417 event.name = f"{board}.total_size.base.rootfs"
418
419 # Package sizes.
420 for pkg, size in size_data["package_sizes"].items():
421 event = output.events.add()
422 event.gauge = size
423 event.timestamp_milliseconds = ts
424 event.name = f"{board}.package_size.base.rootfs.{pkg}"
425
426
Alex Klein27978a42021-07-27 14:18:10 -0600427def _ParseImagesToCreate(to_build: List[int]) -> ImageTypes:
Alex Klein1699fab2022-09-08 08:46:06 -0600428 """Helper function to parse the image types to build.
Alex Klein21b95022019-05-09 14:14:46 -0600429
Alex Klein1699fab2022-09-08 08:46:06 -0600430 This function expresses the dependencies of each image type and adds
431 the requisite image types if they're not explicitly defined.
Alex Klein21b95022019-05-09 14:14:46 -0600432
Alex Klein1699fab2022-09-08 08:46:06 -0600433 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600434 to_build: The image type list.
Alex Klein21b95022019-05-09 14:14:46 -0600435
Alex Klein1699fab2022-09-08 08:46:06 -0600436 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -0600437 ImageTypes: The parsed images to build.
Alex Klein1699fab2022-09-08 08:46:06 -0600438 """
439 image_types = set()
440 vm_types = set()
441 mod_image_types = set()
442 for current in to_build:
443 # Find out if it's a special case (vm, img mod), or just any old image.
444 if current in _VM_IMAGE_MAPPING:
445 vm_types.add(current)
446 # Make sure we build the image required to build the VM.
447 image_types.add(_VM_IMAGE_MAPPING[current])
448 elif current in _MOD_IMAGE_MAPPING:
449 mod_image_types.add(current)
450 image_types.add(_MOD_IMAGE_MAPPING[current])
451 elif current in _IMAGE_MAPPING:
452 image_types.add(_IMAGE_MAPPING[current])
453 else:
454 # Not expected, but at least it will be obvious if this comes up.
455 cros_build_lib.Die(
Alex Kleindf8ee502022-10-18 09:48:15 -0600456 "The service's known image types do not match those in "
457 "image.proto. Unknown Enum ID: %s",
458 current,
Alex Klein1699fab2022-09-08 08:46:06 -0600459 )
Alex Klein21b95022019-05-09 14:14:46 -0600460
Alex Klein1699fab2022-09-08 08:46:06 -0600461 # We can only build one type of these images at a time since image_to_vm.sh
462 # uses the default path if a name is not provided.
463 if vm_types.issuperset({_BASE_VM_ID, _TEST_VM_ID}):
464 cros_build_lib.Die("Cannot create more than one VM.")
Alex Klein21b95022019-05-09 14:14:46 -0600465
Alex Klein1699fab2022-09-08 08:46:06 -0600466 return ImageTypes(
467 images=image_types, vms=vm_types, mod_images=mod_image_types
468 )
Alex Klein21b95022019-05-09 14:14:46 -0600469
470
471def _ParseCreateBuildConfig(input_proto):
Alex Klein1699fab2022-09-08 08:46:06 -0600472 """Helper to parse the image build config for Create."""
473 enable_rootfs_verification = not input_proto.disable_rootfs_verification
474 version = input_proto.version or None
475 disk_layout = input_proto.disk_layout or None
476 builder_path = input_proto.builder_path or None
477 base_is_recovery = input_proto.base_is_recovery or False
478 return image.BuildConfig(
479 enable_rootfs_verification=enable_rootfs_verification,
480 replace=True,
481 version=version,
482 disk_layout=disk_layout,
483 builder_path=builder_path,
484 symlink=LOCATION_CORE,
485 base_is_recovery=base_is_recovery,
486 )
Alex Klein21b95022019-05-09 14:14:46 -0600487
Alex Klein1bcd9882019-03-19 13:25:24 -0600488
Alex Klein857b53d2022-10-21 13:53:26 -0600489@faux.all_empty
490@validate.require("build_target.name")
491@validate.validation_complete
492def CreateNetboot(input_proto, _output_proto, _config):
493 """Create a netboot kernel.
494
495 The netboot kernel currently needs network access because it's not building
496 everything in build_packages like other images. Once that has been remedied,
497 using Create to build the netboot kernel will be the expected workflow, and
498 this endpoint will be deprecated (b/255397725).
499 """
500 build_target = controller_util.ParseBuildTarget(input_proto.build_target)
501 if input_proto.factory_shim_path:
502 factory_shim_location = Path(input_proto.factory_shim_path).parent
503 else:
504 factory_shim_location = Path(
505 image_lib.GetLatestImageLink(
506 build_target.name, pointer=LOCATION_FACTORY
507 )
508 )
509 if not factory_shim_location.exists():
510 logging.warning(
511 "Factory shim directory does not exist. Skipping netboot creation."
512 )
513 return
514
515 image.create_netboot_kernel(build_target.name, str(factory_shim_location))
516
517
Michael Mortensen10146cf2019-11-19 19:59:22 -0700518def _SignerTestResponse(_input_proto, output_proto, _config):
Alex Klein1699fab2022-09-08 08:46:06 -0600519 """Set output_proto success field on a successful SignerTest response."""
520 output_proto.success = True
521 return controller.RETURN_CODE_SUCCESS
Michael Mortensen10146cf2019-11-19 19:59:22 -0700522
523
524@faux.success(_SignerTestResponse)
Michael Mortensen85d38402019-12-12 09:50:29 -0700525@faux.empty_completed_unsuccessfully_error
Alex Klein1699fab2022-09-08 08:46:06 -0600526@validate.exists("image.path")
Alex Klein231d2da2019-07-22 16:44:45 -0600527@validate.validation_complete
Alex Klein1699fab2022-09-08 08:46:06 -0600528def SignerTest(
529 input_proto: "image_pb2.ImageTestRequest",
530 output_proto: "image_pb2.ImageTestRequest",
531 _config: "api_config.ApiConfig",
532):
533 """Run image tests.
Michael Mortensenc83c9952019-08-05 12:15:12 -0600534
Alex Klein1699fab2022-09-08 08:46:06 -0600535 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600536 input_proto: The input message.
537 output_proto: The output message.
538 _config: The API call config.
Alex Klein1699fab2022-09-08 08:46:06 -0600539 """
540 image_path = input_proto.image.path
Michael Mortensenc83c9952019-08-05 12:15:12 -0600541
Alex Klein1699fab2022-09-08 08:46:06 -0600542 result = image_lib.SecurityTest(image=image_path)
543 output_proto.success = result
544 if result:
545 return controller.RETURN_CODE_SUCCESS
546 else:
547 return controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY
Michael Mortensenc83c9952019-08-05 12:15:12 -0600548
Alex Klein076841b2019-08-29 15:19:39 -0600549
Michael Mortensen10146cf2019-11-19 19:59:22 -0700550def _TestResponse(_input_proto, output_proto, _config):
Alex Klein1699fab2022-09-08 08:46:06 -0600551 """Set output_proto success field on a successful Test response."""
552 output_proto.success = True
553 return controller.RETURN_CODE_SUCCESS
Michael Mortensen10146cf2019-11-19 19:59:22 -0700554
555
556@faux.success(_TestResponse)
Michael Mortensen85d38402019-12-12 09:50:29 -0700557@faux.empty_completed_unsuccessfully_error
Alex Klein1699fab2022-09-08 08:46:06 -0600558@validate.require("build_target.name", "result.directory")
559@validate.exists("image.path")
560def Test(
561 input_proto: "image_pb2.ImageTestRequest",
562 output_proto: "image_pb2.ImageTestResult",
563 config: "api_config.ApiConfig",
564):
565 """Run image tests.
Alex Klein2966e302019-01-17 13:29:38 -0700566
Alex Klein1699fab2022-09-08 08:46:06 -0600567 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600568 input_proto: The input message.
569 output_proto: The output message.
570 config: The API call config.
Alex Klein1699fab2022-09-08 08:46:06 -0600571 """
572 image_path = input_proto.image.path
573 board = input_proto.build_target.name
574 result_directory = input_proto.result.directory
Alex Klein2966e302019-01-17 13:29:38 -0700575
Alex Klein1699fab2022-09-08 08:46:06 -0600576 if not os.path.isfile(image_path) or not image_path.endswith(".bin"):
577 cros_build_lib.Die(
578 "The image.path must be an existing image file with a .bin extension."
579 )
Alex Klein2966e302019-01-17 13:29:38 -0700580
Alex Klein1699fab2022-09-08 08:46:06 -0600581 if config.validate_only:
582 return controller.RETURN_CODE_VALID_INPUT
Alex Klein231d2da2019-07-22 16:44:45 -0600583
Alex Klein1699fab2022-09-08 08:46:06 -0600584 success = image.Test(board, result_directory, image_dir=image_path)
585 output_proto.success = success
Alex Klein8cb365a2019-05-15 16:24:53 -0600586
Alex Klein1699fab2022-09-08 08:46:06 -0600587 if success:
588 return controller.RETURN_CODE_SUCCESS
589 else:
590 return controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY
Jack Neus761e1842020-12-01 18:20:11 +0000591
592
593@faux.empty_success
594@faux.empty_completed_unsuccessfully_error
Alex Klein1699fab2022-09-08 08:46:06 -0600595@validate.require("gs_image_dir", "sysroot.build_target.name")
596def PushImage(
597 input_proto: "image_pb2.PushImageRequest",
598 _output_proto: "image_pb2.PushImageResponse",
599 config: "api.config.ApiConfig",
600):
601 """Push artifacts from the archive bucket to the release bucket.
Jack Neus761e1842020-12-01 18:20:11 +0000602
Alex Klein1699fab2022-09-08 08:46:06 -0600603 Wraps chromite/scripts/pushimage.py.
Jack Neus761e1842020-12-01 18:20:11 +0000604
Alex Klein1699fab2022-09-08 08:46:06 -0600605 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600606 input_proto: Input proto.
607 _output_proto: Output proto.
608 config: The API call config.
Jack Neus761e1842020-12-01 18:20:11 +0000609
Alex Klein1699fab2022-09-08 08:46:06 -0600610 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -0600611 A controller return code (e.g. controller.RETURN_CODE_SUCCESS).
Alex Klein1699fab2022-09-08 08:46:06 -0600612 """
613 sign_types = []
614 if input_proto.sign_types:
615 for sign_type in input_proto.sign_types:
616 if sign_type not in SUPPORTED_IMAGE_TYPES:
617 logging.error("unsupported sign type %g", sign_type)
618 return controller.RETURN_CODE_INVALID_INPUT
619 sign_types.append(SUPPORTED_IMAGE_TYPES[sign_type])
Jack Neus761e1842020-12-01 18:20:11 +0000620
Alex Klein1699fab2022-09-08 08:46:06 -0600621 # If configured for validation only we're done here.
622 if config.validate_only:
623 return controller.RETURN_CODE_VALID_INPUT
Jack Neus761e1842020-12-01 18:20:11 +0000624
Alex Klein1699fab2022-09-08 08:46:06 -0600625 kwargs = {}
626 if input_proto.profile.name:
627 kwargs["profile"] = input_proto.profile.name
628 if input_proto.dest_bucket:
629 kwargs["dest_bucket"] = input_proto.dest_bucket
630 if input_proto.channels:
631 kwargs["force_channels"] = [
632 common_pb2.Channel.Name(channel).lower()[len("channel_") :]
633 for channel in input_proto.channels
634 ]
635 try:
636 channel_to_uris = pushimage.PushImage(
637 input_proto.gs_image_dir,
638 input_proto.sysroot.build_target.name,
639 dry_run=input_proto.dryrun,
640 sign_types=sign_types,
641 **kwargs,
642 )
643 except Exception:
644 logging.error("PushImage failed: ", exc_info=True)
645 return controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY
646 if channel_to_uris:
647 for uris in channel_to_uris.values():
648 for uri in uris:
649 _output_proto.instructions.add().instructions_file_path = uri
650 return controller.RETURN_CODE_SUCCESS