blob: 90ee2fdd4812cfaf0f3c23fe30dc958161bc91ae [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
Mike Frysingerd13faeb2013-09-05 16:00:46 -04002# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""ChromeOS image pusher (from cbuildbot to signer).
7
8This pushes files from the archive bucket to the signer bucket and marks
9artifacts for signing (which a signing process will look for).
10"""
11
12from __future__ import print_function
13
Mike Frysingerd13faeb2013-09-05 16:00:46 -040014import getpass
15import os
16import re
Mike Frysinger99a7f8a2020-04-19 06:23:18 -040017import sys
Mike Frysinger09fe0122014-02-09 02:44:05 -050018import textwrap
Mike Frysingerd13faeb2013-09-05 16:00:46 -040019
Mike Frysingerb43142e2019-08-27 17:50:44 -040020from six.moves import configparser
Mike Frysingerfd544572019-09-06 16:35:50 -040021from six.moves import StringIO
Mike Frysingerb43142e2019-08-27 17:50:44 -040022
Aviv Keshetb7519e12016-10-04 00:50:00 -070023from chromite.lib import constants
Mike Frysingerd13faeb2013-09-05 16:00:46 -040024from chromite.lib import commandline
25from chromite.lib import cros_build_lib
Ralph Nathan5a582ff2015-03-20 18:18:30 -070026from chromite.lib import cros_logging as logging
Mike Frysingerd13faeb2013-09-05 16:00:46 -040027from chromite.lib import gs
28from chromite.lib import osutils
29from chromite.lib import signing
30
31
Mike Frysinger99a7f8a2020-04-19 06:23:18 -040032assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
33
34
Mike Frysingerd13faeb2013-09-05 16:00:46 -040035# This will split a fully qualified ChromeOS version string up.
36# R34-5126.0.0 will break into "34" and "5126.0.0".
37VERSION_REGEX = r'^R([0-9]+)-([^-]+)'
38
Mike Frysingerdad40d62014-02-09 02:18:02 -050039# The test signers will scan this dir looking for test work.
40# Keep it in sync with the signer config files [gs_test_buckets].
41TEST_SIGN_BUCKET_BASE = 'gs://chromeos-throw-away-bucket/signer-tests'
42
David Rileyf8205122015-09-04 13:46:36 -070043# Keysets that are only valid in the above test bucket.
44TEST_KEYSET_PREFIX = 'test-keys'
45TEST_KEYSETS = set((
46 'mp',
47 'premp',
48 'nvidia-premp',
49))
Mike Frysingerdad40d62014-02-09 02:18:02 -050050
Amey Deshpandea936c622015-08-12 17:27:54 -070051# Supported image types for signing.
52_SUPPORTED_IMAGE_TYPES = (
53 constants.IMAGE_TYPE_RECOVERY,
54 constants.IMAGE_TYPE_FACTORY,
55 constants.IMAGE_TYPE_FIRMWARE,
Vincent Palatind599c662015-10-26 09:51:41 -070056 constants.IMAGE_TYPE_ACCESSORY_USBPD,
57 constants.IMAGE_TYPE_ACCESSORY_RWSIG,
Amey Deshpandea936c622015-08-12 17:27:54 -070058 constants.IMAGE_TYPE_BASE,
Vadim Bendeburyfe37f282020-11-04 19:05:49 -080059 constants.IMAGE_TYPE_GSC_FIRMWARE,
Amey Deshpandea936c622015-08-12 17:27:54 -070060)
61
Mike Frysingerd13faeb2013-09-05 16:00:46 -040062
Mike Frysinger4495b032014-03-05 17:24:03 -050063class PushError(Exception):
64 """When an (unknown) error happened while trying to push artifacts."""
65
66
Mike Frysingerd13faeb2013-09-05 16:00:46 -040067class MissingBoardInstructions(Exception):
68 """Raised when a board lacks any signer instructions."""
69
Mike Frysingerd84d91e2015-11-05 18:02:24 -050070 def __init__(self, board, image_type, input_insns):
71 Exception.__init__(self, 'Board %s lacks insns for %s image: %s not found' %
72 (board, image_type, input_insns))
73
Mike Frysingerd13faeb2013-09-05 16:00:46 -040074
75class InputInsns(object):
76 """Object to hold settings for a signable board.
77
78 Note: The format of the instruction file pushimage outputs (and the signer
79 reads) is not exactly the same as the instruction file pushimage reads.
80 """
81
Don Garrett3cf5f9a2018-08-14 13:14:47 -070082 def __init__(self, board, image_type=None, buildroot=None):
Mike Frysingerd84d91e2015-11-05 18:02:24 -050083 """Initialization.
84
85 Args:
86 board: The board to look up details.
87 image_type: The type of image we will be signing (see --sign-types).
Don Garrett3cf5f9a2018-08-14 13:14:47 -070088 buildroot: Buildroot in which to look for signing instructions.
Mike Frysingerd84d91e2015-11-05 18:02:24 -050089 """
Mike Frysingerd13faeb2013-09-05 16:00:46 -040090 self.board = board
Don Garrett3cf5f9a2018-08-14 13:14:47 -070091 self.buildroot = buildroot or constants.SOURCE_ROOT
Mike Frysingerd13faeb2013-09-05 16:00:46 -040092
Mike Frysingerb43142e2019-08-27 17:50:44 -040093 config = configparser.ConfigParser()
Mike Frysingerd7c93092019-10-14 00:12:50 -040094 with open(self.GetInsnFile('DEFAULT')) as fp:
Mike Frysingerb6ce0222020-05-09 00:50:06 -040095 config.read_file(fp)
Mike Frysingerd84d91e2015-11-05 18:02:24 -050096
Amey Deshpandea936c622015-08-12 17:27:54 -070097 # What pushimage internally refers to as 'recovery', are the basic signing
98 # instructions in practice, and other types are stacked on top.
Mike Frysingerd84d91e2015-11-05 18:02:24 -050099 if image_type is None:
100 image_type = constants.IMAGE_TYPE_RECOVERY
101 self.image_type = image_type
Amey Deshpandea936c622015-08-12 17:27:54 -0700102 input_insns = self.GetInsnFile(constants.IMAGE_TYPE_RECOVERY)
103 if not os.path.exists(input_insns):
104 # This board doesn't have any signing instructions.
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500105 raise MissingBoardInstructions(self.board, image_type, input_insns)
Mike Frysingerd7c93092019-10-14 00:12:50 -0400106 with open(input_insns) as fp:
Mike Frysingerb6ce0222020-05-09 00:50:06 -0400107 config.read_file(fp)
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500108
109 if image_type is not None:
110 input_insns = self.GetInsnFile(image_type)
111 if not os.path.exists(input_insns):
112 # This type doesn't have any signing instructions.
113 raise MissingBoardInstructions(self.board, image_type, input_insns)
114
115 self.image_type = image_type
Mike Frysingerd7c93092019-10-14 00:12:50 -0400116 with open(input_insns) as fp:
Mike Frysingerb6ce0222020-05-09 00:50:06 -0400117 config.read_file(fp)
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500118
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400119 self.cfg = config
120
121 def GetInsnFile(self, image_type):
122 """Find the signer instruction files for this board/image type.
123
124 Args:
125 image_type: The type of instructions to load. It can be a common file
126 (like "DEFAULT"), or one of the --sign-types.
127
128 Returns:
129 Full path to the instruction file using |image_type| and |self.board|.
130 """
131 if image_type == image_type.upper():
132 name = image_type
Amey Deshpandea936c622015-08-12 17:27:54 -0700133 elif image_type in (constants.IMAGE_TYPE_RECOVERY,
134 constants.IMAGE_TYPE_BASE):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400135 name = self.board
136 else:
137 name = '%s.%s' % (self.board, image_type)
138
Don Garrett3cf5f9a2018-08-14 13:14:47 -0700139 return os.path.join(
140 self.buildroot, signing.INPUT_INSN_DIR_REL, '%s.instructions' % name)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400141
142 @staticmethod
143 def SplitCfgField(val):
144 """Split a string into multiple elements.
145
146 This centralizes our convention for multiple elements in the input files
147 being delimited by either a space or comma.
148
149 Args:
150 val: The string to split.
151
152 Returns:
153 The list of elements from having done split the string.
154 """
155 return val.replace(',', ' ').split()
156
157 def GetChannels(self):
158 """Return the list of channels to sign for this board.
159
160 If the board-specific config doesn't specify a preference, we'll use the
161 common settings.
162 """
163 return self.SplitCfgField(self.cfg.get('insns', 'channel'))
164
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500165 def GetKeysets(self, insns_merge=None):
166 """Return the list of keysets to sign for this board.
167
168 Args:
169 insns_merge: The additional section to look at over [insns].
170 """
171 # First load the default value from [insns.keyset] if available.
172 sections = ['insns']
173 # Then overlay the [insns.xxx.keyset] if requested.
174 if insns_merge is not None:
175 sections += [insns_merge]
176
177 keyset = ''
178 for section in sections:
179 try:
180 keyset = self.cfg.get(section, 'keyset')
Mike Frysingerb43142e2019-08-27 17:50:44 -0400181 except (configparser.NoSectionError, configparser.NoOptionError):
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500182 pass
183
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500184 # We do not perturb the order (e.g. using sorted() or making a set())
185 # because we want the behavior stable, and we want the input insns to
186 # explicitly control the order (since it has an impact on naming).
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500187 return self.SplitCfgField(keyset)
188
189 def GetAltInsnSets(self):
190 """Return the list of alternative insn sections."""
191 # We do not perturb the order (e.g. using sorted() or making a set())
192 # because we want the behavior stable, and we want the input insns to
193 # explicitly control the order (since it has an impact on naming).
194 ret = [x for x in self.cfg.sections() if x.startswith('insns.')]
195 return ret if ret else [None]
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400196
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500197 @staticmethod
198 def CopyConfigParser(config):
199 """Return a copy of a ConfigParser object.
200
Thiemo Nagel9fb99722017-05-26 16:26:40 +0200201 The python folks broke the ability to use something like deepcopy:
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500202 https://bugs.python.org/issue16058
203 """
204 # Write the current config to a string io object.
Mike Frysingerfd544572019-09-06 16:35:50 -0400205 data = StringIO()
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500206 config.write(data)
207 data.seek(0)
208
209 # Create a new ConfigParser from the serialized data.
Mike Frysingerb43142e2019-08-27 17:50:44 -0400210 ret = configparser.ConfigParser()
Mike Frysingerb6ce0222020-05-09 00:50:06 -0400211 ret.read_file(data)
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500212
213 return ret
214
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500215 def OutputInsns(self, output_file, sect_insns, sect_general,
216 insns_merge=None):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400217 """Generate the output instruction file for sending to the signer.
218
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500219 The override order is (later has precedence):
220 [insns]
221 [insns_merge] (should be named "insns.xxx")
222 sect_insns
223
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400224 Note: The format of the instruction file pushimage outputs (and the signer
225 reads) is not exactly the same as the instruction file pushimage reads.
226
227 Args:
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400228 output_file: The file to write the new instruction file to.
229 sect_insns: Items to set/override in the [insns] section.
230 sect_general: Items to set/override in the [general] section.
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500231 insns_merge: The alternative insns.xxx section to merge.
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400232 """
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500233 # Create a copy so we can clobber certain fields.
234 config = self.CopyConfigParser(self.cfg)
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500235 sect_insns = sect_insns.copy()
236
237 # Merge in the alternative insns section if need be.
238 if insns_merge is not None:
239 for k, v in config.items(insns_merge):
240 sect_insns.setdefault(k, v)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400241
242 # Clear channel entry in instructions file, ensuring we only get
243 # one channel for the signer to look at. Then provide all the
244 # other details for this signing request to avoid any ambiguity
245 # and to avoid relying on encoding data into filenames.
246 for sect, fields in zip(('insns', 'general'), (sect_insns, sect_general)):
247 if not config.has_section(sect):
248 config.add_section(sect)
Mike Frysinger0bdbc102019-06-13 15:27:29 -0400249 for k, v in fields.items():
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400250 config.set(sect, k, v)
251
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500252 # Now prune the alternative sections.
253 for alt in self.GetAltInsnSets():
254 config.remove_section(alt)
255
Mike Frysingerfd544572019-09-06 16:35:50 -0400256 output = StringIO()
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400257 config.write(output)
258 data = output.getvalue()
259 osutils.WriteFile(output_file, data)
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500260 logging.debug('generated insns file for %s:\n%s', self.image_type, data)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400261
262
263def MarkImageToBeSigned(ctx, tbs_base, insns_path, priority):
264 """Mark an instructions file for signing.
265
266 This will upload a file to the GS bucket flagging an image for signing by
267 the signers.
268
269 Args:
270 ctx: A viable gs.GSContext.
271 tbs_base: The full path to where the tobesigned directory lives.
272 insns_path: The path (relative to |tbs_base|) of the file to sign.
273 priority: Set the signing priority (lower == higher prio).
274
275 Returns:
276 The full path to the remote tobesigned file.
277 """
278 if priority < 0 or priority > 99:
279 raise ValueError('priority must be [0, 99] inclusive')
280
281 if insns_path.startswith(tbs_base):
282 insns_path = insns_path[len(tbs_base):].lstrip('/')
283
284 tbs_path = '%s/tobesigned/%02i,%s' % (tbs_base, priority,
285 insns_path.replace('/', ','))
286
Mike Frysinger6430d132014-10-27 23:43:30 -0400287 # The caller will catch gs.GSContextException for us.
288 ctx.Copy('-', tbs_path, input=cros_build_lib.MachineDetails())
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400289
290 return tbs_path
291
292
293def PushImage(src_path, board, versionrev=None, profile=None, priority=50,
Mike Frysinger77912102017-08-30 18:35:46 -0400294 sign_types=None, dry_run=False, mock=False, force_keysets=(),
Jack Neus485a9d22020-12-21 03:15:15 +0000295 force_channels=None, buildroot=constants.SOURCE_ROOT,
296 dest_bucket=constants.RELEASE_BUCKET):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400297 """Push the image from the archive bucket to the release bucket.
298
299 Args:
300 src_path: Where to copy the files from; can be a local path or gs:// URL.
301 Should be a full path to the artifacts in either case.
302 board: The board we're uploading artifacts for (e.g. $BOARD).
303 versionrev: The full Chromium OS version string (e.g. R34-5126.0.0).
304 profile: The board profile in use (e.g. "asan").
305 priority: Set the signing priority (lower == higher prio).
306 sign_types: If set, a set of types which we'll restrict ourselves to
307 signing. See the --sign-types option for more details.
308 dry_run: Show what would be done, but do not upload anything.
309 mock: Upload to a testing bucket rather than the real one.
Mike Frysingerdad40d62014-02-09 02:18:02 -0500310 force_keysets: Set of keysets to use rather than what the inputs say.
Mike Frysinger77912102017-08-30 18:35:46 -0400311 force_channels: Set of channels to use rather than what the inputs say.
Don Garrett3cf5f9a2018-08-14 13:14:47 -0700312 buildroot: Buildroot in which to look for signing instructions.
Jack Neus485a9d22020-12-21 03:15:15 +0000313 dest_bucket: Bucket to push results to.
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400314
315 Returns:
Don Garrett9459c2f2014-01-22 18:20:24 -0800316 A dictionary that maps 'channel' -> ['gs://signer_instruction_uri1',
317 'gs://signer_instruction_uri2',
318 ...]
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400319 """
Mike Frysinger4495b032014-03-05 17:24:03 -0500320 # Whether we hit an unknown error. If so, we'll throw an error, but only
321 # at the end (so that we still upload as many files as possible).
Amey Deshpandea936c622015-08-12 17:27:54 -0700322 # It's implemented using a list to deal with variable scopes in nested
323 # functions below.
324 unknown_error = [False]
Mike Frysinger4495b032014-03-05 17:24:03 -0500325
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400326 if versionrev is None:
327 # Extract milestone/version from the directory name.
328 versionrev = os.path.basename(src_path)
329
330 # We only support the latest format here. Older releases can use pushimage
331 # from the respective branch which deals with legacy cruft.
332 m = re.match(VERSION_REGEX, versionrev)
333 if not m:
334 raise ValueError('version %s does not match %s' %
335 (versionrev, VERSION_REGEX))
336 milestone = m.group(1)
337 version = m.group(2)
338
339 # Normalize board to always use dashes not underscores. This is mostly a
340 # historical artifact at this point, but we can't really break it since the
341 # value is used in URLs.
342 boardpath = board.replace('_', '-')
343 if profile is not None:
344 boardpath += '-%s' % profile.replace('_', '-')
345
346 ctx = gs.GSContext(dry_run=dry_run)
347
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400348 try:
Don Garrett3cf5f9a2018-08-14 13:14:47 -0700349 input_insns = InputInsns(board, buildroot=buildroot)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400350 except MissingBoardInstructions as e:
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500351 logging.warning('Missing base instruction file: %s', e)
Ralph Nathan446aee92015-03-23 14:44:56 -0700352 logging.warning('not uploading anything for signing')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400353 return
Mike Frysinger77912102017-08-30 18:35:46 -0400354
355 if force_channels is None:
356 channels = input_insns.GetChannels()
357 else:
358 # Filter out duplicates.
359 channels = sorted(set(force_channels))
Mike Frysingerdad40d62014-02-09 02:18:02 -0500360
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500361 # We want force_keysets as a set.
Mike Frysingerdad40d62014-02-09 02:18:02 -0500362 force_keysets = set(force_keysets)
Mike Frysingerdad40d62014-02-09 02:18:02 -0500363
364 if mock:
Ralph Nathan03047282015-03-23 11:09:32 -0700365 logging.info('Upload mode: mock; signers will not process anything')
Mike Frysingerdad40d62014-02-09 02:18:02 -0500366 tbs_base = gs_base = os.path.join(constants.TRASH_BUCKET, 'pushimage-tests',
367 getpass.getuser())
David Rileyf8205122015-09-04 13:46:36 -0700368 elif set(['%s-%s' % (TEST_KEYSET_PREFIX, x)
369 for x in TEST_KEYSETS]) & force_keysets:
Ralph Nathan03047282015-03-23 11:09:32 -0700370 logging.info('Upload mode: test; signers will process test keys')
Mike Frysingerdad40d62014-02-09 02:18:02 -0500371 # We need the tbs_base to be in the place the signer will actually scan.
372 tbs_base = TEST_SIGN_BUCKET_BASE
373 gs_base = os.path.join(tbs_base, getpass.getuser())
374 else:
Ralph Nathan03047282015-03-23 11:09:32 -0700375 logging.info('Upload mode: normal; signers will process the images')
Jack Neus485a9d22020-12-21 03:15:15 +0000376 tbs_base = gs_base = dest_bucket
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400377
378 sect_general = {
379 'config_board': board,
380 'board': boardpath,
381 'version': version,
382 'versionrev': versionrev,
383 'milestone': milestone,
384 }
385 sect_insns = {}
386
387 if dry_run:
Ralph Nathan03047282015-03-23 11:09:32 -0700388 logging.info('DRY RUN MODE ACTIVE: NOTHING WILL BE UPLOADED')
389 logging.info('Signing for channels: %s', ' '.join(channels))
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400390
Don Garrett9459c2f2014-01-22 18:20:24 -0800391 instruction_urls = {}
392
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400393 def _ImageNameBase(image_type=None):
394 lmid = ('%s-' % image_type) if image_type else ''
395 return 'ChromeOS-%s%s-%s' % (lmid, versionrev, boardpath)
396
Amey Deshpandea936c622015-08-12 17:27:54 -0700397 # These variables are defined outside the loop so that the nested functions
398 # below can access them without 'cell-var-from-loop' linter warning.
Mike Frysinger80de5012019-08-01 14:10:53 -0400399 dst_path = ''
Amey Deshpandea936c622015-08-12 17:27:54 -0700400 files_to_sign = []
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400401 for channel in channels:
Ralph Nathan5a582ff2015-03-20 18:18:30 -0700402 logging.debug('\n\n#### CHANNEL: %s ####\n', channel)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400403 sect_insns['channel'] = channel
404 sub_path = '%s-channel/%s/%s' % (channel, boardpath, version)
405 dst_path = '%s/%s' % (gs_base, sub_path)
Ralph Nathan03047282015-03-23 11:09:32 -0700406 logging.info('Copying images to %s', dst_path)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400407
Amey Deshpandea936c622015-08-12 17:27:54 -0700408 recovery_basename = _ImageNameBase(constants.IMAGE_TYPE_RECOVERY)
409 factory_basename = _ImageNameBase(constants.IMAGE_TYPE_FACTORY)
410 firmware_basename = _ImageNameBase(constants.IMAGE_TYPE_FIRMWARE)
Vincent Palatind599c662015-10-26 09:51:41 -0700411 acc_usbpd_basename = _ImageNameBase(constants.IMAGE_TYPE_ACCESSORY_USBPD)
412 acc_rwsig_basename = _ImageNameBase(constants.IMAGE_TYPE_ACCESSORY_RWSIG)
Vadim Bendeburyfe37f282020-11-04 19:05:49 -0800413 gsc_firmware_basename = _ImageNameBase(constants.IMAGE_TYPE_GSC_FIRMWARE)
Amey Deshpandea936c622015-08-12 17:27:54 -0700414 test_basename = _ImageNameBase(constants.IMAGE_TYPE_TEST)
415 base_basename = _ImageNameBase(constants.IMAGE_TYPE_BASE)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400416 hwqual_tarball = 'chromeos-hwqual-%s-%s.tar.bz2' % (board, versionrev)
417
Amey Deshpandea936c622015-08-12 17:27:54 -0700418 # The following build artifacts, if present, are always copied regardless of
419 # requested signing types.
420 files_to_copy_only = (
421 # (<src>, <dst>, <suffix>),
422 ('image.zip', _ImageNameBase(), 'zip'),
423 (constants.TEST_IMAGE_TAR, test_basename, 'tar.xz'),
424 ('debug.tgz', 'debug-%s' % boardpath, 'tgz'),
Xiaochu Liu254e0dd2019-03-08 16:10:57 -0800425 (hwqual_tarball, None, None),
426 ('stateful.tgz', None, None),
427 ('dlc', None, None),
Amin Hassani21e0ed12019-11-04 14:24:36 -0800428 (constants.QUICK_PROVISION_PAYLOAD_KERNEL, None, None),
429 (constants.QUICK_PROVISION_PAYLOAD_ROOTFS, None, None),
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400430 )
Amey Deshpandea936c622015-08-12 17:27:54 -0700431
432 # The following build artifacts, if present, are always copied.
433 # If |sign_types| is None, all of them are marked for signing, otherwise
434 # only the image types specified in |sign_types| are marked for signing.
435 files_to_copy_and_maybe_sign = (
436 # (<src>, <dst>, <suffix>, <signing type>),
437 (constants.RECOVERY_IMAGE_TAR, recovery_basename, 'tar.xz',
438 constants.IMAGE_TYPE_RECOVERY),
439
440 ('factory_image.zip', factory_basename, 'zip',
441 constants.IMAGE_TYPE_FACTORY),
442
443 ('firmware_from_source.tar.bz2', firmware_basename, 'tar.bz2',
444 constants.IMAGE_TYPE_FIRMWARE),
David Rileya04d19d2015-09-04 16:11:50 -0700445
Vincent Palatind599c662015-10-26 09:51:41 -0700446 ('firmware_from_source.tar.bz2', acc_usbpd_basename, 'tar.bz2',
447 constants.IMAGE_TYPE_ACCESSORY_USBPD),
448
449 ('firmware_from_source.tar.bz2', acc_rwsig_basename, 'tar.bz2',
450 constants.IMAGE_TYPE_ACCESSORY_RWSIG),
LaMont Jones7d6c98f2019-09-27 12:37:33 -0600451
Vadim Bendeburyfe37f282020-11-04 19:05:49 -0800452 ('firmware_from_source.tar.bz2', gsc_firmware_basename, 'tar.bz2',
453 constants.IMAGE_TYPE_GSC_FIRMWARE),
Amey Deshpandea936c622015-08-12 17:27:54 -0700454 )
455
456 # The following build artifacts are copied and marked for signing, if
457 # they are present *and* if the image type is specified via |sign_types|.
458 files_to_maybe_copy_and_sign = (
459 # (<src>, <dst>, <suffix>, <signing type>),
460 (constants.BASE_IMAGE_TAR, base_basename, 'tar.xz',
461 constants.IMAGE_TYPE_BASE),
462 )
463
Xiaochu Liu254e0dd2019-03-08 16:10:57 -0800464 def _CopyFileToGS(src, dst=None, suffix=None):
Amey Deshpandea936c622015-08-12 17:27:54 -0700465 """Returns |dst| file name if the copying was successful."""
Xiaochu Liu254e0dd2019-03-08 16:10:57 -0800466 if dst is None:
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400467 dst = src
Xiaochu Liu254e0dd2019-03-08 16:10:57 -0800468 elif suffix is not None:
Amey Deshpandea936c622015-08-12 17:27:54 -0700469 dst = '%s.%s' % (dst, suffix)
470 success = False
Mike Frysingere51a2652014-01-18 02:36:16 -0500471 try:
Xiaochu Liu254e0dd2019-03-08 16:10:57 -0800472 ctx.Copy(os.path.join(src_path, src), os.path.join(dst_path, dst),
473 recursive=True)
Amey Deshpandea936c622015-08-12 17:27:54 -0700474 success = True
Mike Frysingere51a2652014-01-18 02:36:16 -0500475 except gs.GSNoSuchKey:
Ralph Nathan446aee92015-03-23 14:44:56 -0700476 logging.warning('Skipping %s as it does not exist', src)
Mike Frysinger4495b032014-03-05 17:24:03 -0500477 except gs.GSContextException:
Amey Deshpandea936c622015-08-12 17:27:54 -0700478 unknown_error[0] = True
Ralph Nathan59900422015-03-24 10:41:17 -0700479 logging.error('Skipping %s due to unknown GS error', src, exc_info=True)
Amey Deshpandea936c622015-08-12 17:27:54 -0700480 return dst if success else None
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400481
Amey Deshpandea936c622015-08-12 17:27:54 -0700482 for src, dst, suffix in files_to_copy_only:
483 _CopyFileToGS(src, dst, suffix)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400484
Amey Deshpandea936c622015-08-12 17:27:54 -0700485 # Clear the list of files to sign before adding new artifacts.
486 files_to_sign = []
487
488 def _AddToFilesToSign(image_type, dst, suffix):
489 assert dst.endswith('.' + suffix), (
490 'dst: %s, suffix: %s' % (dst, suffix))
491 dst_base = dst[:-(len(suffix) + 1)]
492 files_to_sign.append([image_type, dst_base, suffix])
493
494 for src, dst, suffix, image_type in files_to_copy_and_maybe_sign:
495 dst = _CopyFileToGS(src, dst, suffix)
496 if dst and (not sign_types or image_type in sign_types):
497 _AddToFilesToSign(image_type, dst, suffix)
498
499 for src, dst, suffix, image_type in files_to_maybe_copy_and_sign:
500 if sign_types and image_type in sign_types:
501 dst = _CopyFileToGS(src, dst, suffix)
502 if dst:
503 _AddToFilesToSign(image_type, dst, suffix)
504
505 logging.debug('Files to sign: %s', files_to_sign)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400506 # Now go through the subset for signing.
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500507 for image_type, dst_name, suffix in files_to_sign:
508 try:
Don Garrett3cf5f9a2018-08-14 13:14:47 -0700509 input_insns = InputInsns(board, image_type=image_type,
510 buildroot=buildroot)
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500511 except MissingBoardInstructions as e:
512 logging.info('Nothing to sign: %s', e)
513 continue
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400514
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500515 dst_archive = '%s.%s' % (dst_name, suffix)
516 sect_general['archive'] = dst_archive
517 sect_general['type'] = image_type
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400518
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500519 # In the default/automatic mode, only flag files for signing if the
520 # archives were actually uploaded in a previous stage. This additional
521 # check can be removed in future once |sign_types| becomes a required
522 # argument.
523 # TODO: Make |sign_types| a required argument.
524 gs_artifact_path = os.path.join(dst_path, dst_archive)
525 exists = False
526 try:
527 exists = ctx.Exists(gs_artifact_path)
528 except gs.GSContextException:
529 unknown_error[0] = True
530 logging.error('Unknown error while checking %s', gs_artifact_path,
531 exc_info=True)
532 if not exists:
533 logging.info('%s does not exist. Nothing to sign.',
534 gs_artifact_path)
535 continue
536
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500537 first_image = True
538 for alt_insn_set in input_insns.GetAltInsnSets():
539 # Figure out which keysets have been requested for this type.
540 # We sort the forced set so tests/runtime behavior is stable.
541 keysets = sorted(force_keysets)
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500542 if not keysets:
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500543 keysets = input_insns.GetKeysets(insns_merge=alt_insn_set)
544 if not keysets:
545 logging.warning('Skipping %s image signing due to no keysets',
546 image_type)
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500547
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500548 for keyset in keysets:
549 sect_insns['keyset'] = keyset
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400550
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500551 # Generate the insn file for this artifact that the signer will use,
552 # and flag it for signing.
Mike Frysinger59babdb2019-09-06 06:25:50 -0400553 with cros_build_lib.UnbufferedNamedTemporaryFile(
554 prefix='pushimage.insns.') as insns_path:
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500555 input_insns.OutputInsns(insns_path.name, sect_insns, sect_general,
556 insns_merge=alt_insn_set)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400557
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500558 gs_insns_path = '%s/%s' % (dst_path, dst_name)
559 if not first_image:
560 gs_insns_path += '-%s' % keyset
561 first_image = False
562 gs_insns_path += '.instructions'
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400563
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500564 try:
565 ctx.Copy(insns_path.name, gs_insns_path)
566 except gs.GSContextException:
567 unknown_error[0] = True
568 logging.error('Unknown error while uploading insns %s',
569 gs_insns_path, exc_info=True)
570 continue
Mike Frysinger4495b032014-03-05 17:24:03 -0500571
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500572 try:
573 MarkImageToBeSigned(ctx, tbs_base, gs_insns_path, priority)
574 except gs.GSContextException:
575 unknown_error[0] = True
576 logging.error('Unknown error while marking for signing %s',
577 gs_insns_path, exc_info=True)
578 continue
579 logging.info('Signing %s image with keyset %s at %s', image_type,
580 keyset, gs_insns_path)
581 instruction_urls.setdefault(channel, []).append(gs_insns_path)
Don Garrett9459c2f2014-01-22 18:20:24 -0800582
Amey Deshpandea936c622015-08-12 17:27:54 -0700583 if unknown_error[0]:
Mike Frysinger4495b032014-03-05 17:24:03 -0500584 raise PushError('hit some unknown error(s)', instruction_urls)
585
Don Garrett9459c2f2014-01-22 18:20:24 -0800586 return instruction_urls
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400587
588
Mike Frysinger26144192017-08-30 18:26:46 -0400589def GetParser():
590 """Creates the argparse parser."""
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400591 parser = commandline.ArgumentParser(description=__doc__)
592
593 # The type of image_dir will strip off trailing slashes (makes later
594 # processing simpler and the display prettier).
595 parser.add_argument('image_dir', default=None, type='local_or_gs_path',
596 help='full path of source artifacts to upload')
597 parser.add_argument('--board', default=None, required=True,
598 help='board to generate symbols for')
599 parser.add_argument('--profile', default=None,
600 help='board profile in use (e.g. "asan")')
601 parser.add_argument('--version', default=None,
602 help='version info (normally extracted from image_dir)')
Mike Frysinger77912102017-08-30 18:35:46 -0400603 parser.add_argument('--channels', default=None, action='split_extend',
604 help='override list of channels to process')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400605 parser.add_argument('-n', '--dry-run', default=False, action='store_true',
606 help='show what would be done, but do not upload')
607 parser.add_argument('-M', '--mock', default=False, action='store_true',
608 help='upload things to a testing bucket (dev testing)')
David Rileyf8205122015-09-04 13:46:36 -0700609 parser.add_argument('--test-sign', default=[], action='append',
610 choices=TEST_KEYSETS,
611 help='mung signing behavior to sign w/ test keys')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400612 parser.add_argument('--priority', type=int, default=50,
613 help='set signing priority (lower == higher prio)')
614 parser.add_argument('--sign-types', default=None, nargs='+',
Amey Deshpandea936c622015-08-12 17:27:54 -0700615 choices=_SUPPORTED_IMAGE_TYPES,
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400616 help='only sign specified image types')
Don Garrett3cf5f9a2018-08-14 13:14:47 -0700617 parser.add_argument('--buildroot', default=constants.SOURCE_ROOT, type='path',
618 help='Buildroot to use. Defaults to current.')
Mike Frysinger09fe0122014-02-09 02:44:05 -0500619 parser.add_argument('--yes', action='store_true', default=False,
620 help='answer yes to all prompts')
Jack Neus485a9d22020-12-21 03:15:15 +0000621 parser.add_argument('--dest-bucket', default=constants.RELEASE_BUCKET,
622 help='dest bucket. Default to %(default)s')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400623
Mike Frysinger26144192017-08-30 18:26:46 -0400624 return parser
625
626
627def main(argv):
628 parser = GetParser()
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400629 opts = parser.parse_args(argv)
630 opts.Freeze()
631
David Rileyf8205122015-09-04 13:46:36 -0700632 force_keysets = set(['%s-%s' % (TEST_KEYSET_PREFIX, x)
633 for x in opts.test_sign])
Mike Frysingerdad40d62014-02-09 02:18:02 -0500634
Mike Frysinger09fe0122014-02-09 02:44:05 -0500635 # If we aren't using mock or test or dry run mode, then let's prompt the user
636 # to make sure they actually want to do this. It's rare that people want to
637 # run this directly and hit the release bucket.
638 if not (opts.mock or force_keysets or opts.dry_run) and not opts.yes:
639 prolog = '\n'.join(textwrap.wrap(textwrap.dedent(
640 'Uploading images for signing to the *release* bucket is not something '
641 'you generally should be doing yourself.'), 80)).strip()
642 if not cros_build_lib.BooleanPrompt(
643 prompt='Are you sure you want to sign these images',
644 default=False, prolog=prolog):
645 cros_build_lib.Die('better safe than sorry')
646
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400647 PushImage(opts.image_dir, opts.board, versionrev=opts.version,
648 profile=opts.profile, priority=opts.priority,
Mike Frysingerdad40d62014-02-09 02:18:02 -0500649 sign_types=opts.sign_types, dry_run=opts.dry_run, mock=opts.mock,
Don Garrett3cf5f9a2018-08-14 13:14:47 -0700650 force_keysets=force_keysets, force_channels=opts.channels,
Jack Neus485a9d22020-12-21 03:15:15 +0000651 buildroot=opts.buildroot, dest_bucket=opts.dest_bucket)