blob: 92cc874095b68fe85abda42a3f7432c972581e3a [file] [log] [blame]
# Copyright 2023 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Script to generate (+ upload) DLC artifacts."""
import logging
import os
import shutil
from typing import List
from chromite.lib import commandline
from chromite.lib import cros_build_lib
from chromite.lib import dlc_lib
from chromite.lib import osutils
# Predefined salts.
_SHORT_SALT = "1337D00D"
# Tarball extension with correct compression.
_TAR_COMP_EXT = ".tar.zst"
_META_OUT_FILE = dlc_lib.DLC_TMP_META_DIR + _TAR_COMP_EXT
# Filenames.
_METADATA_FILE = "metadata"
def ParseArguments(argv: List[str]) -> commandline.ArgumentNamespace:
"""Returns a namespace for the CLI arguments."""
parser = commandline.ArgumentParser(description=__doc__)
parser.add_argument(
"--src-dir",
type="dir_exists",
required=True,
help="The directory to package as a DLC",
)
# Support license addition here. For now, have users explicitly pass in a
# stub license path.
parser.add_argument(
"--license",
type="file_exists",
required=True,
help="The path to license, this should be the same license as the one"
" used within the package",
)
parser.add_argument(
"--output-dir",
type="str_path",
help="The optional output directory to put artifacts into",
)
parser.add_argument(
"--output-metadata-dir",
type="str_path",
help="The optional output directory to put metadata into",
)
parser.add_bool_argument(
"--upload",
default=False,
enabled_desc="Upload the DLC artifacts to google buckets",
disabled_desc="Do not upload the DLC artifacts to google buckets",
)
parser.add_argument(
"--uri-path",
type="gs_path",
help="The override for DLC image URI, check dlc_lib for default",
)
parser.add_bool_argument(
"--upload-dry-run",
default=False,
enabled_desc="Dry run without actual upload",
disabled_desc="Ignored",
)
parser.add_bool_argument(
"--reproducible-image",
default=False,
enabled_desc="To generate reproducible DLC images",
disabled_desc="To generate randomized DLC images",
)
# Enable powerwash safety for this DLC.
parser.add_bool_argument(
"--powerwash-safety",
default=False,
enabled_desc="Enable powerwash safety feature for this DLC",
disabled_desc="Disable powerwash safety feature for this DLC",
)
# DLC required fields.
parser.add_argument(
"--id",
type=str,
required=True,
help="The DLC ID",
)
parser.add_argument(
"--preallocated-blocks",
type=int,
required=True,
help="The preallocated number of blocks in 4KiB chunks",
)
# DLC optional fields.
parser.add_argument(
"--name",
type=str,
default="",
help="The name of the DLC in human friendly format",
)
parser.add_argument(
"--description",
type=str,
default="",
help="The description of the DLC in human friendly format",
)
parser.add_argument(
"--version",
type=str,
required=True,
help="The version of this DLC build",
)
opts = parser.parse_args(argv)
dlc_lib.ValidateDlcIdentifier(opts.id)
opts.Freeze()
return opts
def GenerateDlcParams(
opts: commandline.ArgumentNamespace,
) -> dlc_lib.EbuildParams:
"""Generates and verifies DLC parameters based on options
Args:
opts: The command line arguments.
Returns:
The DLC ebuild parameters.
"""
params = dlc_lib.EbuildParams(
dlc_id=opts.id,
dlc_package="package",
fs_type=dlc_lib.SQUASHFS_TYPE,
pre_allocated_blocks=opts.preallocated_blocks,
version=opts.version,
name=opts.name,
description=opts.description,
# Add preloading support.
preload=False,
used_by="",
mount_file_required=False,
fullnamerev="",
scaled=True,
loadpin_verity_digest=False,
powerwash_safe=opts.powerwash_safety,
use_logical_volume=True,
)
params.VerifyDlcParameters()
return params
def UploadDlcArtifacts(
dlcartifacts: dlc_lib.DlcArtifacts, dry_run: bool
) -> None:
"""Uploads the DLC artifacts based on `DlcArtifacts`
Args:
dlcartifacts: The DLC artifacts to upload.
dry_run: Dry run without actually uploading if true.
"""
logging.info("Uploading DLC artifacts")
logging.debug(
"Uploading DLC image %s to %s",
dlcartifacts.image,
dlcartifacts.uri_path,
)
logging.debug(
"Uploading DLC meta %s to %s", dlcartifacts.meta, dlcartifacts.uri_path
)
dlcartifacts.Upload(dry_run=dry_run)
def GenerateDlcArtifacts(opts: commandline.ArgumentNamespace) -> None:
"""Generates the DLC artifacts
Args:
opts: The command line arguments.
"""
params = GenerateDlcParams(opts)
uri_path = opts.uri_path or params.GetUriPath()
with osutils.TempDir(prefix="dlcartifacts", sudo_rm=True) as tmpdir:
output_dir = opts.output_dir or tmpdir
os.makedirs(output_dir, exist_ok=True)
logging.info("Generating DLC artifacts")
artifacts = dlc_lib.DlcGenerator(
src_dir=opts.src_dir,
sysroot="",
board=dlc_lib.MAGIC_BOARD,
ebuild_params=params,
reproducible=opts.reproducible_image,
license_file=opts.license,
).ExternalGenerateDLC(
tmpdir, _SHORT_SALT if opts.reproducible_image else None
)
logging.debug("Generated DLC artifacts: %s", artifacts.StringJSON())
# Handle the meta.
meta_out = os.path.join(output_dir, _META_OUT_FILE)
logging.info("Emitting the metadata into %s", meta_out)
cros_build_lib.CreateTarball(
tarball_path=meta_out,
cwd=artifacts.meta,
compression=cros_build_lib.CompressionType.ZSTD,
extra_env={"ZSTD_CLEVEL": "9"},
)
# Handle the image.
image_out = os.path.join(output_dir, dlc_lib.DLC_IMAGE)
logging.info("Emitting the DLC image into %s", image_out)
shutil.move(artifacts.image, image_out)
# Handle the upload.
ret_artifacts = dlc_lib.DlcArtifacts(
uri_path=uri_path,
image=image_out,
meta=meta_out,
)
ret_artifacts_json = ret_artifacts.StringJSON()
logging.debug("The final DLC artifacts: %s", ret_artifacts_json)
if opts.output_metadata_dir:
osutils.WriteFile(
os.path.join(opts.output_metadata_dir, _METADATA_FILE),
ret_artifacts_json,
makedirs=True,
)
if opts.upload or opts.upload_dry_run:
UploadDlcArtifacts(ret_artifacts, opts.upload_dry_run)
else:
logging.debug("Skipping DLC artifacts upload")
def main(argv) -> None:
GenerateDlcArtifacts(ParseArguments(argv))