1*6777b538SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*6777b538SAndroid Build Coastguard Worker# Copyright 2020 The Chromium Authors 3*6777b538SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 4*6777b538SAndroid Build Coastguard Worker# found in the LICENSE file. 5*6777b538SAndroid Build Coastguard Worker"""Updates the Fuchsia images to the given revision. Should be used in a 6*6777b538SAndroid Build Coastguard Worker'hooks_os' entry so that it only runs when .gclient's target_os includes 7*6777b538SAndroid Build Coastguard Worker'fuchsia'. Note, for a smooth transition, this file automatically adds 8*6777b538SAndroid Build Coastguard Worker'-release' at the end of the image gcs file name to eliminate the difference 9*6777b538SAndroid Build Coastguard Workerbetween product bundle v2 and gce files.""" 10*6777b538SAndroid Build Coastguard Worker 11*6777b538SAndroid Build Coastguard Worker# TODO(crbug.com/1496426): Remove this file. 12*6777b538SAndroid Build Coastguard Worker 13*6777b538SAndroid Build Coastguard Workerimport argparse 14*6777b538SAndroid Build Coastguard Workerimport itertools 15*6777b538SAndroid Build Coastguard Workerimport logging 16*6777b538SAndroid Build Coastguard Workerimport os 17*6777b538SAndroid Build Coastguard Workerimport re 18*6777b538SAndroid Build Coastguard Workerimport subprocess 19*6777b538SAndroid Build Coastguard Workerimport sys 20*6777b538SAndroid Build Coastguard Workerfrom typing import Dict, Optional 21*6777b538SAndroid Build Coastguard Worker 22*6777b538SAndroid Build Coastguard Workersys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 23*6777b538SAndroid Build Coastguard Worker 'test'))) 24*6777b538SAndroid Build Coastguard Worker 25*6777b538SAndroid Build Coastguard Workerfrom common import DIR_SRC_ROOT, IMAGES_ROOT, get_host_os, \ 26*6777b538SAndroid Build Coastguard Worker make_clean_directory 27*6777b538SAndroid Build Coastguard Worker 28*6777b538SAndroid Build Coastguard Workerfrom gcs_download import DownloadAndUnpackFromCloudStorage 29*6777b538SAndroid Build Coastguard Worker 30*6777b538SAndroid Build Coastguard Workerfrom update_sdk import GetSDKOverrideGCSPath 31*6777b538SAndroid Build Coastguard Worker 32*6777b538SAndroid Build Coastguard WorkerIMAGE_SIGNATURE_FILE = '.hash' 33*6777b538SAndroid Build Coastguard Worker 34*6777b538SAndroid Build Coastguard Worker 35*6777b538SAndroid Build Coastguard Worker# TODO(crbug.com/1138433): Investigate whether we can deprecate 36*6777b538SAndroid Build Coastguard Worker# use of sdk_bucket.txt. 37*6777b538SAndroid Build Coastguard Workerdef GetOverrideCloudStorageBucket(): 38*6777b538SAndroid Build Coastguard Worker """Read bucket entry from sdk_bucket.txt""" 39*6777b538SAndroid Build Coastguard Worker return ReadFile('sdk-bucket.txt').strip() 40*6777b538SAndroid Build Coastguard Worker 41*6777b538SAndroid Build Coastguard Worker 42*6777b538SAndroid Build Coastguard Workerdef ReadFile(filename): 43*6777b538SAndroid Build Coastguard Worker """Read a file in this directory.""" 44*6777b538SAndroid Build Coastguard Worker with open(os.path.join(os.path.dirname(__file__), filename), 'r') as f: 45*6777b538SAndroid Build Coastguard Worker return f.read() 46*6777b538SAndroid Build Coastguard Worker 47*6777b538SAndroid Build Coastguard Worker 48*6777b538SAndroid Build Coastguard Workerdef StrExpansion(): 49*6777b538SAndroid Build Coastguard Worker return lambda str_value: str_value 50*6777b538SAndroid Build Coastguard Worker 51*6777b538SAndroid Build Coastguard Worker 52*6777b538SAndroid Build Coastguard Workerdef VarLookup(local_scope): 53*6777b538SAndroid Build Coastguard Worker return lambda var_name: local_scope['vars'][var_name] 54*6777b538SAndroid Build Coastguard Worker 55*6777b538SAndroid Build Coastguard Worker 56*6777b538SAndroid Build Coastguard Workerdef GetImageHashList(bucket): 57*6777b538SAndroid Build Coastguard Worker """Read filename entries from sdk-hash-files.list (one per line), substitute 58*6777b538SAndroid Build Coastguard Worker {platform} in each entry if present, and read from each filename.""" 59*6777b538SAndroid Build Coastguard Worker assert (get_host_os() == 'linux') 60*6777b538SAndroid Build Coastguard Worker filenames = [ 61*6777b538SAndroid Build Coastguard Worker line.strip() for line in ReadFile('sdk-hash-files.list').replace( 62*6777b538SAndroid Build Coastguard Worker '{platform}', 'linux_internal').splitlines() 63*6777b538SAndroid Build Coastguard Worker ] 64*6777b538SAndroid Build Coastguard Worker image_hashes = [ReadFile(filename).strip() for filename in filenames] 65*6777b538SAndroid Build Coastguard Worker return image_hashes 66*6777b538SAndroid Build Coastguard Worker 67*6777b538SAndroid Build Coastguard Worker 68*6777b538SAndroid Build Coastguard Workerdef ParseDepsDict(deps_content): 69*6777b538SAndroid Build Coastguard Worker local_scope = {} 70*6777b538SAndroid Build Coastguard Worker global_scope = { 71*6777b538SAndroid Build Coastguard Worker 'Str': StrExpansion(), 72*6777b538SAndroid Build Coastguard Worker 'Var': VarLookup(local_scope), 73*6777b538SAndroid Build Coastguard Worker 'deps_os': {}, 74*6777b538SAndroid Build Coastguard Worker } 75*6777b538SAndroid Build Coastguard Worker exec(deps_content, global_scope, local_scope) 76*6777b538SAndroid Build Coastguard Worker return local_scope 77*6777b538SAndroid Build Coastguard Worker 78*6777b538SAndroid Build Coastguard Worker 79*6777b538SAndroid Build Coastguard Workerdef ParseDepsFile(filename): 80*6777b538SAndroid Build Coastguard Worker with open(filename, 'rb') as f: 81*6777b538SAndroid Build Coastguard Worker deps_content = f.read() 82*6777b538SAndroid Build Coastguard Worker return ParseDepsDict(deps_content) 83*6777b538SAndroid Build Coastguard Worker 84*6777b538SAndroid Build Coastguard Worker 85*6777b538SAndroid Build Coastguard Workerdef GetImageHash(bucket): 86*6777b538SAndroid Build Coastguard Worker """Gets the hash identifier of the newest generation of images.""" 87*6777b538SAndroid Build Coastguard Worker if bucket == 'fuchsia-sdk': 88*6777b538SAndroid Build Coastguard Worker hashes = GetImageHashList(bucket) 89*6777b538SAndroid Build Coastguard Worker return max(hashes) 90*6777b538SAndroid Build Coastguard Worker deps_file = os.path.join(DIR_SRC_ROOT, 'DEPS') 91*6777b538SAndroid Build Coastguard Worker return ParseDepsFile(deps_file)['vars']['fuchsia_version'].split(':')[1] 92*6777b538SAndroid Build Coastguard Worker 93*6777b538SAndroid Build Coastguard Worker 94*6777b538SAndroid Build Coastguard Workerdef GetImageSignature(image_hash, boot_images): 95*6777b538SAndroid Build Coastguard Worker return 'gn:{image_hash}:{boot_images}:'.format(image_hash=image_hash, 96*6777b538SAndroid Build Coastguard Worker boot_images=boot_images) 97*6777b538SAndroid Build Coastguard Worker 98*6777b538SAndroid Build Coastguard Worker 99*6777b538SAndroid Build Coastguard Workerdef GetAllImages(boot_image_names): 100*6777b538SAndroid Build Coastguard Worker if not boot_image_names: 101*6777b538SAndroid Build Coastguard Worker return 102*6777b538SAndroid Build Coastguard Worker 103*6777b538SAndroid Build Coastguard Worker all_device_types = ['generic', 'qemu'] 104*6777b538SAndroid Build Coastguard Worker all_archs = ['x64', 'arm64'] 105*6777b538SAndroid Build Coastguard Worker 106*6777b538SAndroid Build Coastguard Worker images_to_download = set() 107*6777b538SAndroid Build Coastguard Worker 108*6777b538SAndroid Build Coastguard Worker for boot_image in boot_image_names.split(','): 109*6777b538SAndroid Build Coastguard Worker components = boot_image.split('.') 110*6777b538SAndroid Build Coastguard Worker if len(components) != 2: 111*6777b538SAndroid Build Coastguard Worker continue 112*6777b538SAndroid Build Coastguard Worker 113*6777b538SAndroid Build Coastguard Worker device_type, arch = components 114*6777b538SAndroid Build Coastguard Worker device_images = all_device_types if device_type == '*' else [device_type] 115*6777b538SAndroid Build Coastguard Worker arch_images = all_archs if arch == '*' else [arch] 116*6777b538SAndroid Build Coastguard Worker images_to_download.update(itertools.product(device_images, arch_images)) 117*6777b538SAndroid Build Coastguard Worker return images_to_download 118*6777b538SAndroid Build Coastguard Worker 119*6777b538SAndroid Build Coastguard Worker 120*6777b538SAndroid Build Coastguard Workerdef DownloadBootImages(bucket, image_hash, boot_image_names, image_root_dir): 121*6777b538SAndroid Build Coastguard Worker images_to_download = GetAllImages(boot_image_names) 122*6777b538SAndroid Build Coastguard Worker for image_to_download in images_to_download: 123*6777b538SAndroid Build Coastguard Worker device_type = image_to_download[0] 124*6777b538SAndroid Build Coastguard Worker arch = image_to_download[1] 125*6777b538SAndroid Build Coastguard Worker image_output_dir = os.path.join(image_root_dir, arch, device_type) 126*6777b538SAndroid Build Coastguard Worker if os.path.exists(image_output_dir): 127*6777b538SAndroid Build Coastguard Worker continue 128*6777b538SAndroid Build Coastguard Worker 129*6777b538SAndroid Build Coastguard Worker logging.info('Downloading Fuchsia boot images for %s.%s...', device_type, 130*6777b538SAndroid Build Coastguard Worker arch) 131*6777b538SAndroid Build Coastguard Worker 132*6777b538SAndroid Build Coastguard Worker # Legacy images use different naming conventions. See fxbug.dev/85552. 133*6777b538SAndroid Build Coastguard Worker legacy_delimiter_device_types = ['qemu', 'generic'] 134*6777b538SAndroid Build Coastguard Worker if bucket == 'fuchsia-sdk' or \ 135*6777b538SAndroid Build Coastguard Worker device_type not in legacy_delimiter_device_types: 136*6777b538SAndroid Build Coastguard Worker type_arch_connector = '.' 137*6777b538SAndroid Build Coastguard Worker else: 138*6777b538SAndroid Build Coastguard Worker type_arch_connector = '-' 139*6777b538SAndroid Build Coastguard Worker 140*6777b538SAndroid Build Coastguard Worker images_tarball_url = 'gs://{bucket}/development/{image_hash}/images/'\ 141*6777b538SAndroid Build Coastguard Worker '{device_type}{type_arch_connector}{arch}.tgz'.format( 142*6777b538SAndroid Build Coastguard Worker bucket=bucket, image_hash=image_hash, device_type=device_type, 143*6777b538SAndroid Build Coastguard Worker type_arch_connector=type_arch_connector, arch=arch) 144*6777b538SAndroid Build Coastguard Worker try: 145*6777b538SAndroid Build Coastguard Worker DownloadAndUnpackFromCloudStorage(images_tarball_url, image_output_dir) 146*6777b538SAndroid Build Coastguard Worker except subprocess.CalledProcessError as e: 147*6777b538SAndroid Build Coastguard Worker logging.exception('Failed to download image %s from URL: %s', 148*6777b538SAndroid Build Coastguard Worker image_to_download, images_tarball_url) 149*6777b538SAndroid Build Coastguard Worker raise e 150*6777b538SAndroid Build Coastguard Worker 151*6777b538SAndroid Build Coastguard Worker 152*6777b538SAndroid Build Coastguard Workerdef _GetImageOverrideInfo() -> Optional[Dict[str, str]]: 153*6777b538SAndroid Build Coastguard Worker """Get the bucket location from sdk_override.txt.""" 154*6777b538SAndroid Build Coastguard Worker location = GetSDKOverrideGCSPath() 155*6777b538SAndroid Build Coastguard Worker if not location: 156*6777b538SAndroid Build Coastguard Worker return None 157*6777b538SAndroid Build Coastguard Worker 158*6777b538SAndroid Build Coastguard Worker m = re.match(r'gs://([^/]+)/development/([^/]+)/?(?:sdk)?', location) 159*6777b538SAndroid Build Coastguard Worker if not m: 160*6777b538SAndroid Build Coastguard Worker raise ValueError('Badly formatted image override location %s' % location) 161*6777b538SAndroid Build Coastguard Worker 162*6777b538SAndroid Build Coastguard Worker return { 163*6777b538SAndroid Build Coastguard Worker 'bucket': m.group(1), 164*6777b538SAndroid Build Coastguard Worker 'image_hash': m.group(2), 165*6777b538SAndroid Build Coastguard Worker } 166*6777b538SAndroid Build Coastguard Worker 167*6777b538SAndroid Build Coastguard Worker 168*6777b538SAndroid Build Coastguard Workerdef GetImageLocationInfo(default_bucket: str, 169*6777b538SAndroid Build Coastguard Worker allow_override: bool = True) -> Dict[str, str]: 170*6777b538SAndroid Build Coastguard Worker """Figures out where to pull the image from. 171*6777b538SAndroid Build Coastguard Worker 172*6777b538SAndroid Build Coastguard Worker Defaults to the provided default bucket and generates the hash from defaults. 173*6777b538SAndroid Build Coastguard Worker If sdk_override.txt exists (and is allowed) it uses that bucket instead. 174*6777b538SAndroid Build Coastguard Worker 175*6777b538SAndroid Build Coastguard Worker Args: 176*6777b538SAndroid Build Coastguard Worker default_bucket: a given default for what bucket to use 177*6777b538SAndroid Build Coastguard Worker allow_override: allow SDK override to be used. 178*6777b538SAndroid Build Coastguard Worker 179*6777b538SAndroid Build Coastguard Worker Returns: 180*6777b538SAndroid Build Coastguard Worker A dictionary containing the bucket and image_hash 181*6777b538SAndroid Build Coastguard Worker """ 182*6777b538SAndroid Build Coastguard Worker # if sdk_override.txt exists (and is allowed) use the image from that bucket. 183*6777b538SAndroid Build Coastguard Worker if allow_override: 184*6777b538SAndroid Build Coastguard Worker override = _GetImageOverrideInfo() 185*6777b538SAndroid Build Coastguard Worker if override: 186*6777b538SAndroid Build Coastguard Worker return override 187*6777b538SAndroid Build Coastguard Worker 188*6777b538SAndroid Build Coastguard Worker # Use the bucket in sdk-bucket.txt if an entry exists. 189*6777b538SAndroid Build Coastguard Worker # Otherwise use the default bucket. 190*6777b538SAndroid Build Coastguard Worker bucket = GetOverrideCloudStorageBucket() or default_bucket 191*6777b538SAndroid Build Coastguard Worker return { 192*6777b538SAndroid Build Coastguard Worker 'bucket': bucket, 193*6777b538SAndroid Build Coastguard Worker 'image_hash': GetImageHash(bucket), 194*6777b538SAndroid Build Coastguard Worker } 195*6777b538SAndroid Build Coastguard Worker 196*6777b538SAndroid Build Coastguard Worker 197*6777b538SAndroid Build Coastguard Workerdef main(): 198*6777b538SAndroid Build Coastguard Worker parser = argparse.ArgumentParser() 199*6777b538SAndroid Build Coastguard Worker parser.add_argument('--verbose', 200*6777b538SAndroid Build Coastguard Worker '-v', 201*6777b538SAndroid Build Coastguard Worker action='store_true', 202*6777b538SAndroid Build Coastguard Worker help='Enable debug-level logging.') 203*6777b538SAndroid Build Coastguard Worker parser.add_argument( 204*6777b538SAndroid Build Coastguard Worker '--boot-images', 205*6777b538SAndroid Build Coastguard Worker type=str, 206*6777b538SAndroid Build Coastguard Worker required=True, 207*6777b538SAndroid Build Coastguard Worker help='List of boot images to download, represented as a comma separated ' 208*6777b538SAndroid Build Coastguard Worker 'list. Wildcards are allowed. ') 209*6777b538SAndroid Build Coastguard Worker parser.add_argument( 210*6777b538SAndroid Build Coastguard Worker '--default-bucket', 211*6777b538SAndroid Build Coastguard Worker type=str, 212*6777b538SAndroid Build Coastguard Worker default='fuchsia', 213*6777b538SAndroid Build Coastguard Worker help='The Google Cloud Storage bucket in which the Fuchsia images are ' 214*6777b538SAndroid Build Coastguard Worker 'stored. Entry in sdk-bucket.txt will override this flag.') 215*6777b538SAndroid Build Coastguard Worker parser.add_argument( 216*6777b538SAndroid Build Coastguard Worker '--image-root-dir', 217*6777b538SAndroid Build Coastguard Worker default=IMAGES_ROOT, 218*6777b538SAndroid Build Coastguard Worker help='Specify the root directory of the downloaded images. Optional') 219*6777b538SAndroid Build Coastguard Worker parser.add_argument( 220*6777b538SAndroid Build Coastguard Worker '--allow-override', 221*6777b538SAndroid Build Coastguard Worker action='store_true', 222*6777b538SAndroid Build Coastguard Worker help='Whether sdk_override.txt can be used for fetching the image, if ' 223*6777b538SAndroid Build Coastguard Worker 'it exists.') 224*6777b538SAndroid Build Coastguard Worker args = parser.parse_args() 225*6777b538SAndroid Build Coastguard Worker 226*6777b538SAndroid Build Coastguard Worker logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO) 227*6777b538SAndroid Build Coastguard Worker 228*6777b538SAndroid Build Coastguard Worker # If no boot images need to be downloaded, exit. 229*6777b538SAndroid Build Coastguard Worker if not args.boot_images: 230*6777b538SAndroid Build Coastguard Worker return 0 231*6777b538SAndroid Build Coastguard Worker 232*6777b538SAndroid Build Coastguard Worker for index, item in enumerate(args.boot_images): 233*6777b538SAndroid Build Coastguard Worker # The gclient configuration is in the src-internal and cannot be changed 234*6777b538SAndroid Build Coastguard Worker # atomically with the test script in src. So the update_images.py needs to 235*6777b538SAndroid Build Coastguard Worker # support both scenarios before being fully deprecated for older milestones. 236*6777b538SAndroid Build Coastguard Worker if not item.endswith('-release'): 237*6777b538SAndroid Build Coastguard Worker args.boot_images[index] = item + '-release' 238*6777b538SAndroid Build Coastguard Worker 239*6777b538SAndroid Build Coastguard Worker # Check whether there's Fuchsia support for this platform. 240*6777b538SAndroid Build Coastguard Worker get_host_os() 241*6777b538SAndroid Build Coastguard Worker image_info = GetImageLocationInfo(args.default_bucket, args.allow_override) 242*6777b538SAndroid Build Coastguard Worker 243*6777b538SAndroid Build Coastguard Worker bucket = image_info['bucket'] 244*6777b538SAndroid Build Coastguard Worker image_hash = image_info['image_hash'] 245*6777b538SAndroid Build Coastguard Worker 246*6777b538SAndroid Build Coastguard Worker if not image_hash: 247*6777b538SAndroid Build Coastguard Worker return 1 248*6777b538SAndroid Build Coastguard Worker 249*6777b538SAndroid Build Coastguard Worker signature_filename = os.path.join(args.image_root_dir, IMAGE_SIGNATURE_FILE) 250*6777b538SAndroid Build Coastguard Worker current_signature = (open(signature_filename, 'r').read().strip() 251*6777b538SAndroid Build Coastguard Worker if os.path.exists(signature_filename) else '') 252*6777b538SAndroid Build Coastguard Worker new_signature = GetImageSignature(image_hash, args.boot_images) 253*6777b538SAndroid Build Coastguard Worker if current_signature != new_signature: 254*6777b538SAndroid Build Coastguard Worker logging.info('Downloading Fuchsia images %s from bucket %s...', image_hash, 255*6777b538SAndroid Build Coastguard Worker bucket) 256*6777b538SAndroid Build Coastguard Worker make_clean_directory(args.image_root_dir) 257*6777b538SAndroid Build Coastguard Worker 258*6777b538SAndroid Build Coastguard Worker try: 259*6777b538SAndroid Build Coastguard Worker DownloadBootImages(bucket, image_hash, args.boot_images, 260*6777b538SAndroid Build Coastguard Worker args.image_root_dir) 261*6777b538SAndroid Build Coastguard Worker with open(signature_filename, 'w') as f: 262*6777b538SAndroid Build Coastguard Worker f.write(new_signature) 263*6777b538SAndroid Build Coastguard Worker except subprocess.CalledProcessError as e: 264*6777b538SAndroid Build Coastguard Worker logging.exception("command '%s' failed with status %d.%s", 265*6777b538SAndroid Build Coastguard Worker ' '.join(e.cmd), e.returncode, 266*6777b538SAndroid Build Coastguard Worker ' Details: ' + e.output if e.output else '') 267*6777b538SAndroid Build Coastguard Worker raise e 268*6777b538SAndroid Build Coastguard Worker else: 269*6777b538SAndroid Build Coastguard Worker logging.info('Signatures matched! Got %s', new_signature) 270*6777b538SAndroid Build Coastguard Worker 271*6777b538SAndroid Build Coastguard Worker return 0 272*6777b538SAndroid Build Coastguard Worker 273*6777b538SAndroid Build Coastguard Worker 274*6777b538SAndroid Build Coastguard Workerif __name__ == '__main__': 275*6777b538SAndroid Build Coastguard Worker sys.exit(main()) 276