blob: e770d123e2e22d58cda81093a65de13536cc5d60 [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
Mike Frysingerd84d91e2015-11-05 18:02:24 -050065 def __init__(self, board, image_type, input_insns):
66 Exception.__init__(self, 'Board %s lacks insns for %s image: %s not found' %
67 (board, image_type, input_insns))
68
Mike Frysingerd13faeb2013-09-05 16:00:46 -040069
70class InputInsns(object):
71 """Object to hold settings for a signable board.
72
73 Note: The format of the instruction file pushimage outputs (and the signer
74 reads) is not exactly the same as the instruction file pushimage reads.
75 """
76
Mike Frysingerd84d91e2015-11-05 18:02:24 -050077 def __init__(self, board, image_type=None):
78 """Initialization.
79
80 Args:
81 board: The board to look up details.
82 image_type: The type of image we will be signing (see --sign-types).
83 """
Mike Frysingerd13faeb2013-09-05 16:00:46 -040084 self.board = board
85
86 config = ConfigParser.ConfigParser()
87 config.readfp(open(self.GetInsnFile('DEFAULT')))
Mike Frysingerd84d91e2015-11-05 18:02:24 -050088
Amey Deshpandea936c622015-08-12 17:27:54 -070089 # What pushimage internally refers to as 'recovery', are the basic signing
90 # instructions in practice, and other types are stacked on top.
Mike Frysingerd84d91e2015-11-05 18:02:24 -050091 if image_type is None:
92 image_type = constants.IMAGE_TYPE_RECOVERY
93 self.image_type = image_type
Amey Deshpandea936c622015-08-12 17:27:54 -070094 input_insns = self.GetInsnFile(constants.IMAGE_TYPE_RECOVERY)
95 if not os.path.exists(input_insns):
96 # This board doesn't have any signing instructions.
Mike Frysingerd84d91e2015-11-05 18:02:24 -050097 raise MissingBoardInstructions(self.board, image_type, input_insns)
Amey Deshpandea936c622015-08-12 17:27:54 -070098 config.readfp(open(input_insns))
Mike Frysingerd84d91e2015-11-05 18:02:24 -050099
100 if image_type is not None:
101 input_insns = self.GetInsnFile(image_type)
102 if not os.path.exists(input_insns):
103 # This type doesn't have any signing instructions.
104 raise MissingBoardInstructions(self.board, image_type, input_insns)
105
106 self.image_type = image_type
107 config.readfp(open(input_insns))
108
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400109 self.cfg = config
110
111 def GetInsnFile(self, image_type):
112 """Find the signer instruction files for this board/image type.
113
114 Args:
115 image_type: The type of instructions to load. It can be a common file
116 (like "DEFAULT"), or one of the --sign-types.
117
118 Returns:
119 Full path to the instruction file using |image_type| and |self.board|.
120 """
121 if image_type == image_type.upper():
122 name = image_type
Amey Deshpandea936c622015-08-12 17:27:54 -0700123 elif image_type in (constants.IMAGE_TYPE_RECOVERY,
124 constants.IMAGE_TYPE_BASE):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400125 name = self.board
126 else:
127 name = '%s.%s' % (self.board, image_type)
128
129 return os.path.join(signing.INPUT_INSN_DIR, '%s.instructions' % name)
130
131 @staticmethod
132 def SplitCfgField(val):
133 """Split a string into multiple elements.
134
135 This centralizes our convention for multiple elements in the input files
136 being delimited by either a space or comma.
137
138 Args:
139 val: The string to split.
140
141 Returns:
142 The list of elements from having done split the string.
143 """
144 return val.replace(',', ' ').split()
145
146 def GetChannels(self):
147 """Return the list of channels to sign for this board.
148
149 If the board-specific config doesn't specify a preference, we'll use the
150 common settings.
151 """
152 return self.SplitCfgField(self.cfg.get('insns', 'channel'))
153
154 def GetKeysets(self):
155 """Return the list of keysets to sign for this board."""
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500156 # We do not perturb the order (e.g. using sorted() or making a set())
157 # because we want the behavior stable, and we want the input insns to
158 # explicitly control the order (since it has an impact on naming).
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400159 return self.SplitCfgField(self.cfg.get('insns', 'keyset'))
160
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500161 @staticmethod
162 def CopyConfigParser(config):
163 """Return a copy of a ConfigParser object.
164
165 The python guys broke the ability to use something like deepcopy:
166 https://bugs.python.org/issue16058
167 """
168 # Write the current config to a string io object.
169 data = cStringIO.StringIO()
170 config.write(data)
171 data.seek(0)
172
173 # Create a new ConfigParser from the serialized data.
174 ret = ConfigParser.ConfigParser()
175 ret.readfp(data)
176
177 return ret
178
179 def OutputInsns(self, output_file, sect_insns, sect_general):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400180 """Generate the output instruction file for sending to the signer.
181
182 Note: The format of the instruction file pushimage outputs (and the signer
183 reads) is not exactly the same as the instruction file pushimage reads.
184
185 Args:
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400186 output_file: The file to write the new instruction file to.
187 sect_insns: Items to set/override in the [insns] section.
188 sect_general: Items to set/override in the [general] section.
189 """
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500190 # Create a copy so we can clobber certain fields.
191 config = self.CopyConfigParser(self.cfg)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400192
193 # Clear channel entry in instructions file, ensuring we only get
194 # one channel for the signer to look at. Then provide all the
195 # other details for this signing request to avoid any ambiguity
196 # and to avoid relying on encoding data into filenames.
197 for sect, fields in zip(('insns', 'general'), (sect_insns, sect_general)):
198 if not config.has_section(sect):
199 config.add_section(sect)
200 for k, v in fields.iteritems():
201 config.set(sect, k, v)
202
203 output = cStringIO.StringIO()
204 config.write(output)
205 data = output.getvalue()
206 osutils.WriteFile(output_file, data)
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500207 logging.debug('generated insns file for %s:\n%s', self.image_type, data)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400208
209
210def MarkImageToBeSigned(ctx, tbs_base, insns_path, priority):
211 """Mark an instructions file for signing.
212
213 This will upload a file to the GS bucket flagging an image for signing by
214 the signers.
215
216 Args:
217 ctx: A viable gs.GSContext.
218 tbs_base: The full path to where the tobesigned directory lives.
219 insns_path: The path (relative to |tbs_base|) of the file to sign.
220 priority: Set the signing priority (lower == higher prio).
221
222 Returns:
223 The full path to the remote tobesigned file.
224 """
225 if priority < 0 or priority > 99:
226 raise ValueError('priority must be [0, 99] inclusive')
227
228 if insns_path.startswith(tbs_base):
229 insns_path = insns_path[len(tbs_base):].lstrip('/')
230
231 tbs_path = '%s/tobesigned/%02i,%s' % (tbs_base, priority,
232 insns_path.replace('/', ','))
233
Mike Frysinger6430d132014-10-27 23:43:30 -0400234 # The caller will catch gs.GSContextException for us.
235 ctx.Copy('-', tbs_path, input=cros_build_lib.MachineDetails())
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400236
237 return tbs_path
238
239
240def PushImage(src_path, board, versionrev=None, profile=None, priority=50,
Mike Frysingerdad40d62014-02-09 02:18:02 -0500241 sign_types=None, dry_run=False, mock=False, force_keysets=()):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400242 """Push the image from the archive bucket to the release bucket.
243
244 Args:
245 src_path: Where to copy the files from; can be a local path or gs:// URL.
246 Should be a full path to the artifacts in either case.
247 board: The board we're uploading artifacts for (e.g. $BOARD).
248 versionrev: The full Chromium OS version string (e.g. R34-5126.0.0).
249 profile: The board profile in use (e.g. "asan").
250 priority: Set the signing priority (lower == higher prio).
251 sign_types: If set, a set of types which we'll restrict ourselves to
252 signing. See the --sign-types option for more details.
253 dry_run: Show what would be done, but do not upload anything.
254 mock: Upload to a testing bucket rather than the real one.
Mike Frysingerdad40d62014-02-09 02:18:02 -0500255 force_keysets: Set of keysets to use rather than what the inputs say.
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400256
257 Returns:
Don Garrett9459c2f2014-01-22 18:20:24 -0800258 A dictionary that maps 'channel' -> ['gs://signer_instruction_uri1',
259 'gs://signer_instruction_uri2',
260 ...]
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400261 """
Mike Frysinger4495b032014-03-05 17:24:03 -0500262 # Whether we hit an unknown error. If so, we'll throw an error, but only
263 # at the end (so that we still upload as many files as possible).
Amey Deshpandea936c622015-08-12 17:27:54 -0700264 # It's implemented using a list to deal with variable scopes in nested
265 # functions below.
266 unknown_error = [False]
Mike Frysinger4495b032014-03-05 17:24:03 -0500267
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400268 if versionrev is None:
269 # Extract milestone/version from the directory name.
270 versionrev = os.path.basename(src_path)
271
272 # We only support the latest format here. Older releases can use pushimage
273 # from the respective branch which deals with legacy cruft.
274 m = re.match(VERSION_REGEX, versionrev)
275 if not m:
276 raise ValueError('version %s does not match %s' %
277 (versionrev, VERSION_REGEX))
278 milestone = m.group(1)
279 version = m.group(2)
280
281 # Normalize board to always use dashes not underscores. This is mostly a
282 # historical artifact at this point, but we can't really break it since the
283 # value is used in URLs.
284 boardpath = board.replace('_', '-')
285 if profile is not None:
286 boardpath += '-%s' % profile.replace('_', '-')
287
288 ctx = gs.GSContext(dry_run=dry_run)
289
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400290 try:
291 input_insns = InputInsns(board)
292 except MissingBoardInstructions as e:
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500293 logging.warning('Missing base instruction file: %s', e)
Ralph Nathan446aee92015-03-23 14:44:56 -0700294 logging.warning('not uploading anything for signing')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400295 return
296 channels = input_insns.GetChannels()
Mike Frysingerdad40d62014-02-09 02:18:02 -0500297
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500298 # We want force_keysets as a set.
Mike Frysingerdad40d62014-02-09 02:18:02 -0500299 force_keysets = set(force_keysets)
Mike Frysingerdad40d62014-02-09 02:18:02 -0500300
301 if mock:
Ralph Nathan03047282015-03-23 11:09:32 -0700302 logging.info('Upload mode: mock; signers will not process anything')
Mike Frysingerdad40d62014-02-09 02:18:02 -0500303 tbs_base = gs_base = os.path.join(constants.TRASH_BUCKET, 'pushimage-tests',
304 getpass.getuser())
David Rileyf8205122015-09-04 13:46:36 -0700305 elif set(['%s-%s' % (TEST_KEYSET_PREFIX, x)
306 for x in TEST_KEYSETS]) & force_keysets:
Ralph Nathan03047282015-03-23 11:09:32 -0700307 logging.info('Upload mode: test; signers will process test keys')
Mike Frysingerdad40d62014-02-09 02:18:02 -0500308 # We need the tbs_base to be in the place the signer will actually scan.
309 tbs_base = TEST_SIGN_BUCKET_BASE
310 gs_base = os.path.join(tbs_base, getpass.getuser())
311 else:
Ralph Nathan03047282015-03-23 11:09:32 -0700312 logging.info('Upload mode: normal; signers will process the images')
Mike Frysingerdad40d62014-02-09 02:18:02 -0500313 tbs_base = gs_base = constants.RELEASE_BUCKET
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400314
315 sect_general = {
316 'config_board': board,
317 'board': boardpath,
318 'version': version,
319 'versionrev': versionrev,
320 'milestone': milestone,
321 }
322 sect_insns = {}
323
324 if dry_run:
Ralph Nathan03047282015-03-23 11:09:32 -0700325 logging.info('DRY RUN MODE ACTIVE: NOTHING WILL BE UPLOADED')
326 logging.info('Signing for channels: %s', ' '.join(channels))
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400327
Don Garrett9459c2f2014-01-22 18:20:24 -0800328 instruction_urls = {}
329
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400330 def _ImageNameBase(image_type=None):
331 lmid = ('%s-' % image_type) if image_type else ''
332 return 'ChromeOS-%s%s-%s' % (lmid, versionrev, boardpath)
333
Amey Deshpandea936c622015-08-12 17:27:54 -0700334 # These variables are defined outside the loop so that the nested functions
335 # below can access them without 'cell-var-from-loop' linter warning.
336 dst_path = ""
337 files_to_sign = []
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400338 for channel in channels:
Ralph Nathan5a582ff2015-03-20 18:18:30 -0700339 logging.debug('\n\n#### CHANNEL: %s ####\n', channel)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400340 sect_insns['channel'] = channel
341 sub_path = '%s-channel/%s/%s' % (channel, boardpath, version)
342 dst_path = '%s/%s' % (gs_base, sub_path)
Ralph Nathan03047282015-03-23 11:09:32 -0700343 logging.info('Copying images to %s', dst_path)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400344
Amey Deshpandea936c622015-08-12 17:27:54 -0700345 recovery_basename = _ImageNameBase(constants.IMAGE_TYPE_RECOVERY)
346 factory_basename = _ImageNameBase(constants.IMAGE_TYPE_FACTORY)
347 firmware_basename = _ImageNameBase(constants.IMAGE_TYPE_FIRMWARE)
David Rileya04d19d2015-09-04 16:11:50 -0700348 nv_lp0_firmware_basename = _ImageNameBase(
349 constants.IMAGE_TYPE_NV_LP0_FIRMWARE)
Vincent Palatind599c662015-10-26 09:51:41 -0700350 acc_usbpd_basename = _ImageNameBase(constants.IMAGE_TYPE_ACCESSORY_USBPD)
351 acc_rwsig_basename = _ImageNameBase(constants.IMAGE_TYPE_ACCESSORY_RWSIG)
Amey Deshpandea936c622015-08-12 17:27:54 -0700352 test_basename = _ImageNameBase(constants.IMAGE_TYPE_TEST)
353 base_basename = _ImageNameBase(constants.IMAGE_TYPE_BASE)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400354 hwqual_tarball = 'chromeos-hwqual-%s-%s.tar.bz2' % (board, versionrev)
355
Amey Deshpandea936c622015-08-12 17:27:54 -0700356 # The following build artifacts, if present, are always copied regardless of
357 # requested signing types.
358 files_to_copy_only = (
359 # (<src>, <dst>, <suffix>),
360 ('image.zip', _ImageNameBase(), 'zip'),
361 (constants.TEST_IMAGE_TAR, test_basename, 'tar.xz'),
362 ('debug.tgz', 'debug-%s' % boardpath, 'tgz'),
363 (hwqual_tarball, '', ''),
364 ('au-generator.zip', '', ''),
365 ('stateful.tgz', '', ''),
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400366 )
Amey Deshpandea936c622015-08-12 17:27:54 -0700367
368 # The following build artifacts, if present, are always copied.
369 # If |sign_types| is None, all of them are marked for signing, otherwise
370 # only the image types specified in |sign_types| are marked for signing.
371 files_to_copy_and_maybe_sign = (
372 # (<src>, <dst>, <suffix>, <signing type>),
373 (constants.RECOVERY_IMAGE_TAR, recovery_basename, 'tar.xz',
374 constants.IMAGE_TYPE_RECOVERY),
375
376 ('factory_image.zip', factory_basename, 'zip',
377 constants.IMAGE_TYPE_FACTORY),
378
379 ('firmware_from_source.tar.bz2', firmware_basename, 'tar.bz2',
380 constants.IMAGE_TYPE_FIRMWARE),
David Rileya04d19d2015-09-04 16:11:50 -0700381
382 ('firmware_from_source.tar.bz2', nv_lp0_firmware_basename, 'tar.bz2',
383 constants.IMAGE_TYPE_NV_LP0_FIRMWARE),
Vincent Palatind599c662015-10-26 09:51:41 -0700384
385 ('firmware_from_source.tar.bz2', acc_usbpd_basename, 'tar.bz2',
386 constants.IMAGE_TYPE_ACCESSORY_USBPD),
387
388 ('firmware_from_source.tar.bz2', acc_rwsig_basename, 'tar.bz2',
389 constants.IMAGE_TYPE_ACCESSORY_RWSIG),
Amey Deshpandea936c622015-08-12 17:27:54 -0700390 )
391
392 # The following build artifacts are copied and marked for signing, if
393 # they are present *and* if the image type is specified via |sign_types|.
394 files_to_maybe_copy_and_sign = (
395 # (<src>, <dst>, <suffix>, <signing type>),
396 (constants.BASE_IMAGE_TAR, base_basename, 'tar.xz',
397 constants.IMAGE_TYPE_BASE),
398 )
399
400 def _CopyFileToGS(src, dst, suffix):
401 """Returns |dst| file name if the copying was successful."""
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400402 if not dst:
403 dst = src
Amey Deshpandea936c622015-08-12 17:27:54 -0700404 elif suffix:
405 dst = '%s.%s' % (dst, suffix)
406 success = False
Mike Frysingere51a2652014-01-18 02:36:16 -0500407 try:
408 ctx.Copy(os.path.join(src_path, src), os.path.join(dst_path, dst))
Amey Deshpandea936c622015-08-12 17:27:54 -0700409 success = True
Mike Frysingere51a2652014-01-18 02:36:16 -0500410 except gs.GSNoSuchKey:
Ralph Nathan446aee92015-03-23 14:44:56 -0700411 logging.warning('Skipping %s as it does not exist', src)
Mike Frysinger4495b032014-03-05 17:24:03 -0500412 except gs.GSContextException:
Amey Deshpandea936c622015-08-12 17:27:54 -0700413 unknown_error[0] = True
Ralph Nathan59900422015-03-24 10:41:17 -0700414 logging.error('Skipping %s due to unknown GS error', src, exc_info=True)
Amey Deshpandea936c622015-08-12 17:27:54 -0700415 return dst if success else None
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400416
Amey Deshpandea936c622015-08-12 17:27:54 -0700417 for src, dst, suffix in files_to_copy_only:
418 _CopyFileToGS(src, dst, suffix)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400419
Amey Deshpandea936c622015-08-12 17:27:54 -0700420 # Clear the list of files to sign before adding new artifacts.
421 files_to_sign = []
422
423 def _AddToFilesToSign(image_type, dst, suffix):
424 assert dst.endswith('.' + suffix), (
425 'dst: %s, suffix: %s' % (dst, suffix))
426 dst_base = dst[:-(len(suffix) + 1)]
427 files_to_sign.append([image_type, dst_base, suffix])
428
429 for src, dst, suffix, image_type in files_to_copy_and_maybe_sign:
430 dst = _CopyFileToGS(src, dst, suffix)
431 if dst and (not sign_types or image_type in sign_types):
432 _AddToFilesToSign(image_type, dst, suffix)
433
434 for src, dst, suffix, image_type in files_to_maybe_copy_and_sign:
435 if sign_types and image_type in sign_types:
436 dst = _CopyFileToGS(src, dst, suffix)
437 if dst:
438 _AddToFilesToSign(image_type, dst, suffix)
439
440 logging.debug('Files to sign: %s', files_to_sign)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400441 # Now go through the subset for signing.
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500442 for image_type, dst_name, suffix in files_to_sign:
443 try:
444 input_insns = InputInsns(board, image_type=image_type)
445 except MissingBoardInstructions as e:
446 logging.info('Nothing to sign: %s', e)
447 continue
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400448
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500449 dst_archive = '%s.%s' % (dst_name, suffix)
450 sect_general['archive'] = dst_archive
451 sect_general['type'] = image_type
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400452
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500453 # In the default/automatic mode, only flag files for signing if the
454 # archives were actually uploaded in a previous stage. This additional
455 # check can be removed in future once |sign_types| becomes a required
456 # argument.
457 # TODO: Make |sign_types| a required argument.
458 gs_artifact_path = os.path.join(dst_path, dst_archive)
459 exists = False
460 try:
461 exists = ctx.Exists(gs_artifact_path)
462 except gs.GSContextException:
463 unknown_error[0] = True
464 logging.error('Unknown error while checking %s', gs_artifact_path,
465 exc_info=True)
466 if not exists:
467 logging.info('%s does not exist. Nothing to sign.',
468 gs_artifact_path)
469 continue
470
471 # Figure out which keysets have been requested for this type. We sort the
472 # forced set so tests/runtime behavior is stable, and because we need/want
473 # list since we'll be indexing it below w/multiple keysets.
474 keysets = sorted(force_keysets)
475 if not keysets:
476 keysets = input_insns.GetKeysets()
477 if not keysets:
478 logging.warning('Skipping %s image signing due to no keysets',
479 image_type)
480
481 for keyset in keysets:
482 sect_insns['keyset'] = keyset
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400483
484 # Generate the insn file for this artifact that the signer will use,
485 # and flag it for signing.
486 with tempfile.NamedTemporaryFile(
487 bufsize=0, prefix='pushimage.insns.') as insns_path:
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500488 input_insns.OutputInsns(insns_path.name, sect_insns, sect_general)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400489
490 gs_insns_path = '%s/%s' % (dst_path, dst_name)
491 if keyset != keysets[0]:
492 gs_insns_path += '-%s' % keyset
493 gs_insns_path += '.instructions'
494
Mike Frysinger4495b032014-03-05 17:24:03 -0500495 try:
496 ctx.Copy(insns_path.name, gs_insns_path)
497 except gs.GSContextException:
Amey Deshpandea936c622015-08-12 17:27:54 -0700498 unknown_error[0] = True
Ralph Nathan59900422015-03-24 10:41:17 -0700499 logging.error('Unknown error while uploading insns %s',
500 gs_insns_path, exc_info=True)
Mike Frysinger4495b032014-03-05 17:24:03 -0500501 continue
502
503 try:
504 MarkImageToBeSigned(ctx, tbs_base, gs_insns_path, priority)
505 except gs.GSContextException:
Amey Deshpandea936c622015-08-12 17:27:54 -0700506 unknown_error[0] = True
Ralph Nathan59900422015-03-24 10:41:17 -0700507 logging.error('Unknown error while marking for signing %s',
508 gs_insns_path, exc_info=True)
Mike Frysinger4495b032014-03-05 17:24:03 -0500509 continue
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500510 logging.info('Signing %s image with keyset %s at %s', image_type,
511 keyset, gs_insns_path)
Don Garrett9459c2f2014-01-22 18:20:24 -0800512 instruction_urls.setdefault(channel, []).append(gs_insns_path)
513
Amey Deshpandea936c622015-08-12 17:27:54 -0700514 if unknown_error[0]:
Mike Frysinger4495b032014-03-05 17:24:03 -0500515 raise PushError('hit some unknown error(s)', instruction_urls)
516
Don Garrett9459c2f2014-01-22 18:20:24 -0800517 return instruction_urls
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400518
519
520def main(argv):
521 parser = commandline.ArgumentParser(description=__doc__)
522
523 # The type of image_dir will strip off trailing slashes (makes later
524 # processing simpler and the display prettier).
525 parser.add_argument('image_dir', default=None, type='local_or_gs_path',
526 help='full path of source artifacts to upload')
527 parser.add_argument('--board', default=None, required=True,
528 help='board to generate symbols for')
529 parser.add_argument('--profile', default=None,
530 help='board profile in use (e.g. "asan")')
531 parser.add_argument('--version', default=None,
532 help='version info (normally extracted from image_dir)')
533 parser.add_argument('-n', '--dry-run', default=False, action='store_true',
534 help='show what would be done, but do not upload')
535 parser.add_argument('-M', '--mock', default=False, action='store_true',
536 help='upload things to a testing bucket (dev testing)')
David Rileyf8205122015-09-04 13:46:36 -0700537 parser.add_argument('--test-sign', default=[], action='append',
538 choices=TEST_KEYSETS,
539 help='mung signing behavior to sign w/ test keys')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400540 parser.add_argument('--priority', type=int, default=50,
541 help='set signing priority (lower == higher prio)')
542 parser.add_argument('--sign-types', default=None, nargs='+',
Amey Deshpandea936c622015-08-12 17:27:54 -0700543 choices=_SUPPORTED_IMAGE_TYPES,
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400544 help='only sign specified image types')
Mike Frysinger09fe0122014-02-09 02:44:05 -0500545 parser.add_argument('--yes', action='store_true', default=False,
546 help='answer yes to all prompts')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400547
548 opts = parser.parse_args(argv)
549 opts.Freeze()
550
David Rileyf8205122015-09-04 13:46:36 -0700551 force_keysets = set(['%s-%s' % (TEST_KEYSET_PREFIX, x)
552 for x in opts.test_sign])
Mike Frysingerdad40d62014-02-09 02:18:02 -0500553
Mike Frysinger09fe0122014-02-09 02:44:05 -0500554 # If we aren't using mock or test or dry run mode, then let's prompt the user
555 # to make sure they actually want to do this. It's rare that people want to
556 # run this directly and hit the release bucket.
557 if not (opts.mock or force_keysets or opts.dry_run) and not opts.yes:
558 prolog = '\n'.join(textwrap.wrap(textwrap.dedent(
559 'Uploading images for signing to the *release* bucket is not something '
560 'you generally should be doing yourself.'), 80)).strip()
561 if not cros_build_lib.BooleanPrompt(
562 prompt='Are you sure you want to sign these images',
563 default=False, prolog=prolog):
564 cros_build_lib.Die('better safe than sorry')
565
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400566 PushImage(opts.image_dir, opts.board, versionrev=opts.version,
567 profile=opts.profile, priority=opts.priority,
Mike Frysingerdad40d62014-02-09 02:18:02 -0500568 sign_types=opts.sign_types, dry_run=opts.dry_run, mock=opts.mock,
569 force_keysets=force_keysets)