Jae Hoon Kim | bb0ddf0 | 2023-07-21 04:23:12 +0000 | [diff] [blame] | 1 | # Copyright 2023 The ChromiumOS Authors |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
| 5 | """Script to generate (+ upload) DLC artifacts.""" |
| 6 | |
| 7 | import logging |
| 8 | import os |
| 9 | import shutil |
| 10 | from typing import List |
| 11 | |
| 12 | from chromite.lib import commandline |
| 13 | from chromite.lib import cros_build_lib |
| 14 | from chromite.lib import dlc_lib |
| 15 | from chromite.lib import osutils |
| 16 | |
| 17 | |
| 18 | # Predefined salts. |
| 19 | _SHORT_SALT = "1337D00D" |
| 20 | |
| 21 | # Tarball extension with correct compression. |
| 22 | _TAR_COMP_EXT = ".tar.zst" |
| 23 | _META_OUT_FILE = dlc_lib.DLC_TMP_META_DIR + _TAR_COMP_EXT |
| 24 | |
| 25 | # Filenames. |
| 26 | _METADATA_FILE = "metadata" |
| 27 | |
| 28 | |
| 29 | def ParseArguments(argv: List[str]) -> commandline.ArgumentNamespace: |
| 30 | """Returns a namespace for the CLI arguments.""" |
| 31 | parser = commandline.ArgumentParser(description=__doc__) |
| 32 | parser.add_argument( |
| 33 | "--src-dir", |
| 34 | type="dir_exists", |
| 35 | required=True, |
| 36 | help="The directory to package as a DLC", |
| 37 | ) |
| 38 | # Support license addition here. For now, have users explicitly pass in a |
| 39 | # stub license path. |
| 40 | parser.add_argument( |
| 41 | "--license", |
| 42 | type="file_exists", |
| 43 | required=True, |
| 44 | help="The path to license, this should be the same license as the one" |
| 45 | " used within the package", |
| 46 | ) |
| 47 | parser.add_argument( |
| 48 | "--output-dir", |
| 49 | type="path", |
| 50 | help="The optional output directory to put artifacts into", |
| 51 | ) |
| 52 | parser.add_argument( |
| 53 | "--output-metadata-dir", |
| 54 | type="path", |
| 55 | help="The optional output directory to put metadata into", |
| 56 | ) |
| 57 | |
Jae Hoon Kim | 3690a22 | 2023-09-20 18:15:27 +0000 | [diff] [blame] | 58 | parser.add_bool_argument( |
Jae Hoon Kim | bb0ddf0 | 2023-07-21 04:23:12 +0000 | [diff] [blame] | 59 | "--upload", |
Jae Hoon Kim | 3690a22 | 2023-09-20 18:15:27 +0000 | [diff] [blame] | 60 | default=False, |
| 61 | enabled_desc="Upload the DLC artifacts to google buckets", |
| 62 | disabled_desc="Do not upload the DLC artifacts to google buckets", |
Jae Hoon Kim | bb0ddf0 | 2023-07-21 04:23:12 +0000 | [diff] [blame] | 63 | ) |
| 64 | parser.add_argument( |
| 65 | "--uri-path", |
| 66 | type="gs_path", |
| 67 | help="The override for DLC image URI, check dlc_lib for default", |
| 68 | ) |
Jae Hoon Kim | 3690a22 | 2023-09-20 18:15:27 +0000 | [diff] [blame] | 69 | parser.add_bool_argument( |
Jae Hoon Kim | bb0ddf0 | 2023-07-21 04:23:12 +0000 | [diff] [blame] | 70 | "--upload-dry-run", |
Jae Hoon Kim | 3690a22 | 2023-09-20 18:15:27 +0000 | [diff] [blame] | 71 | default=False, |
| 72 | enabled_desc="Dry run without actual upload", |
| 73 | disabled_desc="Ignored", |
Jae Hoon Kim | bb0ddf0 | 2023-07-21 04:23:12 +0000 | [diff] [blame] | 74 | ) |
| 75 | |
Jae Hoon Kim | 3690a22 | 2023-09-20 18:15:27 +0000 | [diff] [blame] | 76 | parser.add_bool_argument( |
| 77 | "--reproducible-image", |
| 78 | default=False, |
| 79 | enabled_desc="To generate reproducible DLC images", |
| 80 | disabled_desc="To generate randomized DLC images", |
Jae Hoon Kim | bb0ddf0 | 2023-07-21 04:23:12 +0000 | [diff] [blame] | 81 | ) |
| 82 | |
Jae Hoon Kim | 553a5d0 | 2023-09-20 04:38:40 +0000 | [diff] [blame^] | 83 | # Enable powerwash safety for this DLC. |
| 84 | parser.add_bool_argument( |
| 85 | "--powerwash-safety", |
| 86 | default=False, |
| 87 | enabled_desc="Enable powerwash safety feature for this DLC", |
| 88 | disabled_desc="Disable powerwash safety feature for this DLC", |
| 89 | ) |
| 90 | |
Jae Hoon Kim | bb0ddf0 | 2023-07-21 04:23:12 +0000 | [diff] [blame] | 91 | # DLC required fields. |
| 92 | parser.add_argument( |
| 93 | "--id", |
| 94 | type=str, |
| 95 | required=True, |
| 96 | help="The DLC ID", |
| 97 | ) |
| 98 | parser.add_argument( |
| 99 | "--preallocated-blocks", |
| 100 | type=int, |
| 101 | required=True, |
| 102 | help="The preallocated number of blocks in 4KiB chunks", |
| 103 | ) |
| 104 | |
| 105 | # DLC optional fields. |
| 106 | parser.add_argument( |
| 107 | "--name", |
| 108 | type=str, |
| 109 | default="", |
| 110 | help="The name of the DLC in human friendly format", |
| 111 | ) |
| 112 | parser.add_argument( |
| 113 | "--description", |
| 114 | type=str, |
| 115 | default="", |
| 116 | help="The description of the DLC in human friendly format", |
| 117 | ) |
| 118 | parser.add_argument( |
| 119 | "--version", |
| 120 | type=str, |
| 121 | required=True, |
| 122 | help="The version of this DLC build", |
| 123 | ) |
| 124 | |
| 125 | opts = parser.parse_args(argv) |
| 126 | |
| 127 | dlc_lib.ValidateDlcIdentifier(opts.id) |
| 128 | |
| 129 | opts.Freeze() |
| 130 | |
| 131 | return opts |
| 132 | |
| 133 | |
| 134 | def GenerateDlcParams( |
| 135 | opts: commandline.ArgumentNamespace, |
| 136 | ) -> dlc_lib.EbuildParams: |
| 137 | """Generates and verifies DLC parameters based on options |
| 138 | |
| 139 | Args: |
| 140 | opts: The command line arguments. |
| 141 | |
| 142 | Returns: |
| 143 | The DLC ebuild parameters. |
| 144 | """ |
| 145 | params = dlc_lib.EbuildParams( |
| 146 | dlc_id=opts.id, |
| 147 | dlc_package="package", |
| 148 | fs_type=dlc_lib.SQUASHFS_TYPE, |
| 149 | pre_allocated_blocks=opts.preallocated_blocks, |
| 150 | version=opts.version, |
| 151 | name=opts.name, |
| 152 | description=opts.description, |
| 153 | # Add preloading support. |
| 154 | preload=False, |
| 155 | used_by="", |
| 156 | mount_file_required=False, |
| 157 | fullnamerev="", |
| 158 | scaled=True, |
| 159 | loadpin_verity_digest=False, |
Jae Hoon Kim | 553a5d0 | 2023-09-20 04:38:40 +0000 | [diff] [blame^] | 160 | powerwash_safe=opts.powerwash_safety, |
Jae Hoon Kim | bb0ddf0 | 2023-07-21 04:23:12 +0000 | [diff] [blame] | 161 | ) |
| 162 | params.VerifyDlcParameters() |
| 163 | return params |
| 164 | |
| 165 | |
| 166 | def UploadDlcArtifacts( |
| 167 | dlcartifacts: dlc_lib.DlcArtifacts, dry_run: bool |
| 168 | ) -> None: |
| 169 | """Uploads the DLC artifacts based on `DlcArtifacts` |
| 170 | |
| 171 | Args: |
| 172 | dlcartifacts: The DLC artifacts to upload. |
| 173 | dry_run: Dry run without actually uploading if true. |
| 174 | """ |
| 175 | logging.info("Uploading DLC artifacts") |
| 176 | logging.debug( |
| 177 | "Uploading DLC image %s to %s", |
| 178 | dlcartifacts.image, |
| 179 | dlcartifacts.uri_path, |
| 180 | ) |
| 181 | logging.debug( |
| 182 | "Uploading DLC meta %s to %s", dlcartifacts.meta, dlcartifacts.uri_path |
| 183 | ) |
| 184 | dlcartifacts.Upload(dry_run=dry_run) |
| 185 | |
| 186 | |
| 187 | def GenerateDlcArtifacts(opts: commandline.ArgumentNamespace) -> None: |
| 188 | """Generates the DLC artifacts |
| 189 | |
| 190 | Args: |
| 191 | opts: The command line arguments. |
| 192 | """ |
| 193 | params = GenerateDlcParams(opts) |
Jae Hoon Kim | 95cbdf5 | 2023-08-10 20:22:58 +0000 | [diff] [blame] | 194 | uri_path = opts.uri_path or params.GetUriPath() |
Jae Hoon Kim | bb0ddf0 | 2023-07-21 04:23:12 +0000 | [diff] [blame] | 195 | |
| 196 | with osutils.TempDir(prefix="dlcartifacts", sudo_rm=True) as tmpdir: |
| 197 | output_dir = opts.output_dir or tmpdir |
| 198 | os.makedirs(output_dir, exist_ok=True) |
| 199 | |
| 200 | logging.info("Generating DLC artifacts") |
| 201 | artifacts = dlc_lib.DlcGenerator( |
| 202 | src_dir=opts.src_dir, |
| 203 | sysroot="", |
| 204 | board=dlc_lib.MAGIC_BOARD, |
| 205 | ebuild_params=params, |
Jae Hoon Kim | 3690a22 | 2023-09-20 18:15:27 +0000 | [diff] [blame] | 206 | reproducible=opts.reproducible_image, |
Jae Hoon Kim | bb0ddf0 | 2023-07-21 04:23:12 +0000 | [diff] [blame] | 207 | license_file=opts.license, |
| 208 | ).ExternalGenerateDLC( |
Jae Hoon Kim | 3690a22 | 2023-09-20 18:15:27 +0000 | [diff] [blame] | 209 | tmpdir, _SHORT_SALT if opts.reproducible_image else None |
Jae Hoon Kim | bb0ddf0 | 2023-07-21 04:23:12 +0000 | [diff] [blame] | 210 | ) |
| 211 | logging.debug("Generated DLC artifacts: %s", artifacts.StringJSON()) |
| 212 | |
| 213 | # Handle the meta. |
| 214 | meta_out = os.path.join(output_dir, _META_OUT_FILE) |
| 215 | |
| 216 | logging.info("Emitting the metadata into %s", meta_out) |
| 217 | cros_build_lib.CreateTarball( |
| 218 | tarball_path=meta_out, |
| 219 | cwd=artifacts.meta, |
| 220 | compression=cros_build_lib.CompressionType.ZSTD, |
| 221 | extra_env={"ZSTD_CLEVEL": "9"}, |
| 222 | ) |
| 223 | |
| 224 | # Handle the image. |
| 225 | image_out = os.path.join(output_dir, dlc_lib.DLC_IMAGE) |
| 226 | logging.info("Emitting the DLC image into %s", image_out) |
| 227 | shutil.move(artifacts.image, image_out) |
| 228 | |
| 229 | # Handle the upload. |
| 230 | ret_artifacts = dlc_lib.DlcArtifacts( |
| 231 | uri_path=uri_path, |
| 232 | image=image_out, |
| 233 | meta=meta_out, |
| 234 | ) |
| 235 | ret_artifacts_json = ret_artifacts.StringJSON() |
| 236 | logging.debug("The final DLC artifacts: %s", ret_artifacts_json) |
| 237 | |
| 238 | if opts.output_metadata_dir: |
| 239 | osutils.WriteFile( |
| 240 | os.path.join(opts.output_metadata_dir, _METADATA_FILE), |
| 241 | ret_artifacts_json, |
| 242 | makedirs=True, |
| 243 | ) |
| 244 | |
| 245 | if opts.upload or opts.upload_dry_run: |
| 246 | UploadDlcArtifacts(ret_artifacts, opts.upload_dry_run) |
| 247 | else: |
| 248 | logging.debug("Skipping DLC artifacts upload") |
| 249 | |
| 250 | |
| 251 | def main(argv): |
| 252 | GenerateDlcArtifacts(ParseArguments(argv)) |