blob: 07d03e3afc8f8621c97e963299c8868ed2d8bfc1 [file] [log] [blame]
Mike Frysingerd13faeb2013-09-05 16:00:46 -04001# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""ChromeOS image pusher (from cbuildbot to signer).
6
7This pushes files from the archive bucket to the signer bucket and marks
8artifacts for signing (which a signing process will look for).
9"""
10
11from __future__ import print_function
12
13import ConfigParser
14import cStringIO
Mike Frysingerd13faeb2013-09-05 16:00:46 -040015import getpass
16import os
17import re
18import tempfile
Mike Frysinger09fe0122014-02-09 02:44:05 -050019import textwrap
Mike Frysingerd13faeb2013-09-05 16:00:46 -040020
Don Garrett88b8d782014-05-13 17:30:55 -070021from chromite.cbuildbot import constants
Mike Frysingerd13faeb2013-09-05 16:00:46 -040022from chromite.lib import commandline
23from chromite.lib import cros_build_lib
Ralph Nathan5a582ff2015-03-20 18:18:30 -070024from chromite.lib import cros_logging as logging
Mike Frysingerd13faeb2013-09-05 16:00:46 -040025from chromite.lib import gs
26from chromite.lib import osutils
27from chromite.lib import signing
28
29
30# This will split a fully qualified ChromeOS version string up.
31# R34-5126.0.0 will break into "34" and "5126.0.0".
32VERSION_REGEX = r'^R([0-9]+)-([^-]+)'
33
Mike Frysingerdad40d62014-02-09 02:18:02 -050034# The test signers will scan this dir looking for test work.
35# Keep it in sync with the signer config files [gs_test_buckets].
36TEST_SIGN_BUCKET_BASE = 'gs://chromeos-throw-away-bucket/signer-tests'
37
David Rileyf8205122015-09-04 13:46:36 -070038# Keysets that are only valid in the above test bucket.
39TEST_KEYSET_PREFIX = 'test-keys'
40TEST_KEYSETS = set((
41 'mp',
42 'premp',
43 'nvidia-premp',
44))
Mike Frysingerdad40d62014-02-09 02:18:02 -050045
Amey Deshpandea936c622015-08-12 17:27:54 -070046# Supported image types for signing.
47_SUPPORTED_IMAGE_TYPES = (
48 constants.IMAGE_TYPE_RECOVERY,
49 constants.IMAGE_TYPE_FACTORY,
50 constants.IMAGE_TYPE_FIRMWARE,
51 constants.IMAGE_TYPE_BASE,
52)
53
Mike Frysingerd13faeb2013-09-05 16:00:46 -040054
Mike Frysinger4495b032014-03-05 17:24:03 -050055class PushError(Exception):
56 """When an (unknown) error happened while trying to push artifacts."""
57
58
Mike Frysingerd13faeb2013-09-05 16:00:46 -040059class MissingBoardInstructions(Exception):
60 """Raised when a board lacks any signer instructions."""
61
62
63class InputInsns(object):
64 """Object to hold settings for a signable board.
65
66 Note: The format of the instruction file pushimage outputs (and the signer
67 reads) is not exactly the same as the instruction file pushimage reads.
68 """
69
70 def __init__(self, board):
71 self.board = board
72
73 config = ConfigParser.ConfigParser()
74 config.readfp(open(self.GetInsnFile('DEFAULT')))
Amey Deshpandea936c622015-08-12 17:27:54 -070075 # What pushimage internally refers to as 'recovery', are the basic signing
76 # instructions in practice, and other types are stacked on top.
77 input_insns = self.GetInsnFile(constants.IMAGE_TYPE_RECOVERY)
78 if not os.path.exists(input_insns):
79 # This board doesn't have any signing instructions.
80 raise MissingBoardInstructions(self.board)
81 config.readfp(open(input_insns))
Mike Frysingerd13faeb2013-09-05 16:00:46 -040082 self.cfg = config
83
84 def GetInsnFile(self, image_type):
85 """Find the signer instruction files for this board/image type.
86
87 Args:
88 image_type: The type of instructions to load. It can be a common file
89 (like "DEFAULT"), or one of the --sign-types.
90
91 Returns:
92 Full path to the instruction file using |image_type| and |self.board|.
93 """
94 if image_type == image_type.upper():
95 name = image_type
Amey Deshpandea936c622015-08-12 17:27:54 -070096 elif image_type in (constants.IMAGE_TYPE_RECOVERY,
97 constants.IMAGE_TYPE_BASE):
Mike Frysingerd13faeb2013-09-05 16:00:46 -040098 name = self.board
99 else:
100 name = '%s.%s' % (self.board, image_type)
101
102 return os.path.join(signing.INPUT_INSN_DIR, '%s.instructions' % name)
103
104 @staticmethod
105 def SplitCfgField(val):
106 """Split a string into multiple elements.
107
108 This centralizes our convention for multiple elements in the input files
109 being delimited by either a space or comma.
110
111 Args:
112 val: The string to split.
113
114 Returns:
115 The list of elements from having done split the string.
116 """
117 return val.replace(',', ' ').split()
118
119 def GetChannels(self):
120 """Return the list of channels to sign for this board.
121
122 If the board-specific config doesn't specify a preference, we'll use the
123 common settings.
124 """
125 return self.SplitCfgField(self.cfg.get('insns', 'channel'))
126
127 def GetKeysets(self):
128 """Return the list of keysets to sign for this board."""
129 return self.SplitCfgField(self.cfg.get('insns', 'keyset'))
130
131 def OutputInsns(self, image_type, output_file, sect_insns, sect_general):
132 """Generate the output instruction file for sending to the signer.
133
134 Note: The format of the instruction file pushimage outputs (and the signer
135 reads) is not exactly the same as the instruction file pushimage reads.
136
137 Args:
138 image_type: The type of image we will be signing (see --sign-types).
139 output_file: The file to write the new instruction file to.
140 sect_insns: Items to set/override in the [insns] section.
141 sect_general: Items to set/override in the [general] section.
142 """
143 config = ConfigParser.ConfigParser()
144 config.readfp(open(self.GetInsnFile(image_type)))
145
146 # Clear channel entry in instructions file, ensuring we only get
147 # one channel for the signer to look at. Then provide all the
148 # other details for this signing request to avoid any ambiguity
149 # and to avoid relying on encoding data into filenames.
150 for sect, fields in zip(('insns', 'general'), (sect_insns, sect_general)):
151 if not config.has_section(sect):
152 config.add_section(sect)
153 for k, v in fields.iteritems():
154 config.set(sect, k, v)
155
156 output = cStringIO.StringIO()
157 config.write(output)
158 data = output.getvalue()
159 osutils.WriteFile(output_file, data)
Ralph Nathan5a582ff2015-03-20 18:18:30 -0700160 logging.debug('generated insns file for %s:\n%s', image_type, data)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400161
162
163def MarkImageToBeSigned(ctx, tbs_base, insns_path, priority):
164 """Mark an instructions file for signing.
165
166 This will upload a file to the GS bucket flagging an image for signing by
167 the signers.
168
169 Args:
170 ctx: A viable gs.GSContext.
171 tbs_base: The full path to where the tobesigned directory lives.
172 insns_path: The path (relative to |tbs_base|) of the file to sign.
173 priority: Set the signing priority (lower == higher prio).
174
175 Returns:
176 The full path to the remote tobesigned file.
177 """
178 if priority < 0 or priority > 99:
179 raise ValueError('priority must be [0, 99] inclusive')
180
181 if insns_path.startswith(tbs_base):
182 insns_path = insns_path[len(tbs_base):].lstrip('/')
183
184 tbs_path = '%s/tobesigned/%02i,%s' % (tbs_base, priority,
185 insns_path.replace('/', ','))
186
Mike Frysinger6430d132014-10-27 23:43:30 -0400187 # The caller will catch gs.GSContextException for us.
188 ctx.Copy('-', tbs_path, input=cros_build_lib.MachineDetails())
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400189
190 return tbs_path
191
192
193def PushImage(src_path, board, versionrev=None, profile=None, priority=50,
Mike Frysingerdad40d62014-02-09 02:18:02 -0500194 sign_types=None, dry_run=False, mock=False, force_keysets=()):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400195 """Push the image from the archive bucket to the release bucket.
196
197 Args:
198 src_path: Where to copy the files from; can be a local path or gs:// URL.
199 Should be a full path to the artifacts in either case.
200 board: The board we're uploading artifacts for (e.g. $BOARD).
201 versionrev: The full Chromium OS version string (e.g. R34-5126.0.0).
202 profile: The board profile in use (e.g. "asan").
203 priority: Set the signing priority (lower == higher prio).
204 sign_types: If set, a set of types which we'll restrict ourselves to
205 signing. See the --sign-types option for more details.
206 dry_run: Show what would be done, but do not upload anything.
207 mock: Upload to a testing bucket rather than the real one.
Mike Frysingerdad40d62014-02-09 02:18:02 -0500208 force_keysets: Set of keysets to use rather than what the inputs say.
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400209
210 Returns:
Don Garrett9459c2f2014-01-22 18:20:24 -0800211 A dictionary that maps 'channel' -> ['gs://signer_instruction_uri1',
212 'gs://signer_instruction_uri2',
213 ...]
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400214 """
Mike Frysinger4495b032014-03-05 17:24:03 -0500215 # Whether we hit an unknown error. If so, we'll throw an error, but only
216 # at the end (so that we still upload as many files as possible).
Amey Deshpandea936c622015-08-12 17:27:54 -0700217 # It's implemented using a list to deal with variable scopes in nested
218 # functions below.
219 unknown_error = [False]
Mike Frysinger4495b032014-03-05 17:24:03 -0500220
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400221 if versionrev is None:
222 # Extract milestone/version from the directory name.
223 versionrev = os.path.basename(src_path)
224
225 # We only support the latest format here. Older releases can use pushimage
226 # from the respective branch which deals with legacy cruft.
227 m = re.match(VERSION_REGEX, versionrev)
228 if not m:
229 raise ValueError('version %s does not match %s' %
230 (versionrev, VERSION_REGEX))
231 milestone = m.group(1)
232 version = m.group(2)
233
234 # Normalize board to always use dashes not underscores. This is mostly a
235 # historical artifact at this point, but we can't really break it since the
236 # value is used in URLs.
237 boardpath = board.replace('_', '-')
238 if profile is not None:
239 boardpath += '-%s' % profile.replace('_', '-')
240
241 ctx = gs.GSContext(dry_run=dry_run)
242
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400243 try:
244 input_insns = InputInsns(board)
245 except MissingBoardInstructions as e:
Ralph Nathan446aee92015-03-23 14:44:56 -0700246 logging.warning('board "%s" is missing base instruction file: %s', board, e)
247 logging.warning('not uploading anything for signing')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400248 return
249 channels = input_insns.GetChannels()
Mike Frysingerdad40d62014-02-09 02:18:02 -0500250
251 # We want force_keysets as a set, and keysets as a list.
252 force_keysets = set(force_keysets)
253 keysets = list(force_keysets) if force_keysets else input_insns.GetKeysets()
254
255 if mock:
Ralph Nathan03047282015-03-23 11:09:32 -0700256 logging.info('Upload mode: mock; signers will not process anything')
Mike Frysingerdad40d62014-02-09 02:18:02 -0500257 tbs_base = gs_base = os.path.join(constants.TRASH_BUCKET, 'pushimage-tests',
258 getpass.getuser())
David Rileyf8205122015-09-04 13:46:36 -0700259 elif set(['%s-%s' % (TEST_KEYSET_PREFIX, x)
260 for x in TEST_KEYSETS]) & force_keysets:
Ralph Nathan03047282015-03-23 11:09:32 -0700261 logging.info('Upload mode: test; signers will process test keys')
Mike Frysingerdad40d62014-02-09 02:18:02 -0500262 # We need the tbs_base to be in the place the signer will actually scan.
263 tbs_base = TEST_SIGN_BUCKET_BASE
264 gs_base = os.path.join(tbs_base, getpass.getuser())
265 else:
Ralph Nathan03047282015-03-23 11:09:32 -0700266 logging.info('Upload mode: normal; signers will process the images')
Mike Frysingerdad40d62014-02-09 02:18:02 -0500267 tbs_base = gs_base = constants.RELEASE_BUCKET
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400268
269 sect_general = {
270 'config_board': board,
271 'board': boardpath,
272 'version': version,
273 'versionrev': versionrev,
274 'milestone': milestone,
275 }
276 sect_insns = {}
277
278 if dry_run:
Ralph Nathan03047282015-03-23 11:09:32 -0700279 logging.info('DRY RUN MODE ACTIVE: NOTHING WILL BE UPLOADED')
280 logging.info('Signing for channels: %s', ' '.join(channels))
281 logging.info('Signing for keysets : %s', ' '.join(keysets))
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400282
Don Garrett9459c2f2014-01-22 18:20:24 -0800283 instruction_urls = {}
284
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400285 def _ImageNameBase(image_type=None):
286 lmid = ('%s-' % image_type) if image_type else ''
287 return 'ChromeOS-%s%s-%s' % (lmid, versionrev, boardpath)
288
Amey Deshpandea936c622015-08-12 17:27:54 -0700289 # These variables are defined outside the loop so that the nested functions
290 # below can access them without 'cell-var-from-loop' linter warning.
291 dst_path = ""
292 files_to_sign = []
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400293 for channel in channels:
Ralph Nathan5a582ff2015-03-20 18:18:30 -0700294 logging.debug('\n\n#### CHANNEL: %s ####\n', channel)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400295 sect_insns['channel'] = channel
296 sub_path = '%s-channel/%s/%s' % (channel, boardpath, version)
297 dst_path = '%s/%s' % (gs_base, sub_path)
Ralph Nathan03047282015-03-23 11:09:32 -0700298 logging.info('Copying images to %s', dst_path)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400299
Amey Deshpandea936c622015-08-12 17:27:54 -0700300 recovery_basename = _ImageNameBase(constants.IMAGE_TYPE_RECOVERY)
301 factory_basename = _ImageNameBase(constants.IMAGE_TYPE_FACTORY)
302 firmware_basename = _ImageNameBase(constants.IMAGE_TYPE_FIRMWARE)
303 test_basename = _ImageNameBase(constants.IMAGE_TYPE_TEST)
304 base_basename = _ImageNameBase(constants.IMAGE_TYPE_BASE)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400305 hwqual_tarball = 'chromeos-hwqual-%s-%s.tar.bz2' % (board, versionrev)
306
Amey Deshpandea936c622015-08-12 17:27:54 -0700307 # The following build artifacts, if present, are always copied regardless of
308 # requested signing types.
309 files_to_copy_only = (
310 # (<src>, <dst>, <suffix>),
311 ('image.zip', _ImageNameBase(), 'zip'),
312 (constants.TEST_IMAGE_TAR, test_basename, 'tar.xz'),
313 ('debug.tgz', 'debug-%s' % boardpath, 'tgz'),
314 (hwqual_tarball, '', ''),
315 ('au-generator.zip', '', ''),
316 ('stateful.tgz', '', ''),
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400317 )
Amey Deshpandea936c622015-08-12 17:27:54 -0700318
319 # The following build artifacts, if present, are always copied.
320 # If |sign_types| is None, all of them are marked for signing, otherwise
321 # only the image types specified in |sign_types| are marked for signing.
322 files_to_copy_and_maybe_sign = (
323 # (<src>, <dst>, <suffix>, <signing type>),
324 (constants.RECOVERY_IMAGE_TAR, recovery_basename, 'tar.xz',
325 constants.IMAGE_TYPE_RECOVERY),
326
327 ('factory_image.zip', factory_basename, 'zip',
328 constants.IMAGE_TYPE_FACTORY),
329
330 ('firmware_from_source.tar.bz2', firmware_basename, 'tar.bz2',
331 constants.IMAGE_TYPE_FIRMWARE),
332 )
333
334 # The following build artifacts are copied and marked for signing, if
335 # they are present *and* if the image type is specified via |sign_types|.
336 files_to_maybe_copy_and_sign = (
337 # (<src>, <dst>, <suffix>, <signing type>),
338 (constants.BASE_IMAGE_TAR, base_basename, 'tar.xz',
339 constants.IMAGE_TYPE_BASE),
340 )
341
342 def _CopyFileToGS(src, dst, suffix):
343 """Returns |dst| file name if the copying was successful."""
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400344 if not dst:
345 dst = src
Amey Deshpandea936c622015-08-12 17:27:54 -0700346 elif suffix:
347 dst = '%s.%s' % (dst, suffix)
348 success = False
Mike Frysingere51a2652014-01-18 02:36:16 -0500349 try:
350 ctx.Copy(os.path.join(src_path, src), os.path.join(dst_path, dst))
Amey Deshpandea936c622015-08-12 17:27:54 -0700351 success = True
Mike Frysingere51a2652014-01-18 02:36:16 -0500352 except gs.GSNoSuchKey:
Ralph Nathan446aee92015-03-23 14:44:56 -0700353 logging.warning('Skipping %s as it does not exist', src)
Mike Frysinger4495b032014-03-05 17:24:03 -0500354 except gs.GSContextException:
Amey Deshpandea936c622015-08-12 17:27:54 -0700355 unknown_error[0] = True
Ralph Nathan59900422015-03-24 10:41:17 -0700356 logging.error('Skipping %s due to unknown GS error', src, exc_info=True)
Amey Deshpandea936c622015-08-12 17:27:54 -0700357 return dst if success else None
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400358
Amey Deshpandea936c622015-08-12 17:27:54 -0700359 for src, dst, suffix in files_to_copy_only:
360 _CopyFileToGS(src, dst, suffix)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400361
Amey Deshpandea936c622015-08-12 17:27:54 -0700362 # Clear the list of files to sign before adding new artifacts.
363 files_to_sign = []
364
365 def _AddToFilesToSign(image_type, dst, suffix):
366 assert dst.endswith('.' + suffix), (
367 'dst: %s, suffix: %s' % (dst, suffix))
368 dst_base = dst[:-(len(suffix) + 1)]
369 files_to_sign.append([image_type, dst_base, suffix])
370
371 for src, dst, suffix, image_type in files_to_copy_and_maybe_sign:
372 dst = _CopyFileToGS(src, dst, suffix)
373 if dst and (not sign_types or image_type in sign_types):
374 _AddToFilesToSign(image_type, dst, suffix)
375
376 for src, dst, suffix, image_type in files_to_maybe_copy_and_sign:
377 if sign_types and image_type in sign_types:
378 dst = _CopyFileToGS(src, dst, suffix)
379 if dst:
380 _AddToFilesToSign(image_type, dst, suffix)
381
382 logging.debug('Files to sign: %s', files_to_sign)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400383 # Now go through the subset for signing.
384 for keyset in keysets:
Ralph Nathan5a582ff2015-03-20 18:18:30 -0700385 logging.debug('\n\n#### KEYSET: %s ####\n', keyset)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400386 sect_insns['keyset'] = keyset
387 for image_type, dst_name, suffix in files_to_sign:
Amey Deshpandea936c622015-08-12 17:27:54 -0700388 dst_archive = '%s.%s' % (dst_name, suffix)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400389 sect_general['archive'] = dst_archive
390 sect_general['type'] = image_type
391
Amey Deshpandea936c622015-08-12 17:27:54 -0700392 # In the default/automatic mode, only flag files for signing if the
393 # archives were actually uploaded in a previous stage. This additional
394 # check can be removed in future once |sign_types| becomes a required
395 # argument.
396 # TODO: Make |sign_types| a required argument.
397 gs_artifact_path = os.path.join(dst_path, dst_archive)
398 exists = False
399 try:
400 exists = ctx.Exists(gs_artifact_path)
401 except gs.GSContextException:
402 unknown_error[0] = True
403 logging.error('Unknown error while checking %s', gs_artifact_path,
404 exc_info=True)
405 if not exists:
406 logging.info('%s does not exist. Nothing to sign.',
407 gs_artifact_path)
408 continue
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400409
410 input_insn_path = input_insns.GetInsnFile(image_type)
411 if not os.path.exists(input_insn_path):
Ralph Nathan03047282015-03-23 11:09:32 -0700412 logging.info('%s does not exist. Nothing to sign.', input_insn_path)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400413 continue
414
415 # Generate the insn file for this artifact that the signer will use,
416 # and flag it for signing.
417 with tempfile.NamedTemporaryFile(
418 bufsize=0, prefix='pushimage.insns.') as insns_path:
419 input_insns.OutputInsns(image_type, insns_path.name, sect_insns,
420 sect_general)
421
422 gs_insns_path = '%s/%s' % (dst_path, dst_name)
423 if keyset != keysets[0]:
424 gs_insns_path += '-%s' % keyset
425 gs_insns_path += '.instructions'
426
Mike Frysinger4495b032014-03-05 17:24:03 -0500427 try:
428 ctx.Copy(insns_path.name, gs_insns_path)
429 except gs.GSContextException:
Amey Deshpandea936c622015-08-12 17:27:54 -0700430 unknown_error[0] = True
Ralph Nathan59900422015-03-24 10:41:17 -0700431 logging.error('Unknown error while uploading insns %s',
432 gs_insns_path, exc_info=True)
Mike Frysinger4495b032014-03-05 17:24:03 -0500433 continue
434
435 try:
436 MarkImageToBeSigned(ctx, tbs_base, gs_insns_path, priority)
437 except gs.GSContextException:
Amey Deshpandea936c622015-08-12 17:27:54 -0700438 unknown_error[0] = True
Ralph Nathan59900422015-03-24 10:41:17 -0700439 logging.error('Unknown error while marking for signing %s',
440 gs_insns_path, exc_info=True)
Mike Frysinger4495b032014-03-05 17:24:03 -0500441 continue
Ralph Nathan03047282015-03-23 11:09:32 -0700442 logging.info('Signing %s image %s', image_type, gs_insns_path)
Don Garrett9459c2f2014-01-22 18:20:24 -0800443 instruction_urls.setdefault(channel, []).append(gs_insns_path)
444
Amey Deshpandea936c622015-08-12 17:27:54 -0700445 if unknown_error[0]:
Mike Frysinger4495b032014-03-05 17:24:03 -0500446 raise PushError('hit some unknown error(s)', instruction_urls)
447
Don Garrett9459c2f2014-01-22 18:20:24 -0800448 return instruction_urls
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400449
450
451def main(argv):
452 parser = commandline.ArgumentParser(description=__doc__)
453
454 # The type of image_dir will strip off trailing slashes (makes later
455 # processing simpler and the display prettier).
456 parser.add_argument('image_dir', default=None, type='local_or_gs_path',
457 help='full path of source artifacts to upload')
458 parser.add_argument('--board', default=None, required=True,
459 help='board to generate symbols for')
460 parser.add_argument('--profile', default=None,
461 help='board profile in use (e.g. "asan")')
462 parser.add_argument('--version', default=None,
463 help='version info (normally extracted from image_dir)')
464 parser.add_argument('-n', '--dry-run', default=False, action='store_true',
465 help='show what would be done, but do not upload')
466 parser.add_argument('-M', '--mock', default=False, action='store_true',
467 help='upload things to a testing bucket (dev testing)')
David Rileyf8205122015-09-04 13:46:36 -0700468 parser.add_argument('--test-sign', default=[], action='append',
469 choices=TEST_KEYSETS,
470 help='mung signing behavior to sign w/ test keys')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400471 parser.add_argument('--priority', type=int, default=50,
472 help='set signing priority (lower == higher prio)')
473 parser.add_argument('--sign-types', default=None, nargs='+',
Amey Deshpandea936c622015-08-12 17:27:54 -0700474 choices=_SUPPORTED_IMAGE_TYPES,
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400475 help='only sign specified image types')
Mike Frysinger09fe0122014-02-09 02:44:05 -0500476 parser.add_argument('--yes', action='store_true', default=False,
477 help='answer yes to all prompts')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400478
479 opts = parser.parse_args(argv)
480 opts.Freeze()
481
David Rileyf8205122015-09-04 13:46:36 -0700482 force_keysets = set(['%s-%s' % (TEST_KEYSET_PREFIX, x)
483 for x in opts.test_sign])
Mike Frysingerdad40d62014-02-09 02:18:02 -0500484
Mike Frysinger09fe0122014-02-09 02:44:05 -0500485 # If we aren't using mock or test or dry run mode, then let's prompt the user
486 # to make sure they actually want to do this. It's rare that people want to
487 # run this directly and hit the release bucket.
488 if not (opts.mock or force_keysets or opts.dry_run) and not opts.yes:
489 prolog = '\n'.join(textwrap.wrap(textwrap.dedent(
490 'Uploading images for signing to the *release* bucket is not something '
491 'you generally should be doing yourself.'), 80)).strip()
492 if not cros_build_lib.BooleanPrompt(
493 prompt='Are you sure you want to sign these images',
494 default=False, prolog=prolog):
495 cros_build_lib.Die('better safe than sorry')
496
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400497 PushImage(opts.image_dir, opts.board, versionrev=opts.version,
498 profile=opts.profile, priority=opts.priority,
Mike Frysingerdad40d62014-02-09 02:18:02 -0500499 sign_types=opts.sign_types, dry_run=opts.dry_run, mock=opts.mock,
500 force_keysets=force_keysets)