[prebuilt] Updates to cipd.py error handling
Improve error handling and messages in cipd.py.
Change-Id: I7db679f54fc3f11417a94127822194cc21008d5d
Reviewed-on: https://fuchsia-review.googlesource.com/c/infra/prebuilt/+/405395
Commit-Queue: Rob Mohr <mohrr@google.com>
Reviewed-by: Gary Boone <gboone@google.com>
diff --git a/cipd.py b/cipd.py
index fe0eece..f3401e2 100755
--- a/cipd.py
+++ b/cipd.py
@@ -4,24 +4,35 @@
# found in the LICENSE file.
"""Installs and then runs cipd.
-This script installs cipd in ./tools/ and then executes it, passing through
-all arguments.
+This script installs cipd in ./tools/ (if necessary) and then executes it,
+passing through all arguments.
+
+Must be tested with Python 2 and Python 3.
"""
from __future__ import print_function
import hashlib
-import httplib
import os
import platform
+import ssl
import subprocess
import sys
-import urlparse
+try:
+ import httplib
+except ImportError:
+ import http.client as httplib # type: ignore
+
+try:
+ import urlparse # Python 2.
+except ImportError:
+ import urllib.parse as urlparse # type: ignore
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"
@@ -43,8 +54,8 @@
"""Normalize arch into format expected in CIPD paths."""
machine = platform.machine()
- if machine.startswith("arm"):
- return machine
+ if machine.startswith(("arm", "aarch")):
+ return machine.replace("aarch", "arm")
if machine.endswith("64"):
return "amd64"
if machine.endswith("86"):
@@ -53,16 +64,24 @@
def user_agent():
+ """Generate a user-agent based on the project name and current hash."""
+
try:
rev = subprocess.check_output(
["git", "-C", SCRIPT_DIR, "rev-parse", "HEAD"]
).strip()
except subprocess.CalledProcessError:
rev = "???"
+
+ if isinstance(rev, bytes):
+ rev = rev.decode()
+
return "fuchsia-infra/tools/{}".format(rev)
def actual_hash(path):
+ """Hash the file at path and return it."""
+
hasher = hashlib.sha256()
with open(path, "rb") as ins:
hasher.update(ins.read())
@@ -72,46 +91,81 @@
def expected_hash():
"""Pulls expected hash from digests file."""
+ expected_plat = "{}-{}".format(platform_normalized(), arch_normalized())
+
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()
- ):
+ if hashtype == "sha256" and plat == expected_plat:
return hashval
- raise Exception(
- "platform {} not in {}".format(platform_normalized(), DIGESTS_FILE)
- )
+ raise Exception("platform {} not in {}".format(expected_plat, 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.
- """
+ 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)
+ try:
+ conn = httplib.HTTPSConnection(CIPD_HOST)
+ except AttributeError:
+ print("=" * 70)
+ print(
+ """
+It looks like this version of Python does not support SSL. This is common
+when using Homebrew. If using Homebrew please run the following commands.
+If not using Homebrew check how your version of Python was built.
+
+brew install openssl # Probably already installed, but good to confirm.
+brew uninstall python && brew install python
+""".strip()
+ )
+ print("=" * 70)
+ raise
+
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()
+ try:
+ 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()
+ except ssl.SSLError:
+ print(
+ "\n"
+ "SSL error in Python when downloading CIPD client.\n"
+ "If using system Python try\n"
+ "\n"
+ " sudo pip install certifi\n"
+ "\n"
+ "If using Homebrew Python try\n"
+ "\n"
+ " brew install openssl\n"
+ " brew uninstall python\n"
+ " brew install python\n"
+ "\n"
+ "Otherwise, check that your machine's Python can use SSL, "
+ "testing with the httplib module on Python 2 or http.client on "
+ "Python 3.",
+ file=sys.stderr,
+ )
+ raise
# Found client bytes.
- if res.status == httplib.OK:
+ if res.status == httplib.OK: # pylint: disable=no-else-return
return content
# Redirecting to another location.
@@ -129,19 +183,21 @@
raise Exception("failed to download client")
-def bootstrap():
+def bootstrap(client, silent=False):
"""Bootstrap cipd client installation."""
- client_dir = os.path.dirname(CLIENT)
+ 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()
+ if not silent:
+ print(
+ "Bootstrapping cipd client for {}-{}".format(
+ platform_normalized(), arch_normalized()
+ )
)
- )
- tmp_path = os.path.join(SCRIPT_DIR, "tools", ".cipd")
+
+ tmp_path = client + ".tmp"
with open(tmp_path, "wb") as tmp:
tmp.write(client_bytes())
@@ -150,19 +206,19 @@
if expected != actual:
raise Exception(
- "digest of downloaded CIPD client is incorrect, check that "
- "digests file is current"
+ "digest of downloaded CIPD client is incorrect, "
+ "check that digests file is current"
)
- os.chmod(tmp_path, 0755)
- os.rename(tmp_path, CLIENT)
+ os.chmod(tmp_path, 0o755)
+ os.rename(tmp_path, client)
-def selfupdate():
+def selfupdate(client):
"""Update cipd client."""
cmd = [
- CLIENT,
+ client,
"selfupdate",
"-version-file",
VERSION_FILE,
@@ -172,24 +228,24 @@
subprocess.check_call(cmd)
-def init():
+def init(client=CLIENT, silent=False):
"""Install/update cipd client."""
os.environ["CIPD_HTTP_USER_AGENT_PREFIX"] = user_agent()
try:
- if not os.path.isfile(CLIENT):
- bootstrap()
+ if not os.path.isfile(client):
+ bootstrap(client, silent)
try:
- selfupdate()
+ selfupdate(client)
except subprocess.CalledProcessError:
print(
"CIPD selfupdate failed. Bootstrapping then retrying...",
file=sys.stderr,
)
- bootstrap()
- selfupdate()
+ bootstrap(client)
+ selfupdate(client)
except Exception:
print(
@@ -197,13 +253,15 @@
"`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,
+ user_agent=user_agent(), client=client, version_file=VERSION_FILE,
),
file=sys.stderr,
)
raise
+ return client
+
if __name__ == "__main__":
- init()
- subprocess.check_call([CLIENT] + sys.argv[1:])
+ client_exe = init()
+ subprocess.check_call([client_exe] + sys.argv[1:])