From 46779da3b5eea7b4a768820eab24bd417b746e50 Mon Sep 17 00:00:00 2001 From: Erik Rose Date: Thu, 3 Dec 2015 16:32:59 -0500 Subject: [PATCH] In Phase 2, recreate a venv and reinstall Python packages only if necessary. * Teach the build script how to do special vars. Factor up file reading. * Use a static string for the PyPI JSON location, as it will soon be overrideable via an env var for testing. --- letsencrypt_auto/build.py | 38 +++++++++--- letsencrypt_auto/letsencrypt-auto.template | 68 +++++++++++---------- letsencrypt_auto/pieces/download_upgrade.py | 9 ++- 3 files changed, 72 insertions(+), 43 deletions(-) diff --git a/letsencrypt_auto/build.py b/letsencrypt_auto/build.py index b7dd40890..d89c81470 100755 --- a/letsencrypt_auto/build.py +++ b/letsencrypt_auto/build.py @@ -2,7 +2,8 @@ """Stitch together the letsencrypt-auto script. Implement a simple templating language in which {{ some/file }} turns into the -contents of the file at ./pieces/some/file. +contents of the file at ./pieces/some/file except for certain tokens which have +other, special definitions. """ from os.path import dirname, join @@ -10,18 +11,37 @@ import re from sys import argv +def le_version(build_script_dir): + """Return the version number stamped in letsencrypt/__init__.py.""" + return re.search('''^__version__ = ['"](.+)['"].*''', + file_contents(join(dirname(build_script_dir), + 'letsencrypt', + '__init__.py')), + re.M).group(1) + + +def file_contents(path): + with open(path) as file: + return file.read() + + def main(): dir = dirname(argv[0]) - def replacer(match): - rel_path = match.group(1) - with open(join(dir, 'pieces', rel_path)) as replacement: - return replacement.read() + special_replacements = { + 'LE_AUTO_VERSION': le_version(dir) + } - with open(join(dir, 'letsencrypt-auto.template')) as template: - result = re.sub(r'{{\s*([A-Za-z0-9_./-]+)\s*}}', - replacer, - template.read()) + def replacer(match): + token = match.group(1) + if token in special_replacements: + return special_replacements[token] + else: + return file_contents(join(dir, 'pieces', token)) + + result = re.sub(r'{{\s*([A-Za-z0-9_./-]+)\s*}}', + replacer, + file_contents(join(dir, 'letsencrypt-auto.template'))) with open(join(dir, 'letsencrypt-auto'), 'w') as out: out.write(result) diff --git a/letsencrypt_auto/letsencrypt-auto.template b/letsencrypt_auto/letsencrypt-auto.template index 3c64356c0..70dafde2c 100755 --- a/letsencrypt_auto/letsencrypt-auto.template +++ b/letsencrypt_auto/letsencrypt-auto.template @@ -160,43 +160,49 @@ elif [ "$1" = "--no-self-upgrade" ]; then # Phase 2: Create venv, install LE, and run. shift 1 # the --no-self-upgrade arg - echo "Creating virtual environment..." - # TODO: Embed LE version here, and compare it against letsencrypt --version. - # If it matches, there's no need to recreate the venv. - rm -rf "$VENV_PATH" - DeterminePythonVersion - if [ "$VERBOSE" = 1 ]; then - virtualenv --no-site-packages --python $LE_PYTHON $VENV_PATH + if [ -f $VENV_BIN/letsencrypt ]; then + INSTALLED_VERSION=$($VENV_BIN/letsencrypt --version 2>&1 | cut -d " " -f 2) else - virtualenv --no-site-packages --python $LE_PYTHON $VENV_PATH > /dev/null + INSTALLED_VERSION="0.0.0" fi + if [ "{{ LE_AUTO_VERSION }}" = $INSTALLED_VERSION ]; then + echo "Reusing old virtual environment." + else + echo "Creating virtual environment..." + rm -rf "$VENV_PATH" + DeterminePythonVersion + if [ "$VERBOSE" = 1 ]; then + virtualenv --no-site-packages --python $LE_PYTHON $VENV_PATH + else + virtualenv --no-site-packages --python $LE_PYTHON $VENV_PATH > /dev/null + fi - # Install Python dependencies with peep, then run letsencrypt. - echo "Installing Python package dependencies..." - TEMP_DIR=`mktemp -d 2>/dev/null || mktemp -d -t 'le'` # Linux || OS X - # --------------------------------------------------------------------------- - cat << "UNLIKELY_EOF" > $TEMP_DIR/letsencrypt-auto-requirements.txt + # Install Python dependencies with peep, then run letsencrypt. + echo "Installing Python package dependencies..." + TEMP_DIR=`mktemp -d 2>/dev/null || mktemp -d -t 'le'` # Linux || OS X + # --------------------------------------------------------------------------- + cat << "UNLIKELY_EOF" > $TEMP_DIR/letsencrypt-auto-requirements.txt {{ letsencrypt-auto-requirements.txt }} UNLIKELY_EOF - # --------------------------------------------------------------------------- - cat << "UNLIKELY_EOF" > $TEMP_DIR/peep.py + # --------------------------------------------------------------------------- + cat << "UNLIKELY_EOF" > $TEMP_DIR/peep.py {{ peep.py }} UNLIKELY_EOF - # --------------------------------------------------------------------------- - set +e - PEEP_OUT=`$VENV_BIN/python $TEMP_DIR/peep.py install -r $TEMP_DIR/letsencrypt-auto-requirements.txt` - PEEP_STATUS=$? - set -e - rm -rf $TEMP_DIR - if [ "$PEEP_STATUS" = 0 ]; then - echo "Running letsencrypt..." - echo " " $SUDO $VENV_BIN/letsencrypt "$@" - $SUDO $VENV_BIN/letsencrypt "$@" - else - # Report error: - echo $PEEP_OUT - exit 1 + # --------------------------------------------------------------------------- + set +e + PEEP_OUT=`$VENV_BIN/python $TEMP_DIR/peep.py install -r $TEMP_DIR/letsencrypt-auto-requirements.txt` + PEEP_STATUS=$? + set -e + rm -rf $TEMP_DIR + if [ "$PEEP_STATUS" != 0 ]; then + # Report error: + echo $PEEP_OUT + exit 1 + fi fi + echo "Running letsencrypt..." + echo " " $SUDO $VENV_BIN/letsencrypt "$@" + $SUDO $VENV_BIN/letsencrypt "$@" else # Phase 1: Upgrade letsencrypt-auto if neceesary, then self-invoke. @@ -215,9 +221,7 @@ else # dependencies (curl, etc.), for better flow control, and for the option of # future Windows compatibility. # - # This Python script prints a path to a temp dir containing a new copy of - # letsencrypt-auto or returns non-zero. There is no $ interpolation due to - # quotes on heredoc delimiters. + # There is no $ interpolation due to quotes on heredoc delimiters. set +e # ------------------------------------------------------------------------- TEMP_DIR=`$LE_PYTHON - << "UNLIKELY_EOF" diff --git a/letsencrypt_auto/pieces/download_upgrade.py b/letsencrypt_auto/pieces/download_upgrade.py index a117e9e0a..ec0a11549 100644 --- a/letsencrypt_auto/pieces/download_upgrade.py +++ b/letsencrypt_auto/pieces/download_upgrade.py @@ -1,3 +1,8 @@ +"""Print a path to a temp dir containing a new copy of letsencrypt-auto. + +On failure, return non-zero. + +""" from distutils.version import LooseVersion from json import loads from os import devnull @@ -67,7 +72,7 @@ class TempDir(object): def latest_stable_version(get, package): """Apply a fairly safe heuristic to determine the latest stable release of a PyPI package.""" - metadata = loads(get('https://pypi.python.org/pypi/%s/json' % package)) + metadata = loads(get('https://pypi.python.org/pypi/letsencrypt/json')) # metadata['info']['version'] actually returns the latest of any kind of # release release, contrary to https://wiki.python.org/moin/PyPIJSON. return str(max(LooseVersion(r) for r @@ -108,7 +113,7 @@ def main(): get = HttpsGetter().get temp = TempDir() try: - stable_tag = 'v' + latest_stable_version(get, 'letsencrypt') + stable_tag = 'v' + latest_stable_version(get) print dirname(verified_new_le_auto(get, stable_tag, temp)) except ExpectedError as exc: print exc.args[0], exc.args[1]