blob: 58c0ca23cbb0f7c9b2eb067ec365284b4be2b1bf [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,
David Rileya04d19d2015-09-04 16:11:50 -070051 constants.IMAGE_TYPE_NV_LP0_FIRMWARE,
Amey Deshpandea936c622015-08-12 17:27:54 -070052 constants.IMAGE_TYPE_BASE,
53)
54
Mike Frysingerd13faeb2013-09-05 16:00:46 -040055
Mike Frysinger4495b032014-03-05 17:24:03 -050056class PushError(Exception):
57 """When an (unknown) error happened while trying to push artifacts."""
58
59
Mike Frysingerd13faeb2013-09-05 16:00:46 -040060class MissingBoardInstructions(Exception):
61 """Raised when a board lacks any signer instructions."""
62
63
64class InputInsns(object):
65 """Object to hold settings for a signable board.
66
67 Note: The format of the instruction file pushimage outputs (and the signer
68 reads) is not exactly the same as the instruction file pushimage reads.
69 """
70
71 def __init__(self, board):
72 self.board = board
73
74 config = ConfigParser.ConfigParser()
75 config.readfp(open(self.GetInsnFile('DEFAULT')))
Amey Deshpandea936c622015-08-12 17:27:54 -070076 # What pushimage internally refers to as 'recovery', are the basic signing
77 # instructions in practice, and other types are stacked on top.
78 input_insns = self.GetInsnFile(constants.IMAGE_TYPE_RECOVERY)
79 if not os.path.exists(input_insns):
80 # This board doesn't have any signing instructions.
81 raise MissingBoardInstructions(self.board)
82 config.readfp(open(input_insns))
Mike Frysingerd13faeb2013-09-05 16:00:46 -040083 self.cfg = config
84
85 def GetInsnFile(self, image_type):
86 """Find the signer instruction files for this board/image type.
87
88 Args:
89 image_type: The type of instructions to load. It can be a common file
90 (like "DEFAULT"), or one of the --sign-types.
91
92 Returns:
93 Full path to the instruction file using |image_type| and |self.board|.
94 """
95 if image_type == image_type.upper():
96 name = image_type
Amey Deshpandea936c622015-08-12 17:27:54 -070097 elif image_type in (constants.IMAGE_TYPE_RECOVERY,
98 constants.IMAGE_TYPE_BASE):
Mike Frysingerd13faeb2013-09-05 16:00:46 -040099 name = self.board
100 else:
101 name = '%s.%s' % (self.board, image_type)
102
103 return os.path.join(signing.INPUT_INSN_DIR, '%s.instructions' % name)
104
105 @staticmethod
106 def SplitCfgField(val):
107 """Split a string into multiple elements.
108
109 This centralizes our convention for multiple elements in the input files
110 being delimited by either a space or comma.
111
112 Args:
113 val: The string to split.
114
115 Returns:
116 The list of elements from having done split the string.
117 """
118 return val.replace(',', ' ').split()
119
120 def GetChannels(self):
121 """Return the list of channels to sign for this board.
122
123 If the board-specific config doesn't specify a preference, we'll use the
124 common settings.
125 """
126 return self.SplitCfgField(self.cfg.get('insns', 'channel'))
127
128 def GetKeysets(self):
129 """Return the list of keysets to sign for this board."""
130 return self.SplitCfgField(self.cfg.get('insns', 'keyset'))
131
132 def OutputInsns(self, image_type, output_file, sect_insns, sect_general):
133 """Generate the output instruction file for sending to the signer.
134
135 Note: The format of the instruction file pushimage outputs (and the signer
136 reads) is not exactly the same as the instruction file pushimage reads.
137
138 Args:
139 image_type: The type of image we will be signing (see --sign-types).
140 output_file: The file to write the new instruction file to.
141 sect_insns: Items to set/override in the [insns] section.
142 sect_general: Items to set/override in the [general] section.
143 """
144 config = ConfigParser.ConfigParser()
145 config.readfp(open(self.GetInsnFile(image_type)))
146
147 # Clear channel entry in instructions file, ensuring we only get
148 # one channel for the signer to look at. Then provide all the
149 # other details for this signing request to avoid any ambiguity
150 # and to avoid relying on encoding data into filenames.
151 for sect, fields in zip(('insns', 'general'), (sect_insns, sect_general)):
152 if not config.has_section(sect):
153 config.add_section(sect)
154 for k, v in fields.iteritems():
155 config.set(sect, k, v)
156
157 output = cStringIO.StringIO()
158 config.write(output)
159 data = output.getvalue()
160 osutils.WriteFile(output_file, data)
Ralph Nathan5a582ff2015-03-20 18:18:30 -0700161 logging.debug('generated insns file for %s:\n%s', image_type, data)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400162
163
164def MarkImageToBeSigned(ctx, tbs_base, insns_path, priority):
165 """Mark an instructions file for signing.
166
167 This will upload a file to the GS bucket flagging an image for signing by
168 the signers.
169
170 Args:
171 ctx: A viable gs.GSContext.
172 tbs_base: The full path to where the tobesigned directory lives.
173 insns_path: The path (relative to |tbs_base|) of the file to sign.
174 priority: Set the signing priority (lower == higher prio).
175
176 Returns:
177 The full path to the remote tobesigned file.
178 """
179 if priority < 0 or priority > 99:
180 raise ValueError('priority must be [0, 99] inclusive')
181
182 if insns_path.startswith(tbs_base):
183 insns_path = insns_path[len(tbs_base):].lstrip('/')
184
185 tbs_path = '%s/tobesigned/%02i,%s' % (tbs_base, priority,
186 insns_path.replace('/', ','))
187
Mike Frysinger6430d132014-10-27 23:43:30 -0400188 # The caller will catch gs.GSContextException for us.
189 ctx.Copy('-', tbs_path, input=cros_build_lib.MachineDetails())
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400190
191 return tbs_path
192
193
194def PushImage(src_path, board, versionrev=None, profile=None, priority=50,
Mike Frysingerdad40d62014-02-09 02:18:02 -0500195 sign_types=None, dry_run=False, mock=False, force_keysets=()):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400196 """Push the image from the archive bucket to the release bucket.
197
198 Args:
199 src_path: Where to copy the files from; can be a local path or gs:// URL.
200 Should be a full path to the artifacts in either case.
201 board: The board we're uploading artifacts for (e.g. $BOARD).
202 versionrev: The full Chromium OS version string (e.g. R34-5126.0.0).
203 profile: The board profile in use (e.g. "asan").
204 priority: Set the signing priority (lower == higher prio).
205 sign_types: If set, a set of types which we'll restrict ourselves to
206 signing. See the --sign-types option for more details.
207 dry_run: Show what would be done, but do not upload anything.
208 mock: Upload to a testing bucket rather than the real one.
Mike Frysingerdad40d62014-02-09 02:18:02 -0500209 force_keysets: Set of keysets to use rather than what the inputs say.
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400210
211 Returns:
Don Garrett9459c2f2014-01-22 18:20:24 -0800212 A dictionary that maps 'channel' -> ['gs://signer_instruction_uri1',
213 'gs://signer_instruction_uri2',
214 ...]
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400215 """
Mike Frysinger4495b032014-03-05 17:24:03 -0500216 # Whether we hit an unknown error. If so, we'll throw an error, but only
217 # at the end (so that we still upload as many files as possible).
Amey Deshpandea936c622015-08-12 17:27:54 -0700218 # It's implemented using a list to deal with variable scopes in nested
219 # functions below.
220 unknown_error = [False]
Mike Frysinger4495b032014-03-05 17:24:03 -0500221
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400222 if versionrev is None:
223 # Extract milestone/version from the directory name.
224 versionrev = os.path.basename(src_path)
225
226 # We only support the latest format here. Older releases can use pushimage
227 # from the respective branch which deals with legacy cruft.
228 m = re.match(VERSION_REGEX, versionrev)
229 if not m:
230 raise ValueError('version %s does not match %s' %
231 (versionrev, VERSION_REGEX))
232 milestone = m.group(1)
233 version = m.group(2)
234
235 # Normalize board to always use dashes not underscores. This is mostly a
236 # historical artifact at this point, but we can't really break it since the
237 # value is used in URLs.
238 boardpath = board.replace('_', '-')
239 if profile is not None:
240 boardpath += '-%s' % profile.replace('_', '-')
241
242 ctx = gs.GSContext(dry_run=dry_run)
243
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400244 try:
245 input_insns = InputInsns(board)
246 except MissingBoardInstructions as e:
Ralph Nathan446aee92015-03-23 14:44:56 -0700247 logging.warning('board "%s" is missing base instruction file: %s', board, e)
248 logging.warning('not uploading anything for signing')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400249 return
250 channels = input_insns.GetChannels()
Mike Frysingerdad40d62014-02-09 02:18:02 -0500251
252 # We want force_keysets as a set, and keysets as a list.
253 force_keysets = set(force_keysets)
254 keysets = list(force_keysets) if force_keysets else input_insns.GetKeysets()
255
256 if mock:
Ralph Nathan03047282015-03-23 11:09:32 -0700257 logging.info('Upload mode: mock; signers will not process anything')
Mike Frysingerdad40d62014-02-09 02:18:02 -0500258 tbs_base = gs_base = os.path.join(constants.TRASH_BUCKET, 'pushimage-tests',
259 getpass.getuser())
David Rileyf8205122015-09-04 13:46:36 -0700260 elif set(['%s-%s' % (TEST_KEYSET_PREFIX, x)
261 for x in TEST_KEYSETS]) & force_keysets:
Ralph Nathan03047282015-03-23 11:09:32 -0700262 logging.info('Upload mode: test; signers will process test keys')
Mike Frysingerdad40d62014-02-09 02:18:02 -0500263 # We need the tbs_base to be in the place the signer will actually scan.
264 tbs_base = TEST_SIGN_BUCKET_BASE
265 gs_base = os.path.join(tbs_base, getpass.getuser())
266 else:
Ralph Nathan03047282015-03-23 11:09:32 -0700267 logging.info('Upload mode: normal; signers will process the images')
Mike Frysingerdad40d62014-02-09 02:18:02 -0500268 tbs_base = gs_base = constants.RELEASE_BUCKET
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400269
270 sect_general = {
271 'config_board': board,
272 'board': boardpath,
273 'version': version,
274 'versionrev': versionrev,
275 'milestone': milestone,
276 }
277 sect_insns = {}
278
279 if dry_run:
Ralph Nathan03047282015-03-23 11:09:32 -0700280 logging.info('DRY RUN MODE ACTIVE: NOTHING WILL BE UPLOADED')
281 logging.info('Signing for channels: %s', ' '.join(channels))
282 logging.info('Signing for keysets : %s', ' '.join(keysets))
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400283
Don Garrett9459c2f2014-01-22 18:20:24 -0800284 instruction_urls = {}
285
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400286 def _ImageNameBase(image_type=None):
287 lmid = ('%s-' % image_type) if image_type else ''
288 return 'ChromeOS-%s%s-%s' % (lmid, versionrev, boardpath)
289
Amey Deshpandea936c622015-08-12 17:27:54 -0700290 # These variables are defined outside the loop so that the nested functions
291 # below can access them without 'cell-var-from-loop' linter warning.
292 dst_path = ""
293 files_to_sign = []
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400294 for channel in channels:
Ralph Nathan5a582ff2015-03-20 18:18:30 -0700295 logging.debug('\n\n#### CHANNEL: %s ####\n', channel)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400296 sect_insns['channel'] = channel
297 sub_path = '%s-channel/%s/%s' % (channel, boardpath, version)
298 dst_path = '%s/%s' % (gs_base, sub_path)
Ralph Nathan03047282015-03-23 11:09:32 -0700299 logging.info('Copying images to %s', dst_path)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400300
Amey Deshpandea936c622015-08-12 17:27:54 -0700301 recovery_basename = _ImageNameBase(constants.IMAGE_TYPE_RECOVERY)
302 factory_basename = _ImageNameBase(constants.IMAGE_TYPE_FACTORY)
303 firmware_basename = _ImageNameBase(constants.IMAGE_TYPE_FIRMWARE)
David Rileya04d19d2015-09-04 16:11:50 -0700304 nv_lp0_firmware_basename = _ImageNameBase(
305 constants.IMAGE_TYPE_NV_LP0_FIRMWARE)
Amey Deshpandea936c622015-08-12 17:27:54 -0700306 test_basename = _ImageNameBase(constants.IMAGE_TYPE_TEST)
307 base_basename = _ImageNameBase(constants.IMAGE_TYPE_BASE)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400308 hwqual_tarball = 'chromeos-hwqual-%s-%s.tar.bz2' % (board, versionrev)
309
Amey Deshpandea936c622015-08-12 17:27:54 -0700310 # The following build artifacts, if present, are always copied regardless of
311 # requested signing types.
312 files_to_copy_only = (
313 # (<src>, <dst>, <suffix>),
314 ('image.zip', _ImageNameBase(), 'zip'),
315 (constants.TEST_IMAGE_TAR, test_basename, 'tar.xz'),
316 ('debug.tgz', 'debug-%s' % boardpath, 'tgz'),
317 (hwqual_tarball, '', ''),
318 ('au-generator.zip', '', ''),
319 ('stateful.tgz', '', ''),
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400320 )
Amey Deshpandea936c622015-08-12 17:27:54 -0700321
322 # The following build artifacts, if present, are always copied.
323 # If |sign_types| is None, all of them are marked for signing, otherwise
324 # only the image types specified in |sign_types| are marked for signing.
325 files_to_copy_and_maybe_sign = (
326 # (<src>, <dst>, <suffix>, <signing type>),
327 (constants.RECOVERY_IMAGE_TAR, recovery_basename, 'tar.xz',
328 constants.IMAGE_TYPE_RECOVERY),
329
330 ('factory_image.zip', factory_basename, 'zip',
331 constants.IMAGE_TYPE_FACTORY),
332
333 ('firmware_from_source.tar.bz2', firmware_basename, 'tar.bz2',
334 constants.IMAGE_TYPE_FIRMWARE),
David Rileya04d19d2015-09-04 16:11:50 -0700335
336 ('firmware_from_source.tar.bz2', nv_lp0_firmware_basename, 'tar.bz2',
337 constants.IMAGE_TYPE_NV_LP0_FIRMWARE),
Amey Deshpandea936c622015-08-12 17:27:54 -0700338 )
339
340 # The following build artifacts are copied and marked for signing, if
341 # they are present *and* if the image type is specified via |sign_types|.
342 files_to_maybe_copy_and_sign = (
343 # (<src>, <dst>, <suffix>, <signing type>),
344 (constants.BASE_IMAGE_TAR, base_basename, 'tar.xz',
345 constants.IMAGE_TYPE_BASE),
346 )
347
348 def _CopyFileToGS(src, dst, suffix):
349 """Returns |dst| file name if the copying was successful."""
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400350 if not dst:
351 dst = src
Amey Deshpandea936c622015-08-12 17:27:54 -0700352 elif suffix:
353 dst = '%s.%s' % (dst, suffix)
354 success = False
Mike Frysingere51a2652014-01-18 02:36:16 -0500355 try:
356 ctx.Copy(os.path.join(src_path, src), os.path.join(dst_path, dst))
Amey Deshpandea936c622015-08-12 17:27:54 -0700357 success = True
Mike Frysingere51a2652014-01-18 02:36:16 -0500358 except gs.GSNoSuchKey:
Ralph Nathan446aee92015-03-23 14:44:56 -0700359 logging.warning('Skipping %s as it does not exist', src)
Mike Frysinger4495b032014-03-05 17:24:03 -0500360 except gs.GSContextException:
Amey Deshpandea936c622015-08-12 17:27:54 -0700361 unknown_error[0] = True
Ralph Nathan59900422015-03-24 10:41:17 -0700362 logging.error('Skipping %s due to unknown GS error', src, exc_info=True)
Amey Deshpandea936c622015-08-12 17:27:54 -0700363 return dst if success else None
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400364
Amey Deshpandea936c622015-08-12 17:27:54 -0700365 for src, dst, suffix in files_to_copy_only:
366 _CopyFileToGS(src, dst, suffix)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400367
Amey Deshpandea936c622015-08-12 17:27:54 -0700368 # Clear the list of files to sign before adding new artifacts.
369 files_to_sign = []
370
371 def _AddToFilesToSign(image_type, dst, suffix):
372 assert dst.endswith('.' + suffix), (
373 'dst: %s, suffix: %s' % (dst, suffix))
374 dst_base = dst[:-(len(suffix) + 1)]
375 files_to_sign.append([image_type, dst_base, suffix])
376
377 for src, dst, suffix, image_type in files_to_copy_and_maybe_sign:
378 dst = _CopyFileToGS(src, dst, suffix)
379 if dst and (not sign_types or image_type in sign_types):
380 _AddToFilesToSign(image_type, dst, suffix)
381
382 for src, dst, suffix, image_type in files_to_maybe_copy_and_sign:
383 if sign_types and image_type in sign_types:
384 dst = _CopyFileToGS(src, dst, suffix)
385 if dst:
386 _AddToFilesToSign(image_type, dst, suffix)
387
388 logging.debug('Files to sign: %s', files_to_sign)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400389 # Now go through the subset for signing.
390 for keyset in keysets:
Ralph Nathan5a582ff2015-03-20 18:18:30 -0700391 logging.debug('\n\n#### KEYSET: %s ####\n', keyset)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400392 sect_insns['keyset'] = keyset
393 for image_type, dst_name, suffix in files_to_sign:
Amey Deshpandea936c622015-08-12 17:27:54 -0700394 dst_archive = '%s.%s' % (dst_name, suffix)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400395 sect_general['archive'] = dst_archive
396 sect_general['type'] = image_type
397
Amey Deshpandea936c622015-08-12 17:27:54 -0700398 # In the default/automatic mode, only flag files for signing if the
399 # archives were actually uploaded in a previous stage. This additional
400 # check can be removed in future once |sign_types| becomes a required
401 # argument.
402 # TODO: Make |sign_types| a required argument.
403 gs_artifact_path = os.path.join(dst_path, dst_archive)
404 exists = False
405 try:
406 exists = ctx.Exists(gs_artifact_path)
407 except gs.GSContextException:
408 unknown_error[0] = True
409 logging.error('Unknown error while checking %s', gs_artifact_path,
410 exc_info=True)
411 if not exists:
412 logging.info('%s does not exist. Nothing to sign.',
413 gs_artifact_path)
414 continue
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400415
416 input_insn_path = input_insns.GetInsnFile(image_type)
417 if not os.path.exists(input_insn_path):
Ralph Nathan03047282015-03-23 11:09:32 -0700418 logging.info('%s does not exist. Nothing to sign.', input_insn_path)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400419 continue
420
421 # Generate the insn file for this artifact that the signer will use,
422 # and flag it for signing.
423 with tempfile.NamedTemporaryFile(
424 bufsize=0, prefix='pushimage.insns.') as insns_path:
425 input_insns.OutputInsns(image_type, insns_path.name, sect_insns,
426 sect_general)
427
428 gs_insns_path = '%s/%s' % (dst_path, dst_name)
429 if keyset != keysets[0]:
430 gs_insns_path += '-%s' % keyset
431 gs_insns_path += '.instructions'
432
Mike Frysinger4495b032014-03-05 17:24:03 -0500433 try:
434 ctx.Copy(insns_path.name, gs_insns_path)
435 except gs.GSContextException:
Amey Deshpandea936c622015-08-12 17:27:54 -0700436 unknown_error[0] = True
Ralph Nathan59900422015-03-24 10:41:17 -0700437 logging.error('Unknown error while uploading insns %s',
438 gs_insns_path, exc_info=True)
Mike Frysinger4495b032014-03-05 17:24:03 -0500439 continue
440
441 try:
442 MarkImageToBeSigned(ctx, tbs_base, gs_insns_path, priority)
443 except gs.GSContextException:
Amey Deshpandea936c622015-08-12 17:27:54 -0700444 unknown_error[0] = True
Ralph Nathan59900422015-03-24 10:41:17 -0700445 logging.error('Unknown error while marking for signing %s',
446 gs_insns_path, exc_info=True)
Mike Frysinger4495b032014-03-05 17:24:03 -0500447 continue
Ralph Nathan03047282015-03-23 11:09:32 -0700448 logging.info('Signing %s image %s', image_type, gs_insns_path)
Don Garrett9459c2f2014-01-22 18:20:24 -0800449 instruction_urls.setdefault(channel, []).append(gs_insns_path)
450
Amey Deshpandea936c622015-08-12 17:27:54 -0700451 if unknown_error[0]:
Mike Frysinger4495b032014-03-05 17:24:03 -0500452 raise PushError('hit some unknown error(s)', instruction_urls)
453
Don Garrett9459c2f2014-01-22 18:20:24 -0800454 return instruction_urls
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400455
456
457def main(argv):
458 parser = commandline.ArgumentParser(description=__doc__)
459
460 # The type of image_dir will strip off trailing slashes (makes later
461 # processing simpler and the display prettier).
462 parser.add_argument('image_dir', default=None, type='local_or_gs_path',
463 help='full path of source artifacts to upload')
464 parser.add_argument('--board', default=None, required=True,
465 help='board to generate symbols for')
466 parser.add_argument('--profile', default=None,
467 help='board profile in use (e.g. "asan")')
468 parser.add_argument('--version', default=None,
469 help='version info (normally extracted from image_dir)')
470 parser.add_argument('-n', '--dry-run', default=False, action='store_true',
471 help='show what would be done, but do not upload')
472 parser.add_argument('-M', '--mock', default=False, action='store_true',
473 help='upload things to a testing bucket (dev testing)')
David Rileyf8205122015-09-04 13:46:36 -0700474 parser.add_argument('--test-sign', default=[], action='append',
475 choices=TEST_KEYSETS,
476 help='mung signing behavior to sign w/ test keys')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400477 parser.add_argument('--priority', type=int, default=50,
478 help='set signing priority (lower == higher prio)')
479 parser.add_argument('--sign-types', default=None, nargs='+',
Amey Deshpandea936c622015-08-12 17:27:54 -0700480 choices=_SUPPORTED_IMAGE_TYPES,
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400481 help='only sign specified image types')
Mike Frysinger09fe0122014-02-09 02:44:05 -0500482 parser.add_argument('--yes', action='store_true', default=False,
483 help='answer yes to all prompts')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400484
485 opts = parser.parse_args(argv)
486 opts.Freeze()
487
David Rileyf8205122015-09-04 13:46:36 -0700488 force_keysets = set(['%s-%s' % (TEST_KEYSET_PREFIX, x)
489 for x in opts.test_sign])
Mike Frysingerdad40d62014-02-09 02:18:02 -0500490
Mike Frysinger09fe0122014-02-09 02:44:05 -0500491 # If we aren't using mock or test or dry run mode, then let's prompt the user
492 # to make sure they actually want to do this. It's rare that people want to
493 # run this directly and hit the release bucket.
494 if not (opts.mock or force_keysets or opts.dry_run) and not opts.yes:
495 prolog = '\n'.join(textwrap.wrap(textwrap.dedent(
496 'Uploading images for signing to the *release* bucket is not something '
497 'you generally should be doing yourself.'), 80)).strip()
498 if not cros_build_lib.BooleanPrompt(
499 prompt='Are you sure you want to sign these images',
500 default=False, prolog=prolog):
501 cros_build_lib.Die('better safe than sorry')
502
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400503 PushImage(opts.image_dir, opts.board, versionrev=opts.version,
504 profile=opts.profile, priority=opts.priority,
Mike Frysingerdad40d62014-02-09 02:18:02 -0500505 sign_types=opts.sign_types, dry_run=opts.dry_run, mock=opts.mock,
506 force_keysets=force_keysets)