11"""
22Script 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
519from __future__ import print_function
3044DOWNLOAD_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
3855REGISTRY = "docker.io"
4764# Assumes default docker config dir
4865DEFAULT_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
5169DEFAULT_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
6281if 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 )
6484else :
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+
6993def 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
116164def 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+
195251def 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
231299def 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+
243313def 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+
295380def 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}\n RUN sh\n " .format (image , docker_version )
402+ dockerfile = "FROM {image}:{tag}\n RUN 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
502596def test_all_docker_versions ():
0 commit comments