mirror of
https://github.com/certbot/certbot.git
synced 2026-06-04 06:15:36 -04:00
Certbot relies heavily on bash scripts to deploy a development environment and to execute tests. This is fine for Linux systems, including Travis, but problematic for Windows machines. This PR converts all theses scripts into Python, to make them platform independant. As a consequence, tox-win.ini is not needed anymore, and tox can be run indifferently on Windows or on Linux using a common tox.ini. AppVeyor is updated accordingly to execute tests for acme, certbot and all dns plugins. Other tests are not executed as they are for Docker, unsupported Apache/Nginx/Postfix plugins (for now) or not relevant for Windows (explicit Linux distribution tests or pylint). Another PR will be done on certbot website to update how a dev environment can be set up. * Replace several shell scripts by python equivalent. * Correction on tox coverage * Extend usage of new python scripts * Various corrections * Replace venv construction bash scripts by python equivalents * Update tox.ini * Unicode lines to compare files * Put modifications on letsencrypt-auto-source instead of generated scripts * Add executable permissions for Linux. * Merge tox win tests into main tox * Skip lock_test on Windows * Correct appveyor config * Update appveyor.yml * Explicit coverage py27 or py37 * Avoid to cover non supported certbot plugins on Windows * Update tox.ini * Remove specific warnings during CI * No cover on a debug code for tests only. * Update documentation and help script on venv/venv3.py * Customize help message for Windows * Quote correctly executable path with potential spaces in it. * Copy pipstrap from upstream
184 lines
No EOL
6.7 KiB
Python
Executable file
184 lines
No EOL
6.7 KiB
Python
Executable file
#!/usr/bin/env python
|
|
"""A small script that can act as a trust root for installing pip >=8
|
|
|
|
Embed this in your project, and your VCS checkout is all you have to trust. In
|
|
a post-peep era, this lets you claw your way to a hash-checking version of pip,
|
|
with which you can install the rest of your dependencies safely. All it assumes
|
|
is Python 2.6 or better and *some* version of pip already installed. If
|
|
anything goes wrong, it will exit with a non-zero status code.
|
|
|
|
"""
|
|
# This is here so embedded copies are MIT-compliant:
|
|
# Copyright (c) 2016 Erik Rose
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to
|
|
# deal in the Software without restriction, including without limitation the
|
|
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
# sell copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
from __future__ import print_function
|
|
from distutils.version import StrictVersion
|
|
from hashlib import sha256
|
|
from os import environ
|
|
from os.path import join
|
|
from pipes import quote
|
|
from shutil import rmtree
|
|
try:
|
|
from subprocess import check_output
|
|
except ImportError:
|
|
from subprocess import CalledProcessError, PIPE, Popen
|
|
|
|
def check_output(*popenargs, **kwargs):
|
|
if 'stdout' in kwargs:
|
|
raise ValueError('stdout argument not allowed, it will be '
|
|
'overridden.')
|
|
process = Popen(stdout=PIPE, *popenargs, **kwargs)
|
|
output, unused_err = process.communicate()
|
|
retcode = process.poll()
|
|
if retcode:
|
|
cmd = kwargs.get("args")
|
|
if cmd is None:
|
|
cmd = popenargs[0]
|
|
raise CalledProcessError(retcode, cmd)
|
|
return output
|
|
from sys import exit, version_info, executable
|
|
from tempfile import mkdtemp
|
|
try:
|
|
from urllib2 import build_opener, HTTPHandler, HTTPSHandler
|
|
except ImportError:
|
|
from urllib.request import build_opener, HTTPHandler, HTTPSHandler
|
|
try:
|
|
from urlparse import urlparse
|
|
except ImportError:
|
|
from urllib.parse import urlparse # 3.4
|
|
|
|
|
|
__version__ = 2, 0, 0
|
|
PIP_VERSION = '9.0.1'
|
|
DEFAULT_INDEX_BASE = 'https://pypi.python.org'
|
|
|
|
|
|
# wheel has a conditional dependency on argparse:
|
|
maybe_argparse = (
|
|
[('18/dd/e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/'
|
|
'argparse-1.4.0.tar.gz',
|
|
'62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4')]
|
|
if version_info < (2, 7, 0) else [])
|
|
|
|
|
|
PACKAGES = maybe_argparse + [
|
|
# Pip has no dependencies, as it vendors everything:
|
|
('11/b6/abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/'
|
|
'pip-{0}.tar.gz'.format(PIP_VERSION),
|
|
'09f243e1a7b461f654c26a725fa373211bb7ff17a9300058b205c61658ca940d'),
|
|
# This version of setuptools has only optional dependencies:
|
|
('59/88/2f3990916931a5de6fa9706d6d75eb32ee8b78627bb2abaab7ed9e6d0622/'
|
|
'setuptools-29.0.1.tar.gz',
|
|
'b539118819a4857378398891fa5366e090690e46b3e41421a1e07d6e9fd8feb0'),
|
|
('c9/1d/bd19e691fd4cfe908c76c429fe6e4436c9e83583c4414b54f6c85471954a/'
|
|
'wheel-0.29.0.tar.gz',
|
|
'1ebb8ad7e26b448e9caa4773d2357849bf80ff9e313964bcaf79cbf0201a1648')
|
|
]
|
|
|
|
|
|
class HashError(Exception):
|
|
def __str__(self):
|
|
url, path, actual, expected = self.args
|
|
return ('{url} did not match the expected hash {expected}. Instead, '
|
|
'it was {actual}. The file (left at {path}) may have been '
|
|
'tampered with.'.format(**locals()))
|
|
|
|
|
|
def hashed_download(url, temp, digest):
|
|
"""Download ``url`` to ``temp``, make sure it has the SHA-256 ``digest``,
|
|
and return its path."""
|
|
# Based on pip 1.4.1's URLOpener but with cert verification removed. Python
|
|
# >=2.7.9 verifies HTTPS certs itself, and, in any case, the cert
|
|
# authenticity has only privacy (not arbitrary code execution)
|
|
# implications, since we're checking hashes.
|
|
def opener(using_https=True):
|
|
opener = build_opener(HTTPSHandler())
|
|
if using_https:
|
|
# Strip out HTTPHandler to prevent MITM spoof:
|
|
for handler in opener.handlers:
|
|
if isinstance(handler, HTTPHandler):
|
|
opener.handlers.remove(handler)
|
|
return opener
|
|
|
|
def read_chunks(response, chunk_size):
|
|
while True:
|
|
chunk = response.read(chunk_size)
|
|
if not chunk:
|
|
break
|
|
yield chunk
|
|
|
|
parsed_url = urlparse(url)
|
|
response = opener(using_https=parsed_url.scheme == 'https').open(url)
|
|
path = join(temp, parsed_url.path.split('/')[-1])
|
|
actual_hash = sha256()
|
|
with open(path, 'wb') as file:
|
|
for chunk in read_chunks(response, 4096):
|
|
file.write(chunk)
|
|
actual_hash.update(chunk)
|
|
|
|
actual_digest = actual_hash.hexdigest()
|
|
if actual_digest != digest:
|
|
raise HashError(url, path, actual_digest, digest)
|
|
return path
|
|
|
|
|
|
def get_index_base():
|
|
"""Return the URL to the dir containing the "packages" folder.
|
|
|
|
Try to wring something out of PIP_INDEX_URL, if set. Hack "/simple" off the
|
|
end if it's there; that is likely to give us the right dir.
|
|
|
|
"""
|
|
env_var = environ.get('PIP_INDEX_URL', '').rstrip('/')
|
|
if env_var:
|
|
SIMPLE = '/simple'
|
|
if env_var.endswith(SIMPLE):
|
|
return env_var[:-len(SIMPLE)]
|
|
else:
|
|
return env_var
|
|
else:
|
|
return DEFAULT_INDEX_BASE
|
|
|
|
|
|
def main():
|
|
pip_version = StrictVersion(check_output([executable, '-m', 'pip', '--version'])
|
|
.decode('utf-8').split()[1])
|
|
min_pip_version = StrictVersion(PIP_VERSION)
|
|
if pip_version >= min_pip_version:
|
|
return 0
|
|
has_pip_cache = pip_version >= StrictVersion('6.0')
|
|
index_base = get_index_base()
|
|
temp = mkdtemp(prefix='pipstrap-')
|
|
try:
|
|
downloads = [hashed_download(index_base + '/packages/' + path,
|
|
temp,
|
|
digest)
|
|
for path, digest in PACKAGES]
|
|
check_output('{0} -m pip install --no-index --no-deps -U '.format(quote(executable)) +
|
|
# Disable cache since we're not using it and it otherwise
|
|
# sometimes throws permission warnings:
|
|
('--no-cache-dir ' if has_pip_cache else '') +
|
|
' '.join(quote(d) for d in downloads),
|
|
shell=True)
|
|
except HashError as exc:
|
|
print(exc)
|
|
except Exception:
|
|
rmtree(temp)
|
|
raise
|
|
else:
|
|
rmtree(temp)
|
|
return 0
|
|
return 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
exit(main()) |