blob: d81beebbdf9f193f01d04e1d08c73f2520bdaa0b [file] [log] [blame]
#!/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:])