Skip to content
This repository was archived by the owner on Jul 31, 2025. It is now read-only.

Commit f8c0b01

Browse files
authored
Merge pull request #980 from cyli/improve_dockertest
Fix some issues with dockertest and make it work with hub
2 parents 30c07bd + e952490 commit f8c0b01

File tree

1 file changed

+142
-48
lines changed

1 file changed

+142
-48
lines changed

buildscripts/dockertest.py

Lines changed: 142 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
"""
22
Script that automates trusted pull/pushes on different docker versions.
3+
4+
Usage: python buildscripts/dockertest.py
5+
6+
- assumes that this is run from the root notary directory
7+
- assumes that bin/client already exists
8+
- assumes you are logged in with docker
9+
10+
- environment variables to provide:
11+
- DEBUG=true - produce debug output
12+
- DOCKER_CONTENT_TRUST_SERVER=<notary server url> test against a non-local
13+
notary server
14+
- NOTARY_SERVER_USERNAME=<username> login creds username to notary server
15+
- NOTARY_SERVER_PASSPHRASE=<passwd> login creds password to notary server
16+
- DOCKER_USERNAME=<username> docker hub login username
317
"""
418

519
from __future__ import print_function
@@ -30,9 +44,12 @@
3044
DOWNLOAD_DOCKERS = {
3145
"1.10": ("https://get.docker.com", "docker-1.10.3"),
3246
"1.11": ("https://get.docker.com", "docker-1.11.2"),
33-
"1.12": ("https://get.docker.com", "docker-1.12.0"),
47+
"1.12": ("https://get.docker.com", "docker-1.12.1"),
3448
}
3549

50+
NOTARY_VERSION = "0.4.1" # only version that will work with docker < 1.13
51+
NOTARY_BINARY = "bin/notary"
52+
3653
# please replace with private registry if you want to test against a private
3754
# registry
3855
REGISTRY = "docker.io"
@@ -47,25 +64,32 @@
4764
# Assumes default docker config dir
4865
DEFAULT_DOCKER_CONFIG = os.path.expanduser("~/.docker")
4966

50-
# Assumes the trust server will be run using compose if DOCKER_CONTENT_TRUST_SERVER is not specified
67+
# Assumes the trust server will be run using compose if
68+
# DOCKER_CONTENT_TRUST_SERVER is not specified
5169
DEFAULT_NOTARY_SERVER = "https://notary-server:4443"
5270

5371
# please enter a custom trust server location if you do not wish to use a local
54-
# docker-compose instantiation. If testing against Docker Hub's notary server or
55-
# another trust server, please also ensure that this script does not pick up incorrect TLS
56-
# certificates from ~/.notary/config.json by default
57-
TRUST_SERVER = os.getenv('DOCKER_CONTENT_TRUST_SERVER', DEFAULT_NOTARY_SERVER)
72+
# docker-compose instantiation. If testing against Docker Hub's notary server
73+
# or another trust server, please also ensure that this script does not pick up
74+
# incorrect TLS certificates from ~/.notary/config.json by default
75+
TRUST_SERVER = os.getenv('DOCKER_CONTENT_TRUST_SERVER', DEFAULT_NOTARY_SERVER)
76+
5877

5978
# Assumes the test will be run with `python misc/dockertest.py` from
6079
# the root of the notary repo after binaries are built
6180
# also overrides the notary server location if need be
6281
if TRUST_SERVER != DEFAULT_NOTARY_SERVER:
63-
NOTARY_CLIENT = "bin/notary -s {0}".format(TRUST_SERVER)
82+
NOTARY_CLIENT = "{client} -s {server}".format(
83+
client=NOTARY_BINARY, server=TRUST_SERVER)
6484
else:
65-
NOTARY_CLIENT = "bin/notary -c cmd/notary/config.json"
85+
NOTARY_CLIENT = "{client} -c cmd/notary/config.json".format(
86+
client=NOTARY_BINARY)
87+
88+
DEBUG = " -D" if os.getenv('DEBUG') else ""
6689

6790
# ---- setup ----
6891

92+
6993
def download_docker(download_dir="/tmp"):
7094
"""
7195
Downloads the relevant docker binaries and sets the docker values
@@ -92,9 +116,13 @@ def download_docker(download_dir="/tmp"):
92116

93117
if not os.path.isfile(tarfilename):
94118
url = urljoin(
95-
# as of 1.10 docker downloads are tar-ed due to potentially containing containerd etc.
96-
# note that for windows (which we don't currently support), it's a .zip file
97-
domain, "/".join(["builds", system, architecture, binary+".tgz"]))
119+
# as of 1.10 docker downloads are tar-ed due to potentially
120+
# containing containerd etc.
121+
# note that for windows (which we don't currently support),
122+
# it's a .zip file
123+
domain, "/".join(
124+
["builds", system, architecture, binary+".tgz"]))
125+
98126
print("Downloading", url)
99127
downloadfile.retrieve(url, tarfilename)
100128

@@ -110,20 +138,47 @@ def download_docker(download_dir="/tmp"):
110138
os.chmod(fname, 0755)
111139

112140
if not os.path.isfile(DOCKERS[version]):
113-
raise Exception("Extracted {0} to {1} but could not find {1}".format(tarfilename, extractdir, filename))
141+
raise Exception(
142+
"Extracted {tar} to {loc} but could not find {docker}".format(
143+
tar=tarfilename, loc=extractdir, docker=DOCKERS[version]))
144+
145+
146+
def verify_notary():
147+
"""
148+
Check that notary is the right version
149+
"""
150+
if not os.path.isfile(NOTARY_BINARY):
151+
raise Exception("notary client does not exist: " + NOTARY_BINARY)
152+
153+
output = subprocess.check_output([NOTARY_BINARY, "version"]).strip()
154+
lines = output.split("\n")
155+
if len(lines) != 3:
156+
print(output)
157+
raise Exception("notary version output invalid")
158+
159+
if lines[1].split()[-1] > NOTARY_VERSION:
160+
print(output)
161+
raise Exception("notary version too high: must be <= " + NOTARY_VERSION)
114162

115163

116164
def setup():
117165
"""
118166
Ensure we are set up to run the test
119167
"""
120168
download_docker()
169+
verify_notary()
170+
# ensure that we have the alpine image
171+
subprocess.call("docker pull alpine".split())
172+
121173
# copy the docker config dir over so we don't break anything in real docker
122174
# config directory
123175
os.mkdir(_TEMP_DOCKER_CONFIG_DIR)
176+
124177
# copy any docker creds over so we can push
125178
configfile = os.path.join(_TEMP_DOCKER_CONFIG_DIR, "config.json")
126-
shutil.copyfile(os.path.join(DEFAULT_DOCKER_CONFIG, "config.json"), configfile)
179+
shutil.copyfile(
180+
os.path.join(DEFAULT_DOCKER_CONFIG, "config.json"), configfile)
181+
127182
# always clean up the config file so creds aren't left in this temp directory
128183
atexit.register(os.remove, configfile)
129184
defaulttlsdir = os.path.join(DEFAULT_DOCKER_CONFIG, "tls")
@@ -192,6 +247,7 @@ def clear_tuf():
192247
if "No such file or directory" not in str(ex):
193248
raise
194249

250+
195251
def clear_keys():
196252
"""
197253
Removes the TUF keys in trust directory, since the key format changed
@@ -205,27 +261,39 @@ def clear_keys():
205261
raise
206262

207263

208-
def run_cmd(cmd, fileoutput):
264+
def run_cmd(cmd, fileoutput, input=None):
209265
"""
210266
Takes a string command, runs it, and returns the output even if it fails.
211267
"""
212268
print("$ " + cmd)
213-
fileoutput.write("$ {0}\n".format(cmd))
214-
try:
215-
output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT,
216-
env=_ENV)
217-
except subprocess.CalledProcessError as ex:
218-
print(ex.output)
219-
fileoutput.write(ex.output)
220-
raise
269+
fileoutput.write("$ {cmd}\n".format(cmd=cmd))
270+
271+
if input is not None:
272+
process = subprocess.Popen(
273+
cmd.split(), env=_ENV, stderr=subprocess.STDOUT,
274+
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
275+
276+
process.stdin.write(input)
277+
process.stdin.close()
221278
else:
222-
if output:
223-
print(output)
224-
fileoutput.write(output)
225-
return output
226-
finally:
227-
print()
228-
fileoutput.write("\n")
279+
process = subprocess.Popen(cmd.split(), env=_ENV, stderr=subprocess.STDOUT,
280+
stdout=subprocess.PIPE)
281+
output = ""
282+
while process.poll() is None:
283+
line = process.stdout.readline()
284+
print(line.strip("\n"))
285+
fileoutput.write(line)
286+
if "level=debug" not in line:
287+
output += line
288+
289+
retcode = process.poll()
290+
print()
291+
fileoutput.write("\n")
292+
293+
if retcode:
294+
raise subprocess.CalledProcessError(retcode, cmd, output=output)
295+
296+
return output
229297

230298

231299
def rmi(fout, docker_version, image, tag):
@@ -234,12 +302,14 @@ def rmi(fout, docker_version, image, tag):
234302
"""
235303
try:
236304
run_cmd(
237-
"{0} rmi {1}:{2}".format(DOCKERS[docker_version], image, tag),
305+
"{docker} rmi {image}:{tag}".format(
306+
docker=DOCKERS[docker_version], image=image, tag=tag),
238307
fout)
239308
except subprocess.CalledProcessError as ex:
240309
if "could not find image" not in str(ex):
241310
raise
242311

312+
243313
def assert_equality(actual, expected):
244314
"""
245315
Assert equality, print nice message
@@ -255,9 +325,10 @@ def pull(fout, docker_version, image, tag, expected_sha):
255325
"""
256326
clear_tuf()
257327
rmi(fout, docker_version, image, tag)
258-
output = run_cmd("{0} pull {1}:{2}".format(DOCKERS[docker_version],
259-
image, tag),
260-
fout)
328+
output = run_cmd(
329+
"{docker}{debug} pull {image}:{tag}".format(
330+
docker=DOCKERS[docker_version], image=image, tag=tag, debug=DEBUG),
331+
fout)
261332
sha = _DIGEST_REGEX.search(output).group(1)
262333
assert_equality(sha, expected_sha)
263334

@@ -271,13 +342,15 @@ def push(fout, docker_version, image, tag):
271342

272343
# tag image with the docker version
273344
run_cmd(
274-
"{0} tag alpine {1}:{2}".format(DOCKERS[docker_version], image, tag),
345+
"{docker} tag alpine {image}:{tag}".format(
346+
docker=DOCKERS[docker_version], image=image, tag=tag),
275347
fout)
276348

277349
# push!
278-
output = run_cmd("{0} push {1}:{2}".format(DOCKERS[docker_version],
279-
image, tag),
280-
fout)
350+
output = run_cmd(
351+
"{docker}{debug} push {image}:{tag}".format(
352+
docker=DOCKERS[docker_version], image=image, tag=tag, debug=DEBUG),
353+
fout)
281354
sha = _DIGEST_REGEX.search(output).group(1)
282355
size = _SIZE_REGEX.search(output).group(1)
283356

@@ -292,14 +365,28 @@ def push(fout, docker_version, image, tag):
292365
return sha, size
293366

294367

368+
def get_notary_usernamepass():
369+
"""
370+
Gets the username password for the notary server
371+
"""
372+
username = os.getenv("NOTARY_SERVER_USERNAME")
373+
passwd = os.getenv("NOTARY_SERVER_PASSPHRASE")
374+
375+
if username and passwd:
376+
return username + "\n" + passwd + "\n"
377+
return None
378+
379+
295380
def notary_list(fout, repo):
296381
"""
297382
Calls notary list on the repo and returns a list of lists of tags, shas,
298383
sizes, and roles.
299384
"""
300385
clear_tuf()
301386
output = run_cmd(
302-
"{0} -d {1} list {2}".format(NOTARY_CLIENT, _TRUST_DIR, repo), fout)
387+
"{notary}{debug} -d {trustdir} list {gun}".format(
388+
notary=NOTARY_CLIENT, trustdir=_TRUST_DIR, gun=repo, debug=DEBUG),
389+
fout, input=get_notary_usernamepass())
303390
lines = output.strip().split("\n")
304391
assert len(lines) >= 3, "not enough targets"
305392
return [line.strip().split() for line in lines[2:]]
@@ -312,13 +399,16 @@ def test_build(fout, image, docker_version):
312399
clear_tuf()
313400
# build
314401
# simple dockerfile to test building with trust
315-
dockerfile = "FROM {0}:{1}\nRUN sh\n".format(image, docker_version)
402+
dockerfile = "FROM {image}:{tag}\nRUN sh\n".format(
403+
image=image, tag=docker_version)
316404
tempdir_dockerfile = os.path.join(_TEMPDIR, "Dockerfile")
317405
with open(tempdir_dockerfile, 'wb') as ftemp:
318-
ftemp.write(dockerfile)
406+
ftemp.write(dockerfile)
319407

320408
output = run_cmd(
321-
"{0} build {1}".format(DOCKERS[docker_version], _TEMPDIR), fout)
409+
"{docker}{debug} build {context}".format(
410+
docker=DOCKERS[docker_version], context=_TEMPDIR, debug=DEBUG),
411+
fout)
322412

323413
build_result = _BUILD_REGEX.findall(output)
324414
assert len(build_result) >= 0, "build did not succeed"
@@ -335,7 +425,8 @@ def test_pull_a(fout, docker_version, image, expected_tags):
335425

336426
# pull -a
337427
output = run_cmd(
338-
"{0} pull -a {1}".format(DOCKERS[docker_version], image), fout)
428+
"{docker}{debug} pull -a {image}".format(
429+
docker=DOCKERS[docker_version], image=image, debug=DEBUG), fout)
339430
pulled_tags = _PULL_A_REGEX.findall(output)
340431

341432
assert_equality(len(pulled_tags), len(expected_tags))
@@ -395,7 +486,9 @@ def test_run(fout, image, docker_version):
395486
clear_tuf()
396487
# run
397488
output = run_cmd(
398-
"{0} run -it --rm {1}:{2} echo SUCCESS".format(DOCKERS[docker_version], image, docker_version), fout)
489+
"{docker}{debug} run -it --rm {image}:{tag} echo SUCCESS".format(
490+
docker=DOCKERS[docker_version], image=image, tag=docker_version,
491+
debug=DEBUG), fout)
399492
assert "SUCCESS" in output, "run did not succeed"
400493

401494

@@ -491,12 +584,13 @@ def rotate_to_server_snapshot(fout, image):
491584
Uses the notary client to rotate the snapshot key to be server-managed.
492585
"""
493586
run_cmd(
494-
"{0} -d {1} key rotate {2} snapshot -r".format(
495-
NOTARY_CLIENT, _TRUST_DIR, image),
496-
fout)
587+
"{notary}{debug} -d {trustdir} key rotate {gun} snapshot -r".format(
588+
notary=NOTARY_CLIENT, trustdir=_TRUST_DIR, gun=image, debug=DEBUG),
589+
fout, input=get_notary_usernamepass())
497590
run_cmd(
498-
"{0} -d {1} publish {2}".format(NOTARY_CLIENT, _TRUST_DIR, image),
499-
fout)
591+
"{notary}{debug} -d {trustdir} publish {gun}".format(
592+
notary=NOTARY_CLIENT, trustdir=_TRUST_DIR, gun=image, debug=DEBUG),
593+
fout, input=get_notary_usernamepass())
500594

501595

502596
def test_all_docker_versions():

0 commit comments

Comments
 (0)