| # -*- coding: utf-8 -*- |
| # Copyright 2019 The Chromium OS Authors. All rights reserved. |
| # 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 __future__ import print_function |
| |
| from copy import deepcopy |
| import os |
| |
| from chromite.lib import chroot_util |
| from chromite.lib.paygen import gspaths |
| from chromite.lib.paygen import paygen_payload_lib |
| |
| from chromite.api.gen.chromiumos import common_pb2 |
| from chromite.api.gen.chromite.api import payload_pb2 |
| |
| |
| 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=None, |
| src_image=None, |
| dest_bucket=None, |
| verify=True, |
| keyset=None, |
| upload=True, |
| cache_dir=None): |
| """Init method, sets up all the paths and configuration. |
| |
| Args: |
| tgt_image (UnsignedImage, SignedImage, or DLCImage): |
| Proto for destination image. |
| src_image (UnsignedImage, SignedImage, DLCImage, or None): |
| Proto for source image. |
| dest_bucket (str): Destination bucket to place the final artifacts in. |
| verify (bool): If delta is made, verify the integrity of the payload. |
| keyset (str): The key to sign the image with. |
| upload (bool): Whether the payload generation results should be uploaded. |
| cache_dir (str): 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.verify = verify |
| self.keyset = keyset |
| 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) |
| elif isinstance(self.tgt_image, payload_pb2.SignedImage): |
| tgt_image_path = _GenSignedGSPath(self.tgt_image, self.image_type) |
| 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) |
| if isinstance(self.tgt_image, payload_pb2.SignedImage): |
| src_image_path = _GenSignedGSPath(self.src_image, self.image_type) |
| elif isinstance(self.tgt_image, payload_pb2.DLCImage): |
| src_image_path = _GenDLCImageGSPath(self.src_image) |
| |
| |
| # Set your output location. |
| if self.upload: |
| payload_build = deepcopy(tgt_image_path.build) |
| payload_build.bucket = dest_bucket |
| payload_output_uri = gspaths.ChromeosReleases.PayloadUri( |
| build=payload_build, |
| random_str=None, |
| key=self.keyset, |
| src_version=src_image_path.build.version if src_image else None, |
| ) |
| else: |
| payload_output_uri = None |
| |
| self.payload = gspaths.Payload( |
| tgt_image=tgt_image_path, src_image=src_image_path, |
| uri=payload_output_uri) |
| |
| |
| def GeneratePayload(self): |
| """Do payload generation (& maybe sign) on Google Storage CrOS images. |
| |
| Returns: |
| A tuple of (string, string) containing: |
| 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/...') |
| """ |
| should_sign = self.keyset != '' |
| |
| # 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: |
| self.paygen = paygen_payload_lib.PaygenPayload( |
| self.payload, |
| temp_dir, |
| sign=should_sign, |
| verify=self.verify, |
| upload=self.upload, |
| cache_dir=self.cache_dir) |
| |
| # We run and expect failures to raise, so if we get passed |
| # self.paygen.Run() then it's safe to assume the bin is in place. |
| local_path = os.path.join(temp_dir, 'delta.bin') |
| remote_uri = self.paygen.Run() |
| return (local_path, remote_uri) |
| |
| |
| class GeneratePayloadResult(object): |
| """Value object to report GeneratePayload results.""" |
| |
| def __init__(self, return_code): |
| """Initialize a GeneratePayloadResult. |
| |
| Args: |
| return_code (bool): The return code of the GeneratePayload operation. |
| """ |
| self.success = return_code == 0 |
| |
| |
| def _ImageTypeToStr(image_type_n): |
| """The numeral image type enum in proto to lowercase string.""" |
| return common_pb2.ImageType.Name(image_type_n).lower() |
| |
| |
| def _GenSignedGSPath(image, image_type): |
| """Take a SignedImage_pb2 and return a gspaths.Image. |
| |
| Args: |
| image (SignedImage_pb2): The build to create the gspath from. |
| image_type (string): The image type, either "recovery" or "base". |
| |
| 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 |
| |
| return gspaths.Image(build=build, |
| image_type=image_type, |
| uri=build_uri) |
| |
| |
| def _GenUnsignedGSPath(image, image_type): |
| """Take an UnsignedImage_pb2 and return a gspaths.UnsignedImageArchive. |
| |
| Args: |
| image (UnsignedImage_pb2): The build to create the gspath from. |
| image_type (string): The image type, either "recovery" or "test". |
| |
| 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 |
| |
| return gspaths.UnsignedImageArchive(build=build, |
| milestone=image.milestone, |
| image_type=image_type, |
| uri=build_uri) |
| |
| |
| def _GenDLCImageGSPath(image): |
| """Take a DLCImage_pb2 and return a gspaths.DLCImage. |
| |
| Args: |
| image (DLCImage_pb2): 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) |