-
Notifications
You must be signed in to change notification settings - Fork 629
/
.travis-run-local.py
executable file
·177 lines (141 loc) · 5.45 KB
/
.travis-run-local.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#!/usr/bin/python
"""
Usage:
.travis-run-local.py [--arch=<arch>] [--image=<image> --image-tag=<tag>]
Options:
-h --help Print this help message.
--arch=<arch> Only build in containers with this CPU architecture. [default: any]
--image=<image> Only build for this image name. [default: any]
--image-tag=<tag> Only build for this image tag. [default: any]
"""
import logging
import os
import subprocess
import sys
import time
import yaml
from docopt import docopt
def read_travis_yml():
return yaml.load(open('.travis.yml', 'r'), Loader=yaml.SafeLoader)
def docker_arch(travis_arch):
if travis_arch == 'arm64':
return 'arm64v8'
return travis_arch
def image_name(image, tag, arch):
# e.g. ubuntu:latest
image = '%s:%s' % (image, tag)
if arch != 'amd64':
image_prefix = docker_arch(arch) + '/'
os.environ['IMAGE_PREFIX'] = image_prefix
return image_prefix + image
return image
def env_parse(env, arch):
kv_str = env.split()
kv = { k: v for k, v in [ s.split('=') for s in kv_str ] }
# Ugly trick to prepare for running commands for this image
try:
del os.environ['IMAGE_PREFIX']
except KeyError:
pass
os.environ.update(kv)
if 'IMAGE' not in kv or 'IMAGE_TAG' not in kv:
return None
if options['--arch'] != 'any' and arch != options['--arch']:
return None
if options['--image'] != 'any' and kv['IMAGE'] != options['--image']:
return None
if options['--image-tag'] != 'any' and kv['IMAGE_TAG'] != options['--image-tag']:
return None
return image_name(kv['IMAGE'], kv['IMAGE_TAG'], arch)
def get_images(travis):
match_found = False
for env in travis['env']['global']:
env_parse(env, travis['arch'])
for env in travis['env']['jobs']:
image = env_parse(env, travis['arch'])
if image is not None:
match_found = True
yield image
for job in travis['jobs']['include']:
image = env_parse(job['env'], job['arch'])
if image is not None:
match_found = True
yield image
# If we didn't find a match with our constraints, maybe the user wanted to
# test a specific image not listed in .travis.yml.
if not match_found:
if 'any' not in [options['--image'], options['--image-tag'], options['--arch']]:
image = env_parse(
'IMAGE=%s IMAGE_TAG=%s' % (options['--image'], options['--image-tag']),
options['--arch']
)
if image is not None:
yield image
def docker_pull(image):
subprocess.run(['docker', 'pull', image], check=True)
def pull_images(travis):
for image in get_images(travis):
docker_pull(image)
def init_logging(level=logging.INFO):
root_logger = logging.getLogger('')
root_logger.setLevel(level)
# Log to stdout
console_log_format = logging.Formatter(
'%(asctime)s %(levelname)7s: %(message)s',
'%Y-%m-%d %H:%M:%S')
console_logger = logging.StreamHandler(sys.stdout)
console_logger.setFormatter(console_log_format)
console_logger.setLevel(level)
root_logger.addHandler(console_logger)
return root_logger
def kill_and_wait():
log.info("Terminating build container")
subprocess.run('docker kill $CONTAINER_NAME', shell=True, stdout=subprocess.DEVNULL)
subprocess.run('docker container rm $CONTAINER_NAME', shell=True, stdout=subprocess.DEVNULL)
# The container removal process is asynchronous and there's no
# convenient way to wait for it, so we'll just poll and see if the
# container still exists.
log.info("Waiting for container to exit...")
attempts = 0
while True:
proc = subprocess.run('docker container inspect $CONTAINER_NAME', shell=True, stdout=subprocess.DEVNULL)
if proc.returncode != 0:
break
log.info("Container is still alive, waiting a few seconds")
attempts += 1
if attempts > 5:
raise RuntimeError
time.sleep(3)
log.info("Container has exited.")
def main():
global options
global log
log = init_logging()
options = docopt(__doc__)
# We do a shallow "git submodule update --init" in a real travis build, but
# that's not useful for local development purposes. We should do a real
# update before a container can make shallow clones.
log.info("Updating submodules")
subprocess.run(['git', 'submodule', 'update', '--init'])
log.info("Parsing Travis configuration file")
travis = read_travis_yml()
# Pull the images down first
log.info("Pulling Docker images")
docker_pull('aptman/qus')
pull_images(travis)
# Initialize system environment
log.info("Preparing system to run foreign architecture containers")
subprocess.run(['docker', 'run', '--rm', '--privileged', 'aptman/qus', '-s', '--', '-r'], check=True)
subprocess.run(['docker', 'run', '--rm', '--privileged', 'aptman/qus', '-s', '--', '-p'], check=True)
# Run native tests first
kill_and_wait()
for image in get_images(travis):
log.info("Running build and test process for image %s", image)
stages = ['before_install', 'install', 'script', 'after_script']
for stage in stages:
for command in travis.get(stage, []):
subprocess.run(command, shell=True, check=True)
kill_and_wait()
log.info("Build and test run complete!")
if __name__ == "__main__":
main()