diff options
Diffstat (limited to 'scripts/pushimage.py')
-rw-r--r-- | scripts/pushimage.py | 256 |
1 files changed, 182 insertions, 74 deletions
diff --git a/scripts/pushimage.py b/scripts/pushimage.py index 58c0ca23c..342295f34 100644 --- a/scripts/pushimage.py +++ b/scripts/pushimage.py @@ -49,6 +49,8 @@ _SUPPORTED_IMAGE_TYPES = ( constants.IMAGE_TYPE_FACTORY, constants.IMAGE_TYPE_FIRMWARE, constants.IMAGE_TYPE_NV_LP0_FIRMWARE, + constants.IMAGE_TYPE_ACCESSORY_USBPD, + constants.IMAGE_TYPE_ACCESSORY_RWSIG, constants.IMAGE_TYPE_BASE, ) @@ -60,6 +62,10 @@ class PushError(Exception): class MissingBoardInstructions(Exception): """Raised when a board lacks any signer instructions.""" + def __init__(self, board, image_type, input_insns): + Exception.__init__(self, 'Board %s lacks insns for %s image: %s not found' % + (board, image_type, input_insns)) + class InputInsns(object): """Object to hold settings for a signable board. @@ -68,18 +74,38 @@ class InputInsns(object): reads) is not exactly the same as the instruction file pushimage reads. """ - def __init__(self, board): + def __init__(self, board, image_type=None): + """Initialization. + + Args: + board: The board to look up details. + image_type: The type of image we will be signing (see --sign-types). + """ self.board = board config = ConfigParser.ConfigParser() config.readfp(open(self.GetInsnFile('DEFAULT'))) + # What pushimage internally refers to as 'recovery', are the basic signing # instructions in practice, and other types are stacked on top. + if image_type is None: + image_type = constants.IMAGE_TYPE_RECOVERY + self.image_type = image_type input_insns = self.GetInsnFile(constants.IMAGE_TYPE_RECOVERY) if not os.path.exists(input_insns): # This board doesn't have any signing instructions. - raise MissingBoardInstructions(self.board) + raise MissingBoardInstructions(self.board, image_type, input_insns) config.readfp(open(input_insns)) + + if image_type is not None: + input_insns = self.GetInsnFile(image_type) + if not os.path.exists(input_insns): + # This type doesn't have any signing instructions. + raise MissingBoardInstructions(self.board, image_type, input_insns) + + self.image_type = image_type + config.readfp(open(input_insns)) + self.cfg = config def GetInsnFile(self, image_type): @@ -125,24 +151,82 @@ class InputInsns(object): """ return self.SplitCfgField(self.cfg.get('insns', 'channel')) - def GetKeysets(self): - """Return the list of keysets to sign for this board.""" - return self.SplitCfgField(self.cfg.get('insns', 'keyset')) + def GetKeysets(self, insns_merge=None): + """Return the list of keysets to sign for this board. + + Args: + insns_merge: The additional section to look at over [insns]. + """ + # First load the default value from [insns.keyset] if available. + sections = ['insns'] + # Then overlay the [insns.xxx.keyset] if requested. + if insns_merge is not None: + sections += [insns_merge] + + keyset = '' + for section in sections: + try: + keyset = self.cfg.get(section, 'keyset') + except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): + pass + + # We do not perturb the order (e.g. using sorted() or making a set()) + # because we want the behavior stable, and we want the input insns to + # explicitly control the order (since it has an impact on naming). + return self.SplitCfgField(keyset) + + def GetAltInsnSets(self): + """Return the list of alternative insn sections.""" + # We do not perturb the order (e.g. using sorted() or making a set()) + # because we want the behavior stable, and we want the input insns to + # explicitly control the order (since it has an impact on naming). + ret = [x for x in self.cfg.sections() if x.startswith('insns.')] + return ret if ret else [None] + + @staticmethod + def CopyConfigParser(config): + """Return a copy of a ConfigParser object. + + The python guys broke the ability to use something like deepcopy: + https://bugs.python.org/issue16058 + """ + # Write the current config to a string io object. + data = cStringIO.StringIO() + config.write(data) + data.seek(0) + + # Create a new ConfigParser from the serialized data. + ret = ConfigParser.ConfigParser() + ret.readfp(data) + + return ret - def OutputInsns(self, image_type, output_file, sect_insns, sect_general): + def OutputInsns(self, output_file, sect_insns, sect_general, + insns_merge=None): """Generate the output instruction file for sending to the signer. + The override order is (later has precedence): + [insns] + [insns_merge] (should be named "insns.xxx") + sect_insns + Note: The format of the instruction file pushimage outputs (and the signer reads) is not exactly the same as the instruction file pushimage reads. Args: - image_type: The type of image we will be signing (see --sign-types). output_file: The file to write the new instruction file to. sect_insns: Items to set/override in the [insns] section. sect_general: Items to set/override in the [general] section. + insns_merge: The alternative insns.xxx section to merge. """ - config = ConfigParser.ConfigParser() - config.readfp(open(self.GetInsnFile(image_type))) + # Create a copy so we can clobber certain fields. + config = self.CopyConfigParser(self.cfg) + sect_insns = sect_insns.copy() + + # Merge in the alternative insns section if need be. + if insns_merge is not None: + for k, v in config.items(insns_merge): + sect_insns.setdefault(k, v) # Clear channel entry in instructions file, ensuring we only get # one channel for the signer to look at. Then provide all the @@ -154,11 +238,15 @@ class InputInsns(object): for k, v in fields.iteritems(): config.set(sect, k, v) + # Now prune the alternative sections. + for alt in self.GetAltInsnSets(): + config.remove_section(alt) + output = cStringIO.StringIO() config.write(output) data = output.getvalue() osutils.WriteFile(output_file, data) - logging.debug('generated insns file for %s:\n%s', image_type, data) + logging.debug('generated insns file for %s:\n%s', self.image_type, data) def MarkImageToBeSigned(ctx, tbs_base, insns_path, priority): @@ -244,14 +332,13 @@ def PushImage(src_path, board, versionrev=None, profile=None, priority=50, try: input_insns = InputInsns(board) except MissingBoardInstructions as e: - logging.warning('board "%s" is missing base instruction file: %s', board, e) + logging.warning('Missing base instruction file: %s', e) logging.warning('not uploading anything for signing') return channels = input_insns.GetChannels() - # We want force_keysets as a set, and keysets as a list. + # We want force_keysets as a set. force_keysets = set(force_keysets) - keysets = list(force_keysets) if force_keysets else input_insns.GetKeysets() if mock: logging.info('Upload mode: mock; signers will not process anything') @@ -279,7 +366,6 @@ def PushImage(src_path, board, versionrev=None, profile=None, priority=50, if dry_run: logging.info('DRY RUN MODE ACTIVE: NOTHING WILL BE UPLOADED') logging.info('Signing for channels: %s', ' '.join(channels)) - logging.info('Signing for keysets : %s', ' '.join(keysets)) instruction_urls = {} @@ -303,6 +389,8 @@ def PushImage(src_path, board, versionrev=None, profile=None, priority=50, firmware_basename = _ImageNameBase(constants.IMAGE_TYPE_FIRMWARE) nv_lp0_firmware_basename = _ImageNameBase( constants.IMAGE_TYPE_NV_LP0_FIRMWARE) + acc_usbpd_basename = _ImageNameBase(constants.IMAGE_TYPE_ACCESSORY_USBPD) + acc_rwsig_basename = _ImageNameBase(constants.IMAGE_TYPE_ACCESSORY_RWSIG) test_basename = _ImageNameBase(constants.IMAGE_TYPE_TEST) base_basename = _ImageNameBase(constants.IMAGE_TYPE_BASE) hwqual_tarball = 'chromeos-hwqual-%s-%s.tar.bz2' % (board, versionrev) @@ -335,6 +423,12 @@ def PushImage(src_path, board, versionrev=None, profile=None, priority=50, ('firmware_from_source.tar.bz2', nv_lp0_firmware_basename, 'tar.bz2', constants.IMAGE_TYPE_NV_LP0_FIRMWARE), + + ('firmware_from_source.tar.bz2', acc_usbpd_basename, 'tar.bz2', + constants.IMAGE_TYPE_ACCESSORY_USBPD), + + ('firmware_from_source.tar.bz2', acc_rwsig_basename, 'tar.bz2', + constants.IMAGE_TYPE_ACCESSORY_RWSIG), ) # The following build artifacts are copied and marked for signing, if @@ -387,66 +481,80 @@ def PushImage(src_path, board, versionrev=None, profile=None, priority=50, logging.debug('Files to sign: %s', files_to_sign) # Now go through the subset for signing. - for keyset in keysets: - logging.debug('\n\n#### KEYSET: %s ####\n', keyset) - sect_insns['keyset'] = keyset - for image_type, dst_name, suffix in files_to_sign: - dst_archive = '%s.%s' % (dst_name, suffix) - sect_general['archive'] = dst_archive - sect_general['type'] = image_type - - # In the default/automatic mode, only flag files for signing if the - # archives were actually uploaded in a previous stage. This additional - # check can be removed in future once |sign_types| becomes a required - # argument. - # TODO: Make |sign_types| a required argument. - gs_artifact_path = os.path.join(dst_path, dst_archive) - exists = False - try: - exists = ctx.Exists(gs_artifact_path) - except gs.GSContextException: - unknown_error[0] = True - logging.error('Unknown error while checking %s', gs_artifact_path, - exc_info=True) - if not exists: - logging.info('%s does not exist. Nothing to sign.', - gs_artifact_path) - continue - - input_insn_path = input_insns.GetInsnFile(image_type) - if not os.path.exists(input_insn_path): - logging.info('%s does not exist. Nothing to sign.', input_insn_path) - continue - - # Generate the insn file for this artifact that the signer will use, - # and flag it for signing. - with tempfile.NamedTemporaryFile( - bufsize=0, prefix='pushimage.insns.') as insns_path: - input_insns.OutputInsns(image_type, insns_path.name, sect_insns, - sect_general) - - gs_insns_path = '%s/%s' % (dst_path, dst_name) - if keyset != keysets[0]: - gs_insns_path += '-%s' % keyset - gs_insns_path += '.instructions' - - try: - ctx.Copy(insns_path.name, gs_insns_path) - except gs.GSContextException: - unknown_error[0] = True - logging.error('Unknown error while uploading insns %s', - gs_insns_path, exc_info=True) - continue - - try: - MarkImageToBeSigned(ctx, tbs_base, gs_insns_path, priority) - except gs.GSContextException: - unknown_error[0] = True - logging.error('Unknown error while marking for signing %s', - gs_insns_path, exc_info=True) - continue - logging.info('Signing %s image %s', image_type, gs_insns_path) - instruction_urls.setdefault(channel, []).append(gs_insns_path) + for image_type, dst_name, suffix in files_to_sign: + try: + input_insns = InputInsns(board, image_type=image_type) + except MissingBoardInstructions as e: + logging.info('Nothing to sign: %s', e) + continue + + dst_archive = '%s.%s' % (dst_name, suffix) + sect_general['archive'] = dst_archive + sect_general['type'] = image_type + + # In the default/automatic mode, only flag files for signing if the + # archives were actually uploaded in a previous stage. This additional + # check can be removed in future once |sign_types| becomes a required + # argument. + # TODO: Make |sign_types| a required argument. + gs_artifact_path = os.path.join(dst_path, dst_archive) + exists = False + try: + exists = ctx.Exists(gs_artifact_path) + except gs.GSContextException: + unknown_error[0] = True + logging.error('Unknown error while checking %s', gs_artifact_path, + exc_info=True) + if not exists: + logging.info('%s does not exist. Nothing to sign.', + gs_artifact_path) + continue + + first_image = True + for alt_insn_set in input_insns.GetAltInsnSets(): + # Figure out which keysets have been requested for this type. + # We sort the forced set so tests/runtime behavior is stable. + keysets = sorted(force_keysets) + if not keysets: + keysets = input_insns.GetKeysets(insns_merge=alt_insn_set) + if not keysets: + logging.warning('Skipping %s image signing due to no keysets', + image_type) + + for keyset in keysets: + sect_insns['keyset'] = keyset + + # Generate the insn file for this artifact that the signer will use, + # and flag it for signing. + with tempfile.NamedTemporaryFile( + bufsize=0, prefix='pushimage.insns.') as insns_path: + input_insns.OutputInsns(insns_path.name, sect_insns, sect_general, + insns_merge=alt_insn_set) + + gs_insns_path = '%s/%s' % (dst_path, dst_name) + if not first_image: + gs_insns_path += '-%s' % keyset + first_image = False + gs_insns_path += '.instructions' + + try: + ctx.Copy(insns_path.name, gs_insns_path) + except gs.GSContextException: + unknown_error[0] = True + logging.error('Unknown error while uploading insns %s', + gs_insns_path, exc_info=True) + continue + + try: + MarkImageToBeSigned(ctx, tbs_base, gs_insns_path, priority) + except gs.GSContextException: + unknown_error[0] = True + logging.error('Unknown error while marking for signing %s', + gs_insns_path, exc_info=True) + continue + logging.info('Signing %s image with keyset %s at %s', image_type, + keyset, gs_insns_path) + instruction_urls.setdefault(channel, []).append(gs_insns_path) if unknown_error[0]: raise PushError('hit some unknown error(s)', instruction_urls) |