blob: fe0eece7df47ae550bdeb75b49d40958d823ab96 [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 urlparse
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():
"""Pulls expected hash from digests file."""
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():
"""Pull down the CIPD client and return it as a bytes object.
Often CIPD_HOST returns a 302 FOUND with a pointer to storage.googleapis.com,
so this needs to handle redirects, but it shouldn't require the initial
response to be a redirect either.
"""
with open(VERSION_FILE, "r") as ins:
version = ins.read().strip()
conn = httplib.HTTPSConnection(CIPD_HOST)
path = "/client?platform={platform}-{arch}&version={version}".format(
platform=platform_normalized(), arch=arch_normalized(), version=version
)
for _ in range(10):
conn.request("GET", path)
res = conn.getresponse()
# Have to read the response before making a new request, so make sure
# we always read it.
content = res.read()
# Found client bytes.
if res.status == httplib.OK:
return content
# Redirecting to another location.
elif res.status == httplib.FOUND:
location = res.getheader("location")
url = urlparse.urlparse(location)
if url.netloc != conn.host:
conn = httplib.HTTPSConnection(url.netloc)
path = "{}?{}".format(url.path, url.query)
# Some kind of error in this response.
else:
break
raise Exception("failed to download client")
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:])