| # Copyright 2018 The ChromiumOS Authors | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| """Image API Service. | |
| The image related API endpoints should generally be found here. | |
| """ | |
| import functools | |
| import json | |
| import logging | |
| import os | |
| from pathlib import Path | |
| import time | |
| import traceback | |
| from typing import List, NamedTuple, Set, TYPE_CHECKING, Union | |
| from chromite.api import controller | |
| from chromite.api import faux | |
| from chromite.api import validate | |
| from chromite.api.controller import controller_util | |
| from chromite.api.gen.chromiumos import common_pb2 | |
| from chromite.lib import build_target_lib | |
| from chromite.lib import chroot_lib | |
| from chromite.lib import constants | |
| from chromite.lib import cros_build_lib | |
| from chromite.lib import image_lib | |
| from chromite.lib import sysroot_lib | |
| from chromite.scripts import pushimage | |
| from chromite.service import image | |
| from chromite.service import packages as packages_service | |
| from chromite.utils import timer | |
| if TYPE_CHECKING: | |
| from chromite.api import api_config | |
| from chromite.api.gen.chromite.api import image_pb2 | |
| # The image.proto ImageType enum ids. | |
| _BASE_ID = common_pb2.IMAGE_TYPE_BASE | |
| _DEV_ID = common_pb2.IMAGE_TYPE_DEV | |
| _TEST_ID = common_pb2.IMAGE_TYPE_TEST | |
| _BASE_VM_ID = common_pb2.IMAGE_TYPE_BASE_VM | |
| _TEST_VM_ID = common_pb2.IMAGE_TYPE_TEST_VM | |
| _RECOVERY_ID = common_pb2.IMAGE_TYPE_RECOVERY | |
| _FACTORY_ID = common_pb2.IMAGE_TYPE_FACTORY | |
| _FIRMWARE_ID = common_pb2.IMAGE_TYPE_FIRMWARE | |
| _BASE_GUEST_VM_ID = common_pb2.IMAGE_TYPE_BASE_GUEST_VM | |
| _TEST_GUEST_VM_ID = common_pb2.IMAGE_TYPE_TEST_GUEST_VM | |
| _NETBOOT_ID = common_pb2.IMAGE_TYPE_NETBOOT | |
| # Dict to allow easily translating names to enum ids and vice versa. | |
| _IMAGE_MAPPING = { | |
| _BASE_ID: constants.IMAGE_TYPE_BASE, | |
| constants.IMAGE_TYPE_BASE: _BASE_ID, | |
| _DEV_ID: constants.IMAGE_TYPE_DEV, | |
| constants.IMAGE_TYPE_DEV: _DEV_ID, | |
| _TEST_ID: constants.IMAGE_TYPE_TEST, | |
| constants.IMAGE_TYPE_TEST: _TEST_ID, | |
| _RECOVERY_ID: constants.IMAGE_TYPE_RECOVERY, | |
| constants.IMAGE_TYPE_RECOVERY: _RECOVERY_ID, | |
| _FACTORY_ID: constants.IMAGE_TYPE_FACTORY_SHIM, | |
| constants.IMAGE_TYPE_FACTORY_SHIM: _FACTORY_ID, | |
| _FIRMWARE_ID: constants.IMAGE_TYPE_FIRMWARE, | |
| constants.IMAGE_TYPE_FIRMWARE: _FIRMWARE_ID, | |
| _NETBOOT_ID: constants.IMAGE_TYPE_NETBOOT, | |
| constants.IMAGE_TYPE_NETBOOT: _NETBOOT_ID, | |
| } | |
| # Dict to describe the prerequisite built images for each VM image type. | |
| _VM_IMAGE_MAPPING = { | |
| _BASE_VM_ID: _IMAGE_MAPPING[_BASE_ID], | |
| _TEST_VM_ID: _IMAGE_MAPPING[_TEST_ID], | |
| _BASE_GUEST_VM_ID: _IMAGE_MAPPING[_BASE_ID], | |
| _TEST_GUEST_VM_ID: _IMAGE_MAPPING[_TEST_ID], | |
| } | |
| # Dict to describe the prerequisite built images for each mod image type. | |
| _MOD_IMAGE_MAPPING = { | |
| _RECOVERY_ID: _IMAGE_MAPPING[_BASE_ID], | |
| _NETBOOT_ID: _IMAGE_MAPPING[_FACTORY_ID], | |
| } | |
| # Supported image types for PushImage. | |
| SUPPORTED_IMAGE_TYPES = { | |
| common_pb2.IMAGE_TYPE_RECOVERY: constants.IMAGE_TYPE_RECOVERY, | |
| common_pb2.IMAGE_TYPE_FACTORY: constants.IMAGE_TYPE_FACTORY, | |
| common_pb2.IMAGE_TYPE_FIRMWARE: constants.IMAGE_TYPE_FIRMWARE, | |
| common_pb2.IMAGE_TYPE_ACCESSORY_USBPD: constants.IMAGE_TYPE_ACCESSORY_USBPD, | |
| common_pb2.IMAGE_TYPE_ACCESSORY_RWSIG: constants.IMAGE_TYPE_ACCESSORY_RWSIG, | |
| common_pb2.IMAGE_TYPE_HPS_FIRMWARE: constants.IMAGE_TYPE_HPS_FIRMWARE, | |
| common_pb2.IMAGE_TYPE_BASE: constants.IMAGE_TYPE_BASE, | |
| common_pb2.IMAGE_TYPE_GSC_FIRMWARE: constants.IMAGE_TYPE_GSC_FIRMWARE, | |
| } | |
| # Built image directory symlink names. These names allow specifying a static | |
| # location for creation to simplify later archival stages. In practice, this | |
| # sets the symlink argument to build_packages. | |
| # Core are the build/dev/test images. | |
| # Use "latest" until we do a better job of passing through image directories, | |
| # e.g. for artifacts. | |
| LOCATION_CORE = "latest" | |
| # The factory_install image. | |
| LOCATION_FACTORY = "factory_shim" | |
| class ImageTypes(NamedTuple): | |
| """Parsed image types.""" | |
| images: Set[str] | |
| vms: Set[int] | |
| mod_images: Set[int] | |
| @property | |
| def core_images(self) -> List[str]: | |
| """The core images (base/dev/test) as a list.""" | |
| return list(self.images - {_IMAGE_MAPPING[_FACTORY_ID]}) or [] | |
| @property | |
| def has_factory(self) -> bool: | |
| """Whether the factory image is present.""" | |
| return _IMAGE_MAPPING[_FACTORY_ID] in self.images | |
| @property | |
| def factory(self) -> List[str]: | |
| """A list with the factory type if set.""" | |
| return [_IMAGE_MAPPING[_FACTORY_ID]] if self.has_factory else [] | |
| def _add_image_to_proto( | |
| output_proto, path: Union["Path", str], image_type: int, board: str | |
| ): | |
| """Quick helper function to add a new image to the output proto.""" | |
| new_image = output_proto.images.add() | |
| new_image.path = str(path) | |
| new_image.type = image_type | |
| new_image.build_target.name = board | |
| def ExampleGetResponse(): | |
| """Give an example response to assemble upstream in caller artifacts.""" | |
| uabs = common_pb2.UploadedArtifactsByService | |
| cabs = common_pb2.ArtifactsByService | |
| return uabs.Sysroot( | |
| artifacts=[ | |
| uabs.Image.ArtifactPaths( | |
| artifact_type=cabs.Image.ArtifactType.DLC_IMAGE, | |
| paths=[ | |
| common_pb2.Path( | |
| path="/tmp/dlc/dlc.img", | |
| location=common_pb2.Path.OUTSIDE, | |
| ) | |
| ], | |
| ) | |
| ] | |
| ) | |
| def GetArtifacts( | |
| in_proto: common_pb2.ArtifactsByService.Image, | |
| chroot: chroot_lib.Chroot, | |
| sysroot_class: sysroot_lib.Sysroot, | |
| build_target: build_target_lib.BuildTarget, | |
| output_dir, | |
| ) -> list: | |
| """Builds and copies images to specified output_dir. | |
| Copies (after optionally bundling) all required images into the output_dir, | |
| returning a mapping of image type to a list of (output_dir) paths to | |
| the desired files. Note that currently it is only processing one image | |
| (DLC), but the future direction is to process all required images. Required | |
| images are located within output_artifact.artifact_type. | |
| Args: | |
| in_proto: Proto request defining reqs. | |
| chroot: The chroot proto used for these artifacts. | |
| sysroot_class: The sysroot proto used for these artifacts. | |
| build_target: The build target used for these artifacts. | |
| output_dir: The path to write artifacts to. | |
| Returns: | |
| A list of dictionary mappings of ArtifactType to list of paths. | |
| """ | |
| base_path = chroot.full_path(sysroot_class.path) | |
| board = build_target.name | |
| factory_shim_location = Path( | |
| image_lib.GetLatestImageLink(board, pointer=LOCATION_FACTORY) | |
| ) | |
| generated = [] | |
| dlc_func = functools.partial(image.copy_dlc_image, base_path) | |
| license_func = functools.partial( | |
| image.copy_license_credits, board, symlink=LOCATION_CORE | |
| ) | |
| factory_image_func = functools.partial( | |
| image.create_factory_image_zip, | |
| chroot, | |
| sysroot_class, | |
| factory_shim_location, | |
| packages_service.determine_full_version(), | |
| ) | |
| stripped_packags_func = functools.partial( | |
| image.create_stripped_packages_tar, | |
| chroot, | |
| build_target, | |
| ) | |
| image_scripts_func = functools.partial( | |
| image.create_image_scripts_archive, build_target | |
| ) | |
| artifact_types = { | |
| in_proto.ArtifactType.DLC_IMAGE: dlc_func, | |
| in_proto.ArtifactType.LICENSE_CREDITS: license_func, | |
| in_proto.ArtifactType.FACTORY_IMAGE: factory_image_func, | |
| in_proto.ArtifactType.STRIPPED_PACKAGES: stripped_packags_func, | |
| in_proto.ArtifactType.IMAGE_SCRIPTS: image_scripts_func, | |
| } | |
| for output_artifact in in_proto.output_artifacts: | |
| for artifact_type, func in artifact_types.items(): | |
| if artifact_type in output_artifact.artifact_types: | |
| try: | |
| result = func(output_dir) | |
| except Exception as e: | |
| generated.append( | |
| { | |
| "type": artifact_type, | |
| "failed": True, | |
| "failure_reason": str(e), | |
| } | |
| ) | |
| artifact_name = ( | |
| common_pb2.ArtifactsByService.Image.ArtifactType.Name( | |
| artifact_type | |
| ) | |
| ) | |
| logging.warning( | |
| "%s artifact generation failed with exception %s", | |
| artifact_name, | |
| e, | |
| ) | |
| logging.warning("traceback:\n%s", traceback.format_exc()) | |
| continue | |
| if result: | |
| generated.append( | |
| { | |
| "paths": [result] | |
| if isinstance(result, str) | |
| else result, | |
| "type": artifact_type, | |
| } | |
| ) | |
| return generated | |
| def _CreateResponse(_input_proto, output_proto, _config): | |
| """Set output_proto success field on a successful Create response.""" | |
| output_proto.success = True | |
| @faux.success(_CreateResponse) | |
| @faux.empty_completed_unsuccessfully_error | |
| @validate.require("build_target.name") | |
| @validate.validation_complete | |
| def Create( | |
| input_proto: "image_pb2.CreateImageRequest", | |
| output_proto: "image_pb2.CreateImageResult", | |
| _config: "api_config.ApiConfig", | |
| ): | |
| """Build images. | |
| Args: | |
| input_proto: The input message. | |
| output_proto: The output message. | |
| _config: The API call config. | |
| """ | |
| board = input_proto.build_target.name | |
| # Build the base image if no images provided. | |
| to_build = input_proto.image_types or [_BASE_ID] | |
| image_types = _ParseImagesToCreate(to_build) | |
| build_config = _ParseCreateBuildConfig(input_proto) | |
| factory_build_config = build_config._replace( | |
| symlink=LOCATION_FACTORY, output_dir_suffix=LOCATION_FACTORY | |
| ) | |
| # Try building the core and factory images. | |
| # Sorted isn't really necessary here, but it's much easier to test. | |
| with timer.timer("build-base-dev-test-timer"): | |
| core_result = image.Build( | |
| board, sorted(image_types.core_images), config=build_config | |
| ) | |
| logging.debug("Core Result Images: %s", core_result.images) | |
| with timer.timer("factory-timer"): | |
| factory_result = image.Build( | |
| board, image_types.factory, config=factory_build_config | |
| ) | |
| logging.debug("Factory Result Images: %s", factory_result.images) | |
| # A successful run will have no images missing, will have run at least one | |
| # of the two image sets, and neither attempt errored. The no error condition | |
| # should be redundant with no missing images, but is cheap insurance. | |
| all_built = core_result.all_built and factory_result.all_built | |
| one_ran = core_result.build_run or factory_result.build_run | |
| no_errors = not core_result.run_error and not factory_result.run_error | |
| output_proto.success = success = all_built and one_ran and no_errors | |
| if success: | |
| # Success! We need to record the images we built in the output. | |
| all_images = {**core_result.images, **factory_result.images} | |
| for img_name, img_path in all_images.items(): | |
| _add_image_to_proto( | |
| output_proto, img_path, _IMAGE_MAPPING[img_name], board | |
| ) | |
| # Build and record VMs as necessary. | |
| for vm_type in image_types.vms: | |
| is_test = vm_type in [_TEST_VM_ID, _TEST_GUEST_VM_ID] | |
| img_type = _IMAGE_MAPPING[_TEST_ID if is_test else _BASE_ID] | |
| img_dir = core_result.images[img_type].parent.resolve() | |
| with timer.timer(f"vm-image-{vm_type}-timer"): | |
| try: | |
| if vm_type in [_BASE_GUEST_VM_ID, _TEST_GUEST_VM_ID]: | |
| vm_path = image.CreateGuestVm( | |
| image_dir=img_dir, is_test=is_test | |
| ) | |
| else: | |
| vm_path = image.CreateVm( | |
| board, | |
| disk_layout=build_config.disk_layout, | |
| is_test=is_test, | |
| image_dir=img_dir, | |
| ) | |
| except image.ImageToVmError as e: | |
| cros_build_lib.Die(e) | |
| _add_image_to_proto(output_proto, vm_path, vm_type, board) | |
| # Build and record any mod images. | |
| for mod_type in image_types.mod_images: | |
| if mod_type == _RECOVERY_ID: | |
| base_image_path = core_result.images[constants.IMAGE_TYPE_BASE] | |
| # For ChromeOS Flex special case. | |
| with timer.timer("recovery-image-timer"): | |
| if build_config.base_is_recovery: | |
| result = image.CopyBaseToRecovery( | |
| board=board, image_path=base_image_path | |
| ) | |
| else: | |
| result = image.BuildRecoveryImage( | |
| board=board, image_path=base_image_path | |
| ) | |
| if result.all_built: | |
| _add_image_to_proto( | |
| output_proto, | |
| result.images[_IMAGE_MAPPING[mod_type]], | |
| mod_type, | |
| board, | |
| ) | |
| else: | |
| cros_build_lib.Die("Failed to create recovery image.") | |
| elif mod_type == _NETBOOT_ID: | |
| factory_shim_dir = os.path.dirname( | |
| factory_result.images[constants.IMAGE_TYPE_FACTORY_SHIM] | |
| ) | |
| with timer.timer("netboot-kernel-timer"): | |
| try: | |
| image.create_netboot_kernel(board, factory_shim_dir) | |
| except cros_build_lib.RunCommandError as e: | |
| logging.warning(e) | |
| else: | |
| cros_build_lib.Die( | |
| "_RECOVERY_ID and _NETBOOT_ID are the only mod_image_type." | |
| ) | |
| # Read metric events log and pipe them into output_proto.events. | |
| if core_result.build_run and core_result.output_dir: | |
| _parse_img_metrics_to_response( | |
| output_proto, board, core_result.output_dir | |
| ) | |
| return controller.RETURN_CODE_SUCCESS | |
| else: | |
| # Failure, include all the failed packages in the output when available. | |
| packages = core_result.failed_packages + factory_result.failed_packages | |
| if not packages: | |
| return controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY | |
| for package in packages: | |
| current = output_proto.failed_packages.add() | |
| controller_util.serialize_package_info(package, current) | |
| return controller.RETURN_CODE_UNSUCCESSFUL_RESPONSE_AVAILABLE | |
| def _parse_img_metrics_to_response( | |
| output: "image_pb2.CreateImageResult", board: str, build_path: Path | |
| ): | |
| """Manually translate the package sizes file to the metrics events. | |
| This is a temporary hack to manually translate the package sizes file to | |
| the metrics output because the metrics library does not reliably transmit | |
| the data. This can be removed once we switch over to the new metrics | |
| pipeline. | |
| """ | |
| filename = build_path / f"{constants.BASE_IMAGE_BIN}-package-sizes.json" | |
| if not filename.exists(): | |
| logging.error("Package sizes file does not exist.") | |
| return | |
| size_data = json.loads(filename.read_text(encoding="utf-8")) | |
| ts = int(round(time.time() * 1000)) | |
| # Total size event. | |
| event = output.events.add() | |
| event.gauge = size_data["total_size"] | |
| event.timestamp_milliseconds = ts | |
| event.name = f"{board}.total_size.base.rootfs" | |
| # Package sizes. | |
| for pkg, size in size_data["package_sizes"].items(): | |
| event = output.events.add() | |
| event.gauge = size | |
| event.timestamp_milliseconds = ts | |
| event.name = f"{board}.package_size.base.rootfs.{pkg}" | |
| def _ParseImagesToCreate(to_build: List[int]) -> ImageTypes: | |
| """Helper function to parse the image types to build. | |
| This function expresses the dependencies of each image type and adds | |
| the requisite image types if they're not explicitly defined. | |
| Args: | |
| to_build: The image type list. | |
| Returns: | |
| ImageTypes: The parsed images to build. | |
| """ | |
| image_types = set() | |
| vm_types = set() | |
| mod_image_types = set() | |
| for current in to_build: | |
| # Find out if it's a special case (vm, img mod), or just any old image. | |
| if current in _VM_IMAGE_MAPPING: | |
| vm_types.add(current) | |
| # Make sure we build the image required to build the VM. | |
| image_types.add(_VM_IMAGE_MAPPING[current]) | |
| elif current in _MOD_IMAGE_MAPPING: | |
| mod_image_types.add(current) | |
| image_types.add(_MOD_IMAGE_MAPPING[current]) | |
| elif current in _IMAGE_MAPPING: | |
| image_types.add(_IMAGE_MAPPING[current]) | |
| else: | |
| # Not expected, but at least it will be obvious if this comes up. | |
| cros_build_lib.Die( | |
| "The service's known image types do not match those in " | |
| "image.proto. Unknown Enum ID: %s", | |
| current, | |
| ) | |
| # We can only build one type of these images at a time since image_to_vm.sh | |
| # uses the default path if a name is not provided. | |
| if vm_types.issuperset({_BASE_VM_ID, _TEST_VM_ID}): | |
| cros_build_lib.Die("Cannot create more than one VM.") | |
| return ImageTypes( | |
| images=image_types, vms=vm_types, mod_images=mod_image_types | |
| ) | |
| def _ParseCreateBuildConfig(input_proto): | |
| """Helper to parse the image build config for Create.""" | |
| enable_rootfs_verification = not input_proto.disable_rootfs_verification | |
| version = input_proto.version or None | |
| disk_layout = input_proto.disk_layout or None | |
| builder_path = input_proto.builder_path or None | |
| base_is_recovery = input_proto.base_is_recovery or False | |
| return image.BuildConfig( | |
| enable_rootfs_verification=enable_rootfs_verification, | |
| replace=True, | |
| version=version, | |
| disk_layout=disk_layout, | |
| builder_path=builder_path, | |
| symlink=LOCATION_CORE, | |
| base_is_recovery=base_is_recovery, | |
| ) | |
| @faux.all_empty | |
| @validate.require("build_target.name") | |
| @validate.validation_complete | |
| def CreateNetboot(input_proto, _output_proto, _config): | |
| """Create a netboot kernel. | |
| The netboot kernel currently needs network access because it's not building | |
| everything in build_packages like other images. Once that has been remedied, | |
| using Create to build the netboot kernel will be the expected workflow, and | |
| this endpoint will be deprecated (b/255397725). | |
| """ | |
| build_target = controller_util.ParseBuildTarget(input_proto.build_target) | |
| if input_proto.factory_shim_path: | |
| factory_shim_location = Path(input_proto.factory_shim_path).parent | |
| else: | |
| factory_shim_location = Path( | |
| image_lib.GetLatestImageLink( | |
| build_target.name, pointer=LOCATION_FACTORY | |
| ) | |
| ) | |
| if not factory_shim_location.exists(): | |
| logging.warning( | |
| "Factory shim directory does not exist. Skipping netboot creation." | |
| ) | |
| return | |
| image.create_netboot_kernel(build_target.name, str(factory_shim_location)) | |
| def _SignerTestResponse(_input_proto, output_proto, _config): | |
| """Set output_proto success field on a successful SignerTest response.""" | |
| output_proto.success = True | |
| return controller.RETURN_CODE_SUCCESS | |
| @faux.success(_SignerTestResponse) | |
| @faux.empty_completed_unsuccessfully_error | |
| @validate.exists("image.path") | |
| @validate.validation_complete | |
| def SignerTest( | |
| input_proto: "image_pb2.ImageTestRequest", | |
| output_proto: "image_pb2.ImageTestRequest", | |
| _config: "api_config.ApiConfig", | |
| ): | |
| """Run image tests. | |
| Args: | |
| input_proto: The input message. | |
| output_proto: The output message. | |
| _config: The API call config. | |
| """ | |
| image_path = input_proto.image.path | |
| result = image_lib.SecurityTest(image=image_path) | |
| output_proto.success = result | |
| if result: | |
| return controller.RETURN_CODE_SUCCESS | |
| else: | |
| return controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY | |
| def _TestResponse(_input_proto, output_proto, _config): | |
| """Set output_proto success field on a successful Test response.""" | |
| output_proto.success = True | |
| return controller.RETURN_CODE_SUCCESS | |
| @faux.success(_TestResponse) | |
| @faux.empty_completed_unsuccessfully_error | |
| @validate.require("build_target.name", "result.directory") | |
| @validate.exists("image.path") | |
| def Test( | |
| input_proto: "image_pb2.ImageTestRequest", | |
| output_proto: "image_pb2.ImageTestResult", | |
| config: "api_config.ApiConfig", | |
| ): | |
| """Run image tests. | |
| Args: | |
| input_proto: The input message. | |
| output_proto: The output message. | |
| config: The API call config. | |
| """ | |
| image_path = input_proto.image.path | |
| board = input_proto.build_target.name | |
| result_directory = input_proto.result.directory | |
| if not os.path.isfile(image_path) or not image_path.endswith(".bin"): | |
| cros_build_lib.Die( | |
| "The image.path must be an existing image file with a .bin " | |
| "extension." | |
| ) | |
| if config.validate_only: | |
| return controller.RETURN_CODE_VALID_INPUT | |
| success = image.Test(board, result_directory, image_dir=image_path) | |
| output_proto.success = success | |
| if success: | |
| return controller.RETURN_CODE_SUCCESS | |
| else: | |
| return controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY | |
| @faux.empty_success | |
| @faux.empty_completed_unsuccessfully_error | |
| @validate.require("gs_image_dir", "sysroot.build_target.name") | |
| def PushImage( | |
| input_proto: "image_pb2.PushImageRequest", | |
| _output_proto: "image_pb2.PushImageResponse", | |
| config: "api.config.ApiConfig", | |
| ): | |
| """Push artifacts from the archive bucket to the release bucket. | |
| Wraps chromite/scripts/pushimage.py. | |
| Args: | |
| input_proto: Input proto. | |
| _output_proto: Output proto. | |
| config: The API call config. | |
| Returns: | |
| A controller return code (e.g. controller.RETURN_CODE_SUCCESS). | |
| """ | |
| sign_types = [] | |
| if input_proto.sign_types: | |
| for sign_type in input_proto.sign_types: | |
| if sign_type not in SUPPORTED_IMAGE_TYPES: | |
| logging.error("unsupported sign type %g", sign_type) | |
| return controller.RETURN_CODE_INVALID_INPUT | |
| sign_types.append(SUPPORTED_IMAGE_TYPES[sign_type]) | |
| # If configured for validation only we're done here. | |
| if config.validate_only: | |
| return controller.RETURN_CODE_VALID_INPUT | |
| kwargs = {} | |
| if input_proto.profile.name: | |
| kwargs["profile"] = input_proto.profile.name | |
| if input_proto.dest_bucket: | |
| kwargs["dest_bucket"] = input_proto.dest_bucket | |
| if input_proto.channels: | |
| kwargs["force_channels"] = [ | |
| common_pb2.Channel.Name(channel).lower()[len("channel_") :] | |
| for channel in input_proto.channels | |
| ] | |
| try: | |
| channel_to_uris = pushimage.PushImage( | |
| input_proto.gs_image_dir, | |
| input_proto.sysroot.build_target.name, | |
| dryrun=input_proto.dryrun, | |
| sign_types=sign_types, | |
| **kwargs, | |
| ) | |
| except Exception: | |
| logging.error("PushImage failed: ", exc_info=True) | |
| return controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY | |
| if channel_to_uris: | |
| for uris in channel_to_uris.values(): | |
| for uri in uris: | |
| _output_proto.instructions.add().instructions_file_path = uri | |
| return controller.RETURN_CODE_SUCCESS | |
| @faux.all_empty | |
| @validate.validation_complete | |
| def SignImage( | |
| _input_proto: "image_pb2.SignImageRequest", | |
| _output_proto: "image_pb2.SignImageResponse", | |
| _config: "api.config.ApiConfig", | |
| ): | |
| """Sign artifacts based on the given config. | |
| Args: | |
| input_proto: Input proto. | |
| _output_proto: Output proto. | |
| config: The API call config. | |
| Returns: | |
| A controller return code (e.g. controller.RETURN_CODE_SUCCESS). | |
| """ | |
| return controller.RETURN_CODE_SUCCESS |