blob: 35aaafbf7e068d64b58626cd3a28e3877360bf1e [file] [log] [blame]
# Copyright 2019 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""The payload API is the entry point for payload functionality."""
from copy import deepcopy
import re
from typing import Dict, Optional, Tuple, Union
from chromite.api.gen.chromite.api import payload_pb2
from chromite.api.gen.chromiumos import common_pb2
from chromite.lib import chroot_util
from chromite.lib.paygen import gspaths
from chromite.lib.paygen import paygen_build_lib
from chromite.lib.paygen import paygen_payload_lib
class Error(Exception):
"""Base module error."""
class ImageTypeUnknownError(Error):
"""An error raised when image type is unknown."""
class ImageMismatchError(Error):
"""An error raised when src and tgt aren't compatible."""
class PayloadConfig(object):
"""Value object to hold the GeneratePayload configuration options."""
def __init__(
self,
tgt_image: Optional[
Union[
payload_pb2.UnsignedImage,
payload_pb2.SignedImage,
payload_pb2.DLCImage,
]
] = None,
src_image: Optional[
Union[
payload_pb2.UnsignedImage,
payload_pb2.SignedImage,
payload_pb2.DLCImage,
]
] = None,
dest_bucket: Optional[str] = None,
minios: bool = False,
verify: bool = True,
upload: bool = True,
cache_dir: Optional[str] = None,
):
"""Init method, sets up all the paths and configuration.
Args:
tgt_image: Proto for destination image.
src_image: Proto for source image.
dest_bucket: Destination bucket to place the final artifacts in.
minios: Whether the payload is for the image's miniOS partition.
verify: If delta is made, verify the integrity of the payload.
upload: Whether the payload generation results should be uploaded.
cache_dir: The cache dir for paygen to use or None for default.
"""
# Set when we call GeneratePayload on this object.
self.paygen = None
self.tgt_image = tgt_image
self.src_image = src_image
self.dest_bucket = dest_bucket
self.minios = minios
self.verify = verify
self.upload = upload
self.delta_type = "delta" if self.src_image else "full"
self.image_type = _ImageTypeToStr(tgt_image.image_type)
self.cache_dir = cache_dir
# This block ensures that we have paths to the correct perm of images.
src_image_path = None
if isinstance(self.tgt_image, payload_pb2.UnsignedImage):
tgt_image_path = _GenUnsignedGSPath(
self.tgt_image, self.image_type, self.minios
)
elif isinstance(self.tgt_image, payload_pb2.SignedImage):
tgt_image_path = _GenSignedGSPath(
self.tgt_image, self.image_type, self.minios
)
elif isinstance(self.tgt_image, payload_pb2.DLCImage):
tgt_image_path = _GenDLCImageGSPath(self.tgt_image)
if self.delta_type == "delta":
if isinstance(self.tgt_image, payload_pb2.UnsignedImage):
src_image_path = _GenUnsignedGSPath(
self.src_image, self.image_type, self.minios
)
if isinstance(self.tgt_image, payload_pb2.SignedImage):
src_image_path = _GenSignedGSPath(
self.src_image, self.image_type, self.minios
)
elif isinstance(self.tgt_image, payload_pb2.DLCImage):
src_image_path = _GenDLCImageGSPath(self.src_image)
payload_build = deepcopy(tgt_image_path.build)
payload_build.bucket = dest_bucket
self.payload = gspaths.Payload(
build=payload_build,
tgt_image=tgt_image_path,
src_image=src_image_path,
minios=self.minios,
uri=None,
)
if self.upload:
self.payload.uri = paygen_build_lib.DefaultPayloadUri(self.payload)
def GeneratePayload(self) -> Dict[int, Tuple[str, str]]:
"""Do payload generation (& maybe sign) on Google Storage CrOS images.
Returns:
A dict containing tuples of the following format:
The location of the local generated artifact.
(e.g. /tmp/wdjaio/delta.bin)
The remote location that the payload was uploaded or None.
(e.g. 'gs://cr/beta-channel/coral/12345.0.1/payloads/...')
Keyed by a version number.
Raises:
paygen_payload_lib.PayloadGenerationSkippedException: If paygen was
skipped for any reason.
"""
# Leave the generated artifact local. This is ok because if we're
# testing it's likely we want the artifact anyway, and in production
# this is ran on single shot bots in the context of an overlayfs and
# will get cleaned up anyway.
with chroot_util.TempDirInChroot(delete=False) as temp_dir:
signer = paygen_payload_lib.PaygenSigner(
work_dir=temp_dir, payload_build=self.payload.build
)
self.paygen = paygen_payload_lib.PaygenPayload(
self.payload,
temp_dir,
signer=signer,
verify=self.verify,
upload=self.upload,
cache_dir=self.cache_dir,
)
# The return from paygen will look like:
# {
# 1: (local_path, remote_uri),
# 2: (local_path, remote_uri),
# ...
# }
return self.paygen.Run()
def _ImageTypeToStr(image_type_n: int) -> str:
"""The numeral image type enum in proto to lowercase string."""
ret = common_pb2.ImageType.Name(image_type_n).lower()
return re.sub("^image_type_", "", ret)
def _GenSignedGSPath(
image: payload_pb2.SignedImage, image_type: str, minios: bool
) -> gspaths.Image:
"""Take a SignedImage_pb2 and return a gspaths.Image.
Args:
image: The build to create the gspath from.
image_type: The image type, either "recovery" or "base".
minios: Whether or not it's a miniOS image.
Returns:
A gspaths.Image instance.
"""
build = gspaths.Build(
board=image.build.build_target.name,
version=image.build.version,
channel=image.build.channel,
bucket=image.build.bucket,
)
build_uri = gspaths.ChromeosReleases.ImageUri(build, image.key, image_type)
build.uri = build_uri
if minios:
return gspaths.MiniOSImage(
build=build, image_type=image_type, key=image.key, uri=build_uri
)
else:
return gspaths.Image(
build=build, image_type=image_type, key=image.key, uri=build_uri
)
def _GenUnsignedGSPath(
image: payload_pb2.UnsignedImage, image_type: str, minios: bool
) -> gspaths.UnsignedImageArchive:
"""Take an UnsignedImage_pb2 and return a gspaths.UnsignedImageArchive.
Args:
image: The build to create the gspath from.
image_type: The image type, either "recovery" or "test".
minios: Whether or not it's a miniOS image.
Returns:
A gspaths.UnsignedImageArchive instance.
"""
build = gspaths.Build(
board=image.build.build_target.name,
version=image.build.version,
channel=image.build.channel,
bucket=image.build.bucket,
)
build_uri = gspaths.ChromeosReleases.UnsignedImageUri(
build, image.milestone, image_type
)
build.uri = build_uri
if minios:
return gspaths.UnsignedMiniOSImageArchive(
build=build,
milestone=image.milestone,
image_type=image_type,
uri=build_uri,
)
else:
return gspaths.UnsignedImageArchive(
build=build,
milestone=image.milestone,
image_type=image_type,
uri=build_uri,
)
def _GenDLCImageGSPath(image: payload_pb2.DLCImage) -> gspaths.DLCImage:
"""Take a DLCImage_pb2 and return a gspaths.DLCImage.
Args:
image: The dlc image to create the gspath from.
Returns:
A gspaths.DLCImage instance.
"""
build = gspaths.Build(
board=image.build.build_target.name,
version=image.build.version,
channel=image.build.channel,
bucket=image.build.bucket,
)
dlc_image_uri = gspaths.ChromeosReleases.DLCImageUri(
build, image.dlc_id, image.dlc_package, image.dlc_image
)
return gspaths.DLCImage(
build=build,
image_type=image.image_type,
key="",
uri=dlc_image_uri,
dlc_id=image.dlc_id,
dlc_package=image.dlc_package,
dlc_image=image.dlc_image,
)