| #!/usr/bin/env python |
| # Copyright 2019 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Installs and then runs cipd. |
| |
| This script installs cipd in ./tools/ and then executes it, passing through |
| all arguments. |
| """ |
| |
| from __future__ import print_function |
| |
| import hashlib |
| import httplib |
| import os |
| import platform |
| import subprocess |
| import sys |
| import tempfile |
| |
| |
| SCRIPT_DIR = os.path.dirname(__file__) |
| VERSION_FILE = os.path.join(SCRIPT_DIR, '.cipd_version') |
| DIGESTS_FILE = VERSION_FILE + '.digests' |
| # Put CIPD client in tools so that users can easily get it in their PATH. |
| CLIENT = os.path.join(SCRIPT_DIR, 'tools', 'cipd') |
| CIPD_HOST = 'chrome-infra-packages.appspot.com' |
| |
| |
| def platform_normalized(): |
| """Normalize platform into format expected in CIPD paths.""" |
| |
| try: |
| os_name = platform.system().lower() |
| return { |
| 'linux': 'linux', |
| 'mac': 'mac', |
| 'darwin': 'mac', |
| 'windows': 'windows', |
| }[os_name] |
| except KeyError: |
| raise Exception('unrecognized os: {}'.format(os_name)) |
| |
| |
| def arch_normalized(): |
| """Normalize arch into format expected in CIPD paths.""" |
| |
| machine = platform.machine() |
| if machine.startswith('arm'): |
| return machine |
| if machine.endswith('64'): |
| return 'amd64' |
| if machine.endswith('86'): |
| return '386' |
| raise Exception('unrecognized arch: {}'.format(machine)) |
| |
| |
| def user_agent(): |
| try: |
| rev = subprocess.check_output( |
| ['git', '-C', SCRIPT_DIR, 'rev-parse', 'HEAD']).strip() |
| except subprocess.CalledProcessError: |
| rev = '???' |
| return 'fuchsia-infra/tools/{}'.format(rev) |
| |
| |
| def actual_hash(path): |
| hasher = hashlib.sha256() |
| with open(path, 'rb') as ins: |
| hasher.update(ins.read()) |
| return hasher.hexdigest() |
| |
| |
| def expected_hash(): |
| with open(DIGESTS_FILE, 'r') as ins: |
| for line in ins: |
| line = line.strip() |
| if line.startswith('#') or not line: |
| continue |
| plat, hashtype, hashval = line.split() |
| if (hashtype == 'sha256' and |
| plat == '{}-{}'.format(platform_normalized(), arch_normalized())): |
| return hashval |
| raise Exception( |
| 'platform {} not in {}'.format(platform_normalized(), DIGESTS_FILE)) |
| |
| |
| def client_bytes(): |
| with open(VERSION_FILE, 'r') as ins: |
| version = ins.read().strip() |
| |
| # TODO(mohrr) do this with httplib instead of curl |
| cmd = [ |
| 'curl', |
| '--fail', |
| '--progress-bar', |
| 'https://{host}/client?platform={platform}-{arch}&' |
| 'version={version}'.format( |
| host=CIPD_HOST, |
| platform=platform_normalized(), |
| arch=arch_normalized(), |
| version=version), |
| '--user-agent', user_agent(), |
| '--location', |
| '--output', '-', |
| ] |
| return subprocess.check_output(cmd) |
| |
| |
| def bootstrap(): |
| """Bootstrap cipd client installation.""" |
| |
| client_dir = os.path.dirname(CLIENT) |
| if not os.path.isdir(client_dir): |
| os.makedirs(client_dir) |
| |
| print( |
| 'Bootstrapping cipd client for {}-{}'.format( |
| platform_normalized(), arch_normalized())) |
| tmp_path = os.path.join(SCRIPT_DIR, 'tools', '.cipd') |
| with open(tmp_path, 'wb') as tmp: |
| tmp.write(client_bytes()) |
| |
| expected = expected_hash() |
| actual = actual_hash(tmp_path) |
| |
| if expected != actual: |
| raise Exception('digest of downloaded CIPD client is incorrect, check that ' |
| 'digests file is current') |
| |
| os.chmod(tmp_path, 0755) |
| os.rename(tmp_path, CLIENT) |
| |
| |
| def selfupdate(): |
| """Update cipd client.""" |
| |
| cmd = [ |
| CLIENT, |
| 'selfupdate', |
| '-version-file', VERSION_FILE, |
| '-service-url', 'https://{}'.format(CIPD_HOST), |
| ] |
| subprocess.check_call(cmd) |
| |
| |
| def init(): |
| """Install/update cipd client.""" |
| |
| os.environ['CIPD_HTTP_USER_AGENT_PREFIX'] = user_agent() |
| |
| try: |
| if not os.path.isfile(CLIENT): |
| bootstrap() |
| |
| try: |
| selfupdate() |
| except subprocess.CalledProcessError: |
| print('CIPD selfupdate failed. Bootstrapping then retrying...', |
| file=sys.stderr) |
| bootstrap() |
| selfupdate() |
| |
| except Exception: |
| print('Failed to initialize CIPD. Run ' |
| '`CIPD_HTTP_USER_AGENT_PREFIX={user_agent}/manual {client} ' |
| "selfupdate -version-file '{version_file}'` " |
| 'to diagnose if this is persistent.'.format( |
| user_agent=user_agent(), |
| client=CLIENT, |
| version_file=VERSION_FILE, |
| ), file=sys.stderr) |
| raise |
| |
| |
| if __name__ == '__main__': |
| init() |
| subprocess.check_call([CLIENT] + sys.argv[1:]) |