279 lines
9.5 KiB
Python
279 lines
9.5 KiB
Python
|
import os
|
||
|
import subprocess
|
||
|
import sys
|
||
|
import time
|
||
|
|
||
|
import unicodedata
|
||
|
|
||
|
from threading import Thread
|
||
|
|
||
|
from termcolor import colored
|
||
|
|
||
|
from client import DockerClient, PodmanClient, Command, FileCopy
|
||
|
|
||
|
|
||
|
def remove_control_characters(s):
|
||
|
return "".join(ch for ch in unicode(s, 'utf-8') if unicodedata.category(ch)[0] != "C")
|
||
|
|
||
|
|
||
|
# These tuples are the box&version and the client to use.
|
||
|
BOXES = [
|
||
|
("kleesc/centos7-podman --box-version=0.11.1.1", PodmanClient()), # podman 0.11.1.1
|
||
|
|
||
|
("kleesc/coreos --box-version=1911.4.0", DockerClient()), # docker 18.06.1
|
||
|
("kleesc/coreos --box-version=1800.7.0", DockerClient()), # docker 18.03.1
|
||
|
("kleesc/coreos --box-version=1688.5.3", DockerClient()), # docker 17.12.1
|
||
|
("kleesc/coreos --box-version=1632.3.0", DockerClient()), # docker 17.09.1
|
||
|
("kleesc/coreos --box-version=1576.5.0", DockerClient()), # docker 17.09.0
|
||
|
("kleesc/coreos --box-version=1520.9.0", DockerClient()), # docker 1.12.6
|
||
|
("kleesc/coreos --box-version=1235.6.0", DockerClient()), # docker 1.12.3
|
||
|
("kleesc/coreos --box-version=1185.5.0", DockerClient()), # docker 1.11.2
|
||
|
|
||
|
("kleesc/coreos --box-version=1122.3.0", DockerClient(requires_email=True)), # docker 1.10.3
|
||
|
("kleesc/coreos --box-version=899.17.0", DockerClient(requires_email=True)), # docker 1.9.1
|
||
|
("kleesc/coreos --box-version=835.13.0", DockerClient(requires_email=True)), # docker 1.8.3
|
||
|
("kleesc/coreos --box-version=766.5.0", DockerClient(requires_email=True)), # docker 1.7.1
|
||
|
("kleesc/coreos --box-version=717.3.0", DockerClient(requires_email=True)), # docker 1.6.2
|
||
|
("kleesc/coreos --box-version=647.2.0", DockerClient(requires_email=True)), # docker 1.5.0
|
||
|
("kleesc/coreos --box-version=557.2.0", DockerClient(requires_email=True)), # docker 1.4.1
|
||
|
("kleesc/coreos --box-version=522.6.0", DockerClient(requires_email=True)), # docker 1.3.3
|
||
|
|
||
|
("yungsang/coreos --box-version=1.3.7", DockerClient(requires_email=True)), # docker 1.3.2
|
||
|
("yungsang/coreos --box-version=1.2.9", DockerClient(requires_email=True)), # docker 1.2.0
|
||
|
("yungsang/coreos --box-version=1.1.5", DockerClient(requires_email=True)), # docker 1.1.2
|
||
|
("yungsang/coreos --box-version=1.0.0", DockerClient(requires_email=True)), # docker 1.0.1
|
||
|
("yungsang/coreos --box-version=0.9.10", DockerClient(requires_email=True)), # docker 1.0.0
|
||
|
("yungsang/coreos --box-version=0.9.6", DockerClient(requires_email=True)), # docker 0.11.1
|
||
|
|
||
|
("yungsang/coreos --box-version=0.9.1", DockerClient(requires_v1=True, requires_email=True)), # docker 0.10.0
|
||
|
("yungsang/coreos --box-version=0.3.1", DockerClient(requires_v1=True, requires_email=True)), # docker 0.9.0
|
||
|
]
|
||
|
|
||
|
|
||
|
def _check_vagrant():
|
||
|
vagrant_command = 'vagrant'
|
||
|
vagrant = any(os.access(os.path.join(path, vagrant_command), os.X_OK)
|
||
|
for path in os.environ.get('PATH').split(':'))
|
||
|
vagrant_plugins = subprocess.check_output([vagrant_command, 'plugin', 'list'])
|
||
|
return (vagrant, 'vagrant-scp' in vagrant_plugins)
|
||
|
|
||
|
|
||
|
def _load_ca(box, ca_cert):
|
||
|
if 'coreos' in box:
|
||
|
yield FileCopy(ca_cert, '/home/core/ca.pem')
|
||
|
yield Command('sudo cp /home/core/ca.pem /etc/ssl/certs/ca.pem')
|
||
|
yield Command('sudo update-ca-certificates')
|
||
|
yield Command('sudo systemctl daemon-reload')
|
||
|
elif 'centos' in box:
|
||
|
yield FileCopy(ca_cert, '/home/vagrant/ca.pem')
|
||
|
yield Command('sudo cp /home/vagrant/ca.pem /etc/pki/ca-trust/source/anchors/')
|
||
|
yield Command('sudo update-ca-trust enable')
|
||
|
yield Command('sudo update-ca-trust extract')
|
||
|
else:
|
||
|
raise Exception("unknown box for loading CA cert")
|
||
|
|
||
|
|
||
|
# extra steps to initialize the system
|
||
|
def _init_system(box):
|
||
|
if 'coreos' in box:
|
||
|
# disable the update-engine so that it's easier to debug
|
||
|
yield Command('sudo systemctl stop update-engine')
|
||
|
|
||
|
|
||
|
class CommandFailedException(Exception):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class SpinOutputter(Thread):
|
||
|
def __init__(self, initial_message):
|
||
|
super(SpinOutputter, self).__init__()
|
||
|
self.previous_line = ''
|
||
|
self.next_line = initial_message
|
||
|
self.running = True
|
||
|
self.daemon = True
|
||
|
|
||
|
@staticmethod
|
||
|
def spinning_cursor():
|
||
|
while 1:
|
||
|
for cursor in '|/-\\':
|
||
|
yield cursor
|
||
|
|
||
|
def set_next(self, text):
|
||
|
first_line = text.split('\n')[0].strip()
|
||
|
first_line = remove_control_characters(first_line)
|
||
|
self.next_line = first_line[:80]
|
||
|
|
||
|
def _clear_line(self):
|
||
|
sys.stdout.write('\r')
|
||
|
sys.stdout.write(' ' * (len(self.previous_line) + 2))
|
||
|
sys.stdout.flush()
|
||
|
|
||
|
sys.stdout.write('\r')
|
||
|
sys.stdout.flush()
|
||
|
self.previous_line = ''
|
||
|
|
||
|
def stop(self):
|
||
|
self._clear_line()
|
||
|
self.running = False
|
||
|
|
||
|
def run(self):
|
||
|
spinner = SpinOutputter.spinning_cursor()
|
||
|
while self.running:
|
||
|
self._clear_line()
|
||
|
sys.stdout.write('\r')
|
||
|
sys.stdout.flush()
|
||
|
|
||
|
sys.stdout.write(next(spinner))
|
||
|
sys.stdout.write(" ")
|
||
|
sys.stdout.write(colored(self.next_line, attrs=['dark']))
|
||
|
sys.stdout.flush()
|
||
|
|
||
|
self.previous_line = self.next_line
|
||
|
time.sleep(0.25)
|
||
|
|
||
|
|
||
|
def _run_and_wait(command, error_allowed=False):
|
||
|
# Run the command itself.
|
||
|
outputter = SpinOutputter('Running command %s' % command)
|
||
|
outputter.start()
|
||
|
|
||
|
output = ''
|
||
|
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||
|
for line in iter(process.stdout.readline, ''):
|
||
|
output += line
|
||
|
outputter.set_next(line)
|
||
|
|
||
|
result = process.wait()
|
||
|
outputter.stop()
|
||
|
|
||
|
failed = result != 0 and not error_allowed
|
||
|
# vagrant scp doesn't report auth failure as non-0 exit
|
||
|
failed = failed or (len(command) > 1 and command[1] == 'scp' and
|
||
|
'authentication failures' in output)
|
||
|
|
||
|
if failed:
|
||
|
print colored('>>> Command `%s` Failed:' % command, 'red')
|
||
|
print output
|
||
|
raise CommandFailedException()
|
||
|
|
||
|
return output
|
||
|
|
||
|
|
||
|
def _indent(text, amount):
|
||
|
return ''.join((' ' * amount) + line for line in text.splitlines(True))
|
||
|
|
||
|
|
||
|
def scp_to_vagrant(source, destination):
|
||
|
'''scp_to_vagrant copies the file from source to destination in the default
|
||
|
vagrant box without vagrant scp, which may fail on some coreos boxes.
|
||
|
'''
|
||
|
config = _run_and_wait(['vagrant', 'ssh-config'])
|
||
|
config_lines = config.split('\n')
|
||
|
params = ['scp']
|
||
|
for i in xrange(len(config_lines)):
|
||
|
if 'Host default' in config_lines[i]:
|
||
|
config_i = i + 1
|
||
|
while config_i < len(config_lines):
|
||
|
if config_lines[config_i].startswith(' '):
|
||
|
params += ['-o', '='.join(config_lines[config_i].split())]
|
||
|
else:
|
||
|
break
|
||
|
|
||
|
config_i += 1
|
||
|
break
|
||
|
|
||
|
params.append(source)
|
||
|
params.append('core@localhost:' + destination)
|
||
|
return _run_and_wait(params)
|
||
|
|
||
|
|
||
|
def _run_commands(commands):
|
||
|
last_result = None
|
||
|
for command in commands:
|
||
|
if isinstance(command, Command):
|
||
|
last_result = _run_and_wait(['vagrant', 'ssh', '-c', command.command])
|
||
|
else:
|
||
|
try:
|
||
|
last_result = _run_and_wait(['vagrant', 'scp', command.source, command.destination])
|
||
|
except CommandFailedException as e:
|
||
|
print colored('>>> Retry FileCopy command without vagrant scp...', 'red')
|
||
|
# sometimes the vagrant scp fails because of invalid ssh configuration.
|
||
|
last_result = scp_to_vagrant(command.source, command.destination)
|
||
|
|
||
|
return last_result
|
||
|
|
||
|
|
||
|
def _run_box(box, client, registry, ca_cert):
|
||
|
vagrant, vagrant_scp = _check_vagrant()
|
||
|
if not vagrant:
|
||
|
print("vagrant command not found")
|
||
|
return
|
||
|
|
||
|
if not vagrant_scp:
|
||
|
print("vagrant-scp plugin not installed")
|
||
|
return
|
||
|
|
||
|
namespace = 'devtable'
|
||
|
repo_name = 'testrepo%s' % int(time.time())
|
||
|
username = 'devtable'
|
||
|
password = 'password'
|
||
|
|
||
|
print colored('>>> Box: %s' % box, attrs=['bold'])
|
||
|
print colored('>>> Starting box', 'yellow')
|
||
|
_run_and_wait(['vagrant', 'destroy', '-f'], error_allowed=True)
|
||
|
_run_and_wait(['rm', 'Vagrantfile'], error_allowed=True)
|
||
|
_run_and_wait(['vagrant', 'init'] + box.split(' '))
|
||
|
_run_and_wait(['vagrant', 'up', '--provider', 'virtualbox'])
|
||
|
|
||
|
_run_commands(_init_system(box))
|
||
|
|
||
|
if ca_cert:
|
||
|
print colored('>>> Setting up runtime with cert ' + ca_cert, 'yellow')
|
||
|
_run_commands(_load_ca(box, ca_cert))
|
||
|
_run_commands(client.setup_client(registry, verify_tls=True))
|
||
|
else:
|
||
|
print colored('>>> Setting up runtime with insecure HTTP(S)', 'yellow')
|
||
|
_run_commands(client.setup_client(registry, verify_tls=False))
|
||
|
|
||
|
print colored('>>> Client version', 'cyan')
|
||
|
runtime_version = _run_commands(client.print_version())
|
||
|
print _indent(runtime_version, 4)
|
||
|
|
||
|
print colored('>>> Populating test image', 'yellow')
|
||
|
_run_commands(client.populate_test_image(registry, namespace, repo_name))
|
||
|
|
||
|
print colored('>>> Testing login', 'cyan')
|
||
|
_run_commands(client.login(registry, username, password))
|
||
|
|
||
|
print colored('>>> Testing push', 'cyan')
|
||
|
_run_commands(client.push(registry, namespace, repo_name))
|
||
|
|
||
|
print colored('>>> Removing all images', 'yellow')
|
||
|
_run_commands(client.pre_pull_cleanup(registry, namespace, repo_name))
|
||
|
|
||
|
print colored('>>> Testing pull', 'cyan')
|
||
|
_run_commands(client.pull(registry, namespace, repo_name))
|
||
|
|
||
|
print colored('>>> Verifying', 'cyan')
|
||
|
_run_commands(client.verify(registry, namespace, repo_name))
|
||
|
|
||
|
print colored('>>> Tearing down box', 'magenta')
|
||
|
_run_and_wait(['vagrant', 'destroy', '-f'], error_allowed=True)
|
||
|
|
||
|
print colored('>>> Successfully tested box %s' % box, 'green')
|
||
|
print ""
|
||
|
|
||
|
|
||
|
def test_clients(registry='10.0.2.2:5000', ca_cert=''):
|
||
|
print colored('>>> Running against registry ', attrs=['bold']) + colored(registry, 'cyan')
|
||
|
for box, client in BOXES:
|
||
|
try:
|
||
|
_run_box(box, client, registry, ca_cert)
|
||
|
except CommandFailedException:
|
||
|
sys.exit(-1)
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
test_clients(sys.argv[1] if len(sys.argv) > 1 else '10.0.2.2:5000', sys.argv[2]
|
||
|
if len(sys.argv) > 2 else '')
|