]>
git.proxmox.com Git - mirror_qemu.git/blob - tests/docker/docker.py
3 # Docker controlling module
5 # Copyright (c) 2016 Red Hat Inc.
8 # Fam Zheng <famz@redhat.com>
10 # This work is licensed under the terms of the GNU GPL, version 2
11 # or (at your option) any later version. See the COPYING file in
12 # the top-level directory.
24 from shutil
import copy
, rmtree
26 def _text_checksum(text
):
27 """Calculate a digest string unique to the text content"""
28 return hashlib
.sha1(text
).hexdigest()
30 def _guess_docker_command():
31 """ Guess a working docker command or raise exception if not found"""
32 commands
= [["docker"], ["sudo", "-n", "docker"]]
34 if subprocess
.call(cmd
+ ["images"],
35 stdout
=subprocess
.PIPE
,
36 stderr
=subprocess
.PIPE
) == 0:
38 commands_txt
= "\n".join([" " + " ".join(x
) for x
in commands
])
39 raise Exception("Cannot find working docker command. Tried:\n%s" % \
42 def _copy_with_mkdir(src
, root_dir
, sub_path
):
43 """Copy src into root_dir, creating sub_path as needed."""
44 dest_dir
= os
.path
.normpath("%s/%s" % (root_dir
, sub_path
))
48 # we can safely ignore already created directories
51 dest_file
= "%s/%s" % (dest_dir
, os
.path
.basename(src
))
55 def _get_so_libs(executable
):
56 """Return a list of libraries associated with an executable.
58 The paths may be symbolic links which would need to be resolved to
59 ensure theright data is copied."""
62 ldd_re
= re
.compile(r
"(/.*/)(\S*)")
64 ldd_output
= subprocess
.check_output(["ldd", executable
])
65 for line
in ldd_output
.split("\n"):
66 search
= ldd_re
.search(line
)
67 if search
and len(search
.groups()) == 2:
68 so_path
= search
.groups()[0]
69 so_lib
= search
.groups()[1]
70 libs
.append("%s/%s" % (so_path
, so_lib
))
71 except subprocess
.CalledProcessError
:
72 print "%s had no associated libraries (static build?)" % (executable
)
76 def _copy_binary_with_libs(src
, dest_dir
):
77 """Copy a binary executable and all its dependant libraries.
79 This does rely on the host file-system being fairly multi-arch
80 aware so the file don't clash with the guests layout."""
82 _copy_with_mkdir(src
, dest_dir
, "/usr/bin")
84 libs
= _get_so_libs(src
)
87 so_path
= os
.path
.dirname(l
)
88 _copy_with_mkdir(l
, dest_dir
, so_path
)
91 """ Running Docker commands """
93 self
._command
= _guess_docker_command()
95 atexit
.register(self
._kill
_instances
)
97 def _do(self
, cmd
, quiet
=True, **kwargs
):
99 kwargs
["stdout"] = subprocess
.PIPE
100 return subprocess
.call(self
._command
+ cmd
, **kwargs
)
102 def _do_kill_instances(self
, only_known
, only_active
=True):
106 for i
in self
._output
(cmd
).split():
107 resp
= self
._output
(["inspect", i
])
108 labels
= json
.loads(resp
)[0]["Config"]["Labels"]
109 active
= json
.loads(resp
)[0]["State"]["Running"]
112 instance_uuid
= labels
.get("com.qemu.instance.uuid", None)
113 if not instance_uuid
:
115 if only_known
and instance_uuid
not in self
._instances
:
117 print "Terminating", i
119 self
._do
(["kill", i
])
123 self
._do
_kill
_instances
(False, False)
126 def _kill_instances(self
):
127 return self
._do
_kill
_instances
(True)
129 def _output(self
, cmd
, **kwargs
):
130 return subprocess
.check_output(self
._command
+ cmd
,
131 stderr
=subprocess
.STDOUT
,
134 def get_image_dockerfile_checksum(self
, tag
):
135 resp
= self
._output
(["inspect", tag
])
136 labels
= json
.loads(resp
)[0]["Config"].get("Labels", {})
137 return labels
.get("com.qemu.dockerfile-checksum", "")
139 def build_image(self
, tag
, docker_dir
, dockerfile
, quiet
=True, argv
=None):
143 tmp_df
= tempfile
.NamedTemporaryFile(dir=docker_dir
, suffix
=".docker")
144 tmp_df
.write(dockerfile
)
147 tmp_df
.write("LABEL com.qemu.dockerfile-checksum=%s" %
148 _text_checksum(dockerfile
))
151 self
._do
(["build", "-t", tag
, "-f", tmp_df
.name
] + argv
+ \
155 def image_matches_dockerfile(self
, tag
, dockerfile
):
157 checksum
= self
.get_image_dockerfile_checksum(tag
)
160 return checksum
== _text_checksum(dockerfile
)
162 def run(self
, cmd
, keep
, quiet
):
163 label
= uuid
.uuid1().hex
165 self
._instances
.append(label
)
166 ret
= self
._do
(["run", "--label",
167 "com.qemu.instance.uuid=" + label
] + cmd
,
170 self
._instances
.remove(label
)
173 class SubCommand(object):
174 """A SubCommand template base class"""
175 name
= None # Subcommand name
176 def shared_args(self
, parser
):
177 parser
.add_argument("--quiet", action
="store_true",
178 help="Run quietly unless an error occured")
180 def args(self
, parser
):
181 """Setup argument parser"""
183 def run(self
, args
, argv
):
185 args: parsed argument by argument parser.
186 argv: remaining arguments from sys.argv.
190 class RunCommand(SubCommand
):
191 """Invoke docker run and take care of cleaning up"""
193 def args(self
, parser
):
194 parser
.add_argument("--keep", action
="store_true",
195 help="Don't remove image when command completes")
196 def run(self
, args
, argv
):
197 return Docker().run(argv
, args
.keep
, quiet
=args
.quiet
)
199 class BuildCommand(SubCommand
):
200 """ Build docker image out of a dockerfile. Arguments: <tag> <dockerfile>"""
202 def args(self
, parser
):
203 parser
.add_argument("--include-executable", "-e",
204 help="""Specify a binary that will be copied to the
205 container together with all its dependent
207 parser
.add_argument("tag",
209 parser
.add_argument("dockerfile",
210 help="Dockerfile name")
212 def run(self
, args
, argv
):
213 dockerfile
= open(args
.dockerfile
, "rb").read()
217 if dkr
.image_matches_dockerfile(tag
, dockerfile
):
219 print "Image is up to date."
221 # Create a docker context directory for the build
222 docker_dir
= tempfile
.mkdtemp(prefix
="docker_build")
224 # Do we include a extra binary?
225 if args
.include_executable
:
226 _copy_binary_with_libs(args
.include_executable
,
229 dkr
.build_image(tag
, docker_dir
, dockerfile
,
230 quiet
=args
.quiet
, argv
=argv
)
236 class CleanCommand(SubCommand
):
237 """Clean up docker instances"""
239 def run(self
, args
, argv
):
244 parser
= argparse
.ArgumentParser(description
="A Docker helper",
245 usage
="%s <subcommand> ..." % os
.path
.basename(sys
.argv
[0]))
246 subparsers
= parser
.add_subparsers(title
="subcommands", help=None)
247 for cls
in SubCommand
.__subclasses
__():
249 subp
= subparsers
.add_parser(cmd
.name
, help=cmd
.__doc
__)
250 cmd
.shared_args(subp
)
252 subp
.set_defaults(cmdobj
=cmd
)
253 args
, argv
= parser
.parse_known_args()
254 return args
.cmdobj
.run(args
, argv
)
256 if __name__
== "__main__":