blob: 7c56e34590ea290328987d73556546ee305f6ea6 [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,
Vincent Palatind599c662015-10-26 09:51:41 -070052 constants.IMAGE_TYPE_ACCESSORY_USBPD,
53 constants.IMAGE_TYPE_ACCESSORY_RWSIG,
Amey Deshpandea936c622015-08-12 17:27:54 -070054 constants.IMAGE_TYPE_BASE,
55)
56
Mike Frysingerd13faeb2013-09-05 16:00:46 -040057
Mike Frysinger4495b032014-03-05 17:24:03 -050058class PushError(Exception):
59 """When an (unknown) error happened while trying to push artifacts."""
60
61
Mike Frysingerd13faeb2013-09-05 16:00:46 -040062class MissingBoardInstructions(Exception):
63 """Raised when a board lacks any signer instructions."""
64
65
66class InputInsns(object):
67 """Object to hold settings for a signable board.
68
69 Note: The format of the instruction file pushimage outputs (and the signer
70 reads) is not exactly the same as the instruction file pushimage reads.
71 """
72
73 def __init__(self, board):
74 self.board = board
75
76 config = ConfigParser.ConfigParser()
77 config.readfp(open(self.GetInsnFile('DEFAULT')))
Amey Deshpandea936c622015-08-12 17:27:54 -070078 # What pushimage internally refers to as 'recovery', are the basic signing
79 # instructions in practice, and other types are stacked on top.
80 input_insns = self.GetInsnFile(constants.IMAGE_TYPE_RECOVERY)
81 if not os.path.exists(input_insns):
82 # This board doesn't have any signing instructions.
83 raise MissingBoardInstructions(self.board)
84 config.readfp(open(input_insns))
Mike Frysingerd13faeb2013-09-05 16:00:46 -040085 self.cfg = config
86
87 def GetInsnFile(self, image_type):
88 """Find the signer instruction files for this board/image type.
89
90 Args:
91 image_type: The type of instructions to load. It can be a common file
92 (like "DEFAULT"), or one of the --sign-types.
93
94 Returns:
95 Full path to the instruction file using |image_type| and |self.board|.
96 """
97 if image_type == image_type.upper():
98 name = image_type
Amey Deshpandea936c622015-08-12 17:27:54 -070099 elif image_type in (constants.IMAGE_TYPE_RECOVERY,
100 constants.IMAGE_TYPE_BASE):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400101 name = self.board
102 else:
103 name = '%s.%s' % (self.board, image_type)
104
105 return os.path.join(signing.INPUT_INSN_DIR, '%s.instructions' % name)
106
107 @staticmethod
108 def SplitCfgField(val):
109 """Split a string into multiple elements.
110
111 This centralizes our convention for multiple elements in the input files
112 being delimited by either a space or comma.
113
114 Args:
115 val: The string to split.
116
117 Returns:
118 The list of elements from having done split the string.
119 """
120 return val.replace(',', ' ').split()
121
122 def GetChannels(self):
123 """Return the list of channels to sign for this board.
124
125 If the board-specific config doesn't specify a preference, we'll use the
126 common settings.
127 """
128 return self.SplitCfgField(self.cfg.get('insns', 'channel'))
129
130 def GetKeysets(self):
131 """Return the list of keysets to sign for this board."""
132 return self.SplitCfgField(self.cfg.get('insns', 'keyset'))
133
134 def OutputInsns(self, image_type, output_file, sect_insns, sect_general):
135 """Generate the output instruction file for sending to the signer.
136
137 Note: The format of the instruction file pushimage outputs (and the signer
138 reads) is not exactly the same as the instruction file pushimage reads.
139
140 Args:
141 image_type: The type of image we will be signing (see --sign-types).
142 output_file: The file to write the new instruction file to.
143 sect_insns: Items to set/override in the [insns] section.
144 sect_general: Items to set/override in the [general] section.
145 """
146 config = ConfigParser.ConfigParser()
147 config.readfp(open(self.GetInsnFile(image_type)))
148
149 # Clear channel entry in instructions file, ensuring we only get
150 # one channel for the signer to look at. Then provide all the
151 # other details for this signing request to avoid any ambiguity
152 # and to avoid relying on encoding data into filenames.
153 for sect, fields in zip(('insns', 'general'), (sect_insns, sect_general)):
154 if not config.has_section(sect):
155 config.add_section(sect)
156 for k, v in fields.iteritems():
157 config.set(sect, k, v)
158
159 output = cStringIO.StringIO()
160 config.write(output)
161 data = output.getvalue()
162 osutils.WriteFile(output_file, data)
Ralph Nathan5a582ff2015-03-20 18:18:30 -0700163 logging.debug('generated insns file for %s:\n%s', image_type, data)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400164
165
166def MarkImageToBeSigned(ctx, tbs_base, insns_path, priority):
167 """Mark an instructions file for signing.
168
169 This will upload a file to the GS bucket flagging an image for signing by
170 the signers.
171
172 Args:
173 ctx: A viable gs.GSContext.
174 tbs_base: The full path to where the tobesigned directory lives.
175 insns_path: The path (relative to |tbs_base|) of the file to sign.
176 priority: Set the signing priority (lower == higher prio).
177
178 Returns:
179 The full path to the remote tobesigned file.
180 """
181 if priority < 0 or priority > 99:
182 raise ValueError('priority must be [0, 99] inclusive')
183
184 if insns_path.startswith(tbs_base):
185 insns_path = insns_path[len(tbs_base):].lstrip('/')
186
187 tbs_path = '%s/tobesigned/%02i,%s' % (tbs_base, priority,
188 insns_path.replace('/', ','))
189
Mike Frysinger6430d132014-10-27 23:43:30 -0400190 # The caller will catch gs.GSContextException for us.
191 ctx.Copy('-', tbs_path, input=cros_build_lib.MachineDetails())
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400192
193 return tbs_path
194
195
196def PushImage(src_path, board, versionrev=None, profile=None, priority=50,
Mike Frysingerdad40d62014-02-09 02:18:02 -0500197 sign_types=None, dry_run=False, mock=False, force_keysets=()):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400198 """Push the image from the archive bucket to the release bucket.
199
200 Args:
201 src_path: Where to copy the files from; can be a local path or gs:// URL.
202 Should be a full path to the artifacts in either case.
203 board: The board we're uploading artifacts for (e.g. $BOARD).
204 versionrev: The full Chromium OS version string (e.g. R34-5126.0.0).
205 profile: The board profile in use (e.g. "asan").
206 priority: Set the signing priority (lower == higher prio).
207 sign_types: If set, a set of types which we'll restrict ourselves to
208 signing. See the --sign-types option for more details.
209 dry_run: Show what would be done, but do not upload anything.
210 mock: Upload to a testing bucket rather than the real one.
Mike Frysingerdad40d62014-02-09 02:18:02 -0500211 force_keysets: Set of keysets to use rather than what the inputs say.
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400212
213 Returns:
Don Garrett9459c2f2014-01-22 18:20:24 -0800214 A dictionary that maps 'channel' -> ['gs://signer_instruction_uri1',
215 'gs://signer_instruction_uri2',
216 ...]
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400217 """
Mike Frysinger4495b032014-03-05 17:24:03 -0500218 # Whether we hit an unknown error. If so, we'll throw an error, but only
219 # at the end (so that we still upload as many files as possible).
Amey Deshpandea936c622015-08-12 17:27:54 -0700220 # It's implemented using a list to deal with variable scopes in nested
221 # functions below.
222 unknown_error = [False]
Mike Frysinger4495b032014-03-05 17:24:03 -0500223
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400224 if versionrev is None:
225 # Extract milestone/version from the directory name.
226 versionrev = os.path.basename(src_path)
227
228 # We only support the latest format here. Older releases can use pushimage
229 # from the respective branch which deals with legacy cruft.
230 m = re.match(VERSION_REGEX, versionrev)
231 if not m:
232 raise ValueError('version %s does not match %s' %
233 (versionrev, VERSION_REGEX))
234 milestone = m.group(1)
235 version = m.group(2)
236
237 # Normalize board to always use dashes not underscores. This is mostly a
238 # historical artifact at this point, but we can't really break it since the
239 # value is used in URLs.
240 boardpath = board.replace('_', '-')
241 if profile is not None:
242 boardpath += '-%s' % profile.replace('_', '-')
243
244 ctx = gs.GSContext(dry_run=dry_run)
245
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400246 try:
247 input_insns = InputInsns(board)
248 except MissingBoardInstructions as e:
Ralph Nathan446aee92015-03-23 14:44:56 -0700249 logging.warning('board "%s" is missing base instruction file: %s', board, e)
250 logging.warning('not uploading anything for signing')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400251 return
252 channels = input_insns.GetChannels()
Mike Frysingerdad40d62014-02-09 02:18:02 -0500253
254 # We want force_keysets as a set, and keysets as a list.
255 force_keysets = set(force_keysets)
256 keysets = list(force_keysets) if force_keysets else input_insns.GetKeysets()
257
258 if mock:
Ralph Nathan03047282015-03-23 11:09:32 -0700259 logging.info('Upload mode: mock; signers will not process anything')
Mike Frysingerdad40d62014-02-09 02:18:02 -0500260 tbs_base = gs_base = os.path.join(constants.TRASH_BUCKET, 'pushimage-tests',
261 getpass.getuser())
David Rileyf8205122015-09-04 13:46:36 -0700262 elif set(['%s-%s' % (TEST_KEYSET_PREFIX, x)
263 for x in TEST_KEYSETS]) & force_keysets:
Ralph Nathan03047282015-03-23 11:09:32 -0700264 logging.info('Upload mode: test; signers will process test keys')
Mike Frysingerdad40d62014-02-09 02:18:02 -0500265 # We need the tbs_base to be in the place the signer will actually scan.
266 tbs_base = TEST_SIGN_BUCKET_BASE
267 gs_base = os.path.join(tbs_base, getpass.getuser())
268 else:
Ralph Nathan03047282015-03-23 11:09:32 -0700269 logging.info('Upload mode: normal; signers will process the images')
Mike Frysingerdad40d62014-02-09 02:18:02 -0500270 tbs_base = gs_base = constants.RELEASE_BUCKET
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400271
272 sect_general = {
273 'config_board': board,
274 'board': boardpath,
275 'version': version,
276 'versionrev': versionrev,
277 'milestone': milestone,
278 }
279 sect_insns = {}
280
281 if dry_run:
Ralph Nathan03047282015-03-23 11:09:32 -0700282 logging.info('DRY RUN MODE ACTIVE: NOTHING WILL BE UPLOADED')
283 logging.info('Signing for channels: %s', ' '.join(channels))
284 logging.info('Signing for keysets : %s', ' '.join(keysets))
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400285
Don Garrett9459c2f2014-01-22 18:20:24 -0800286 instruction_urls = {}
287
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400288 def _ImageNameBase(image_type=None):
289 lmid = ('%s-' % image_type) if image_type else ''
290 return 'ChromeOS-%s%s-%s' % (lmid, versionrev, boardpath)
291
Amey Deshpandea936c622015-08-12 17:27:54 -0700292 # These variables are defined outside the loop so that the nested functions
293 # below can access them without 'cell-var-from-loop' linter warning.
294 dst_path = ""
295 files_to_sign = []
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400296 for channel in channels:
Ralph Nathan5a582ff2015-03-20 18:18:30 -0700297 logging.debug('\n\n#### CHANNEL: %s ####\n', channel)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400298 sect_insns['channel'] = channel
299 sub_path = '%s-channel/%s/%s' % (channel, boardpath, version)
300 dst_path = '%s/%s' % (gs_base, sub_path)
Ralph Nathan03047282015-03-23 11:09:32 -0700301 logging.info('Copying images to %s', dst_path)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400302
Amey Deshpandea936c622015-08-12 17:27:54 -0700303 recovery_basename = _ImageNameBase(constants.IMAGE_TYPE_RECOVERY)
304 factory_basename = _ImageNameBase(constants.IMAGE_TYPE_FACTORY)
305 firmware_basename = _ImageNameBase(constants.IMAGE_TYPE_FIRMWARE)
David Rileya04d19d2015-09-04 16:11:50 -0700306 nv_lp0_firmware_basename = _ImageNameBase(
307 constants.IMAGE_TYPE_NV_LP0_FIRMWARE)
Vincent Palatind599c662015-10-26 09:51:41 -0700308 acc_usbpd_basename = _ImageNameBase(constants.IMAGE_TYPE_ACCESSORY_USBPD)
309 acc_rwsig_basename = _ImageNameBase(constants.IMAGE_TYPE_ACCESSORY_RWSIG)
Amey Deshpandea936c622015-08-12 17:27:54 -0700310 test_basename = _ImageNameBase(constants.IMAGE_TYPE_TEST)
311 base_basename = _ImageNameBase(constants.IMAGE_TYPE_BASE)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400312 hwqual_tarball = 'chromeos-hwqual-%s-%s.tar.bz2' % (board, versionrev)
313
Amey Deshpandea936c622015-08-12 17:27:54 -0700314 # The following build artifacts, if present, are always copied regardless of
315 # requested signing types.
316 files_to_copy_only = (
317 # (<src>, <dst>, <suffix>),
318 ('image.zip', _ImageNameBase(), 'zip'),
319 (constants.TEST_IMAGE_TAR, test_basename, 'tar.xz'),
320 ('debug.tgz', 'debug-%s' % boardpath, 'tgz'),
321 (hwqual_tarball, '', ''),
322 ('au-generator.zip', '', ''),
323 ('stateful.tgz', '', ''),
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400324 )
Amey Deshpandea936c622015-08-12 17:27:54 -0700325
326 # The following build artifacts, if present, are always copied.
327 # If |sign_types| is None, all of them are marked for signing, otherwise
328 # only the image types specified in |sign_types| are marked for signing.
329 files_to_copy_and_maybe_sign = (
330 # (<src>, <dst>, <suffix>, <signing type>),
331 (constants.RECOVERY_IMAGE_TAR, recovery_basename, 'tar.xz',
332 constants.IMAGE_TYPE_RECOVERY),
333
334 ('factory_image.zip', factory_basename, 'zip',
335 constants.IMAGE_TYPE_FACTORY),
336
337 ('firmware_from_source.tar.bz2', firmware_basename, 'tar.bz2',
338 constants.IMAGE_TYPE_FIRMWARE),
David Rileya04d19d2015-09-04 16:11:50 -0700339
340 ('firmware_from_source.tar.bz2', nv_lp0_firmware_basename, 'tar.bz2',
341 constants.IMAGE_TYPE_NV_LP0_FIRMWARE),
Vincent Palatind599c662015-10-26 09:51:41 -0700342
343 ('firmware_from_source.tar.bz2', acc_usbpd_basename, 'tar.bz2',
344 constants.IMAGE_TYPE_ACCESSORY_USBPD),
345
346 ('firmware_from_source.tar.bz2', acc_rwsig_basename, 'tar.bz2',
347 constants.IMAGE_TYPE_ACCESSORY_RWSIG),
Amey Deshpandea936c622015-08-12 17:27:54 -0700348 )
349
350 # The following build artifacts are copied and marked for signing, if
351 # they are present *and* if the image type is specified via |sign_types|.
352 files_to_maybe_copy_and_sign = (
353 # (<src>, <dst>, <suffix>, <signing type>),
354 (constants.BASE_IMAGE_TAR, base_basename, 'tar.xz',
355 constants.IMAGE_TYPE_BASE),
356 )
357
358 def _CopyFileToGS(src, dst, suffix):
359 """Returns |dst| file name if the copying was successful."""
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400360 if not dst:
361 dst = src
Amey Deshpandea936c622015-08-12 17:27:54 -0700362 elif suffix:
363 dst = '%s.%s' % (dst, suffix)
364 success = False
Mike Frysingere51a2652014-01-18 02:36:16 -0500365 try:
366 ctx.Copy(os.path.join(src_path, src), os.path.join(dst_path, dst))
Amey Deshpandea936c622015-08-12 17:27:54 -0700367 success = True
Mike Frysingere51a2652014-01-18 02:36:16 -0500368 except gs.GSNoSuchKey:
Ralph Nathan446aee92015-03-23 14:44:56 -0700369 logging.warning('Skipping %s as it does not exist', src)
Mike Frysinger4495b032014-03-05 17:24:03 -0500370 except gs.GSContextException:
Amey Deshpandea936c622015-08-12 17:27:54 -0700371 unknown_error[0] = True
Ralph Nathan59900422015-03-24 10:41:17 -0700372 logging.error('Skipping %s due to unknown GS error', src, exc_info=True)
Amey Deshpandea936c622015-08-12 17:27:54 -0700373 return dst if success else None
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400374
Amey Deshpandea936c622015-08-12 17:27:54 -0700375 for src, dst, suffix in files_to_copy_only:
376 _CopyFileToGS(src, dst, suffix)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400377
Amey Deshpandea936c622015-08-12 17:27:54 -0700378 # Clear the list of files to sign before adding new artifacts.
379 files_to_sign = []
380
381 def _AddToFilesToSign(image_type, dst, suffix):
382 assert dst.endswith('.' + suffix), (
383 'dst: %s, suffix: %s' % (dst, suffix))
384 dst_base = dst[:-(len(suffix) + 1)]
385 files_to_sign.append([image_type, dst_base, suffix])
386
387 for src, dst, suffix, image_type in files_to_copy_and_maybe_sign:
388 dst = _CopyFileToGS(src, dst, suffix)
389 if dst and (not sign_types or image_type in sign_types):
390 _AddToFilesToSign(image_type, dst, suffix)
391
392 for src, dst, suffix, image_type in files_to_maybe_copy_and_sign:
393 if sign_types and image_type in sign_types:
394 dst = _CopyFileToGS(src, dst, suffix)
395 if dst:
396 _AddToFilesToSign(image_type, dst, suffix)
397
398 logging.debug('Files to sign: %s', files_to_sign)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400399 # Now go through the subset for signing.
400 for keyset in keysets:
Ralph Nathan5a582ff2015-03-20 18:18:30 -0700401 logging.debug('\n\n#### KEYSET: %s ####\n', keyset)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400402 sect_insns['keyset'] = keyset
403 for image_type, dst_name, suffix in files_to_sign:
Amey Deshpandea936c622015-08-12 17:27:54 -0700404 dst_archive = '%s.%s' % (dst_name, suffix)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400405 sect_general['archive'] = dst_archive
406 sect_general['type'] = image_type
407
Amey Deshpandea936c622015-08-12 17:27:54 -0700408 # In the default/automatic mode, only flag files for signing if the
409 # archives were actually uploaded in a previous stage. This additional
410 # check can be removed in future once |sign_types| becomes a required
411 # argument.
412 # TODO: Make |sign_types| a required argument.
413 gs_artifact_path = os.path.join(dst_path, dst_archive)
414 exists = False
415 try:
416 exists = ctx.Exists(gs_artifact_path)
417 except gs.GSContextException:
418 unknown_error[0] = True
419 logging.error('Unknown error while checking %s', gs_artifact_path,
420 exc_info=True)
421 if not exists:
422 logging.info('%s does not exist. Nothing to sign.',
423 gs_artifact_path)
424 continue
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400425
426 input_insn_path = input_insns.GetInsnFile(image_type)
427 if not os.path.exists(input_insn_path):
Ralph Nathan03047282015-03-23 11:09:32 -0700428 logging.info('%s does not exist. Nothing to sign.', input_insn_path)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400429 continue
430
431 # Generate the insn file for this artifact that the signer will use,
432 # and flag it for signing.
433 with tempfile.NamedTemporaryFile(
434 bufsize=0, prefix='pushimage.insns.') as insns_path:
435 input_insns.OutputInsns(image_type, insns_path.name, sect_insns,
436 sect_general)
437
438 gs_insns_path = '%s/%s' % (dst_path, dst_name)
439 if keyset != keysets[0]:
440 gs_insns_path += '-%s' % keyset
441 gs_insns_path += '.instructions'
442
Mike Frysinger4495b032014-03-05 17:24:03 -0500443 try:
444 ctx.Copy(insns_path.name, gs_insns_path)
445 except gs.GSContextException:
Amey Deshpandea936c622015-08-12 17:27:54 -0700446 unknown_error[0] = True
Ralph Nathan59900422015-03-24 10:41:17 -0700447 logging.error('Unknown error while uploading insns %s',
448 gs_insns_path, exc_info=True)
Mike Frysinger4495b032014-03-05 17:24:03 -0500449 continue
450
451 try:
452 MarkImageToBeSigned(ctx, tbs_base, gs_insns_path, priority)
453 except gs.GSContextException:
Amey Deshpandea936c622015-08-12 17:27:54 -0700454 unknown_error[0] = True
Ralph Nathan59900422015-03-24 10:41:17 -0700455 logging.error('Unknown error while marking for signing %s',
456 gs_insns_path, exc_info=True)
Mike Frysinger4495b032014-03-05 17:24:03 -0500457 continue
Ralph Nathan03047282015-03-23 11:09:32 -0700458 logging.info('Signing %s image %s', image_type, gs_insns_path)
Don Garrett9459c2f2014-01-22 18:20:24 -0800459 instruction_urls.setdefault(channel, []).append(gs_insns_path)
460
Amey Deshpandea936c622015-08-12 17:27:54 -0700461 if unknown_error[0]:
Mike Frysinger4495b032014-03-05 17:24:03 -0500462 raise PushError('hit some unknown error(s)', instruction_urls)
463
Don Garrett9459c2f2014-01-22 18:20:24 -0800464 return instruction_urls
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400465
466
467def main(argv):
468 parser = commandline.ArgumentParser(description=__doc__)
469
470 # The type of image_dir will strip off trailing slashes (makes later
471 # processing simpler and the display prettier).
472 parser.add_argument('image_dir', default=None, type='local_or_gs_path',
473 help='full path of source artifacts to upload')
474 parser.add_argument('--board', default=None, required=True,
475 help='board to generate symbols for')
476 parser.add_argument('--profile', default=None,
477 help='board profile in use (e.g. "asan")')
478 parser.add_argument('--version', default=None,
479 help='version info (normally extracted from image_dir)')
480 parser.add_argument('-n', '--dry-run', default=False, action='store_true',
481 help='show what would be done, but do not upload')
482 parser.add_argument('-M', '--mock', default=False, action='store_true',
483 help='upload things to a testing bucket (dev testing)')
David Rileyf8205122015-09-04 13:46:36 -0700484 parser.add_argument('--test-sign', default=[], action='append',
485 choices=TEST_KEYSETS,
486 help='mung signing behavior to sign w/ test keys')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400487 parser.add_argument('--priority', type=int, default=50,
488 help='set signing priority (lower == higher prio)')
489 parser.add_argument('--sign-types', default=None, nargs='+',
Amey Deshpandea936c622015-08-12 17:27:54 -0700490 choices=_SUPPORTED_IMAGE_TYPES,
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400491 help='only sign specified image types')
Mike Frysinger09fe0122014-02-09 02:44:05 -0500492 parser.add_argument('--yes', action='store_true', default=False,
493 help='answer yes to all prompts')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400494
495 opts = parser.parse_args(argv)
496 opts.Freeze()
497
David Rileyf8205122015-09-04 13:46:36 -0700498 force_keysets = set(['%s-%s' % (TEST_KEYSET_PREFIX, x)
499 for x in opts.test_sign])
Mike Frysingerdad40d62014-02-09 02:18:02 -0500500
Mike Frysinger09fe0122014-02-09 02:44:05 -0500501 # If we aren't using mock or test or dry run mode, then let's prompt the user
502 # to make sure they actually want to do this. It's rare that people want to
503 # run this directly and hit the release bucket.
504 if not (opts.mock or force_keysets or opts.dry_run) and not opts.yes:
505 prolog = '\n'.join(textwrap.wrap(textwrap.dedent(
506 'Uploading images for signing to the *release* bucket is not something '
507 'you generally should be doing yourself.'), 80)).strip()
508 if not cros_build_lib.BooleanPrompt(
509 prompt='Are you sure you want to sign these images',
510 default=False, prolog=prolog):
511 cros_build_lib.Die('better safe than sorry')
512
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400513 PushImage(opts.image_dir, opts.board, versionrev=opts.version,
514 profile=opts.profile, priority=opts.priority,
Mike Frysingerdad40d62014-02-09 02:18:02 -0500515 sign_types=opts.sign_types, dry_run=opts.dry_run, mock=opts.mock,
516 force_keysets=force_keysets)