blob: 2406c4ac5e22d49309ffb59a4196f73361ae30bd [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.
"""Trigger signing for gsc images.
Causes signing to occur for a given artifact.
"""
import argparse
import json
import logging
import re
from chromite.api.gen.chromiumos import common_pb2
from chromite.api.gen.chromiumos import sign_image_pb2
from chromite.lib import commandline
from chromite.lib import cros_build_lib
from chromite.lib import gs
GSC_PRODUCTION_JOB = "chromeos/release/sign-image"
GSC_STAGING_JOB = "chromeos/staging/staging-sign-image"
_IMAGE_TYPE = "image_type_gsc_firmware"
# See ../infra/proto/src/chromiumos/common.proto.
_channels = {
k.lower().replace("channel_", ""): v for k, v in common_pb2.Channel.items()
}
# See ../infra/proto/src/chromiumos/common.proto.
_image_types = {k.lower(): v for k, v in common_pb2.ImageType.items() if v}
# See ../infra/proto/src/chromiumos/sign_image.proto.
_signer_types = {
k.lower().replace("signer_", ""): v
for k, v in sign_image_pb2.SignerType.items()
if v
}
# See ../infra/proto/src/chromiumos/sign_image.proto.
_target_types = {
k.lower(): v for k, v in sign_image_pb2.GscInstructions.Target.items() if v
}
def GetParser():
"""Creates the argparse parser."""
class uint32(int):
"""Unsigned 32-bit int."""
def __new__(cls, val):
"""Return a positive 32-bit value."""
return int(val, 0) & 0xFFFFFFFF
class Dev01Action(argparse.Action):
"""Convert --dev01 MMM NNN into an %08x-%08x device_id."""
def __call__(
self, parser, namespace, values, option_string=None
) -> None:
if not namespace.dev_ids:
namespace.dev_ids = []
namespace.dev_ids.append("%08x-%08x" % (values[0], values[1]))
parser = commandline.ArgumentParser(
description=__doc__, default_log_level="debug", dryrun=True
)
parser.add_argument(
"--staging",
action="store_true",
dest="staging",
help="Use the staging instance to create the request.",
)
parser.add_argument(
"--build-target", default="unknown", help="The build target."
)
parser.add_argument(
"--channel",
choices=_channels,
default="unspecified",
help="The (optional) channel for the artifact.",
)
parser.add_argument(
"--archive", required=True, help="The gs://path of the archive to sign."
)
parser.add_argument(
"--keyset", default="cr50-accessory-mp", help="The keyset to use."
)
parser.add_argument(
"--signer-type",
choices=_signer_types,
default="production",
help="The image type.",
)
parser.add_argument(
"--target",
choices=_target_types,
default="prepvt",
help="The image type.",
)
node_locked = parser.add_argument_group(
"Node_locked",
description="Additional arguments for --target=node_locked",
)
node_locked.add_argument(
"--device-id",
action="append",
dest="dev_ids",
help='The device_id ("%%08x-%%08x" format).',
)
node_locked.add_argument(
"--dev01",
nargs=2,
type=uint32,
action=Dev01Action,
metavar="DEVx",
dest="dev_ids",
help="DEV0 and DEV1 for the device",
)
return parser
def LaunchOne(dryrun, builder, properties) -> None:
"""Launch one build.
Args:
dryrun: If true, just echo what would be done.
builder: builder to use.
properties: json properties to use.
"""
json_prop = json.dumps(properties)
cmd = ["bb", "add", "-p", "@/dev/stdin", builder]
if dryrun:
logging.info("Would run: %s with input: %s", " ".join(cmd), json_prop)
else:
cros_build_lib.run(cmd, input=json_prop, log_output=True)
def main(argv):
parser = GetParser()
options = parser.parse_args(argv)
options.Freeze()
passes = True
if (
options.target == "nightly"
and options.keyset != "ti50-accessory-nodelocked-ro-premp"
):
logging.error("--target nightly can only be built for node locked ros")
passes = False
if options.target == "node_locked":
if not options.dev_ids:
logging.error("--target node_locked must specify device_id")
passes = False
else:
for dev in options.dev_ids:
if not re.match("[0-9a-fA-F]{8}-[0-9a-fA-F]{8}$", dev):
logging.error("Illegal device_id %s", dev)
passes = False
elif options.dev_ids:
logging.error("Device IDs are only valid with `--target node_locked`")
passes = False
if not passes:
return 1
builder = GSC_STAGING_JOB if options.staging else GSC_PRODUCTION_JOB
properties = {
"archive": options.archive,
"build_target": {"name": options.build_target},
"channel": _channels[options.channel],
"gsc_instructions": {
"target": _target_types[options.target],
},
"image_type": _image_types[_IMAGE_TYPE],
"keyset": options.keyset,
"signer_type": _signer_types[options.signer_type],
}
gcs = gs.GSContext()
if not gcs.Exists(options.archive):
logging.error(
"The archive %s was not found on google storage.", options.archive
)
return 1
if options.target != "node_locked":
LaunchOne(options.dryrun, builder, properties)
else:
for dev in options.dev_ids:
properties["gsc_instructions"]["device_id"] = dev
LaunchOne(options.dryrun, builder, properties)
return 0