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.
23 from shutil
import copy
25 def _text_checksum(text
):
26 """Calculate a digest string unique to the text content"""
27 return hashlib
.sha1(text
).hexdigest()
29 def _guess_docker_command():
30 """ Guess a working docker command or raise exception if not found"""
31 commands
= [["docker"], ["sudo", "-n", "docker"]]
33 if subprocess
.call(cmd
+ ["images"],
34 stdout
=subprocess
.PIPE
,
35 stderr
=subprocess
.PIPE
) == 0:
37 commands_txt
= "\n".join([" " + " ".join(x
) for x
in commands
])
38 raise Exception("Cannot find working docker command. Tried:\n%s" % \
42 """ Running Docker commands """
44 self
._command
= _guess_docker_command()
46 atexit
.register(self
._kill
_instances
)
48 def _do(self
, cmd
, quiet
=True, **kwargs
):
50 kwargs
["stdout"] = subprocess
.PIPE
51 return subprocess
.call(self
._command
+ cmd
, **kwargs
)
53 def _do_kill_instances(self
, only_known
, only_active
=True):
57 for i
in self
._output
(cmd
).split():
58 resp
= self
._output
(["inspect", i
])
59 labels
= json
.loads(resp
)[0]["Config"]["Labels"]
60 active
= json
.loads(resp
)[0]["State"]["Running"]
63 instance_uuid
= labels
.get("com.qemu.instance.uuid", None)
66 if only_known
and instance_uuid
not in self
._instances
:
68 print "Terminating", i
74 self
._do
_kill
_instances
(False, False)
77 def _kill_instances(self
):
78 return self
._do
_kill
_instances
(True)
80 def _output(self
, cmd
, **kwargs
):
81 return subprocess
.check_output(self
._command
+ cmd
,
82 stderr
=subprocess
.STDOUT
,
85 def get_image_dockerfile_checksum(self
, tag
):
86 resp
= self
._output
(["inspect", tag
])
87 labels
= json
.loads(resp
)[0]["Config"].get("Labels", {})
88 return labels
.get("com.qemu.dockerfile-checksum", "")
90 def build_image(self
, tag
, dockerfile
, df_path
, quiet
=True, argv
=None):
93 tmp_dir
= tempfile
.mkdtemp(prefix
="docker_build")
95 tmp_df
= tempfile
.NamedTemporaryFile(dir=tmp_dir
, suffix
=".docker")
96 tmp_df
.write(dockerfile
)
99 tmp_df
.write("LABEL com.qemu.dockerfile-checksum=%s" %
100 _text_checksum(dockerfile
))
102 self
._do
(["build", "-t", tag
, "-f", tmp_df
.name
] + argv
+ \
106 def image_matches_dockerfile(self
, tag
, dockerfile
):
108 checksum
= self
.get_image_dockerfile_checksum(tag
)
111 return checksum
== _text_checksum(dockerfile
)
113 def run(self
, cmd
, keep
, quiet
):
114 label
= uuid
.uuid1().hex
116 self
._instances
.append(label
)
117 ret
= self
._do
(["run", "--label",
118 "com.qemu.instance.uuid=" + label
] + cmd
,
121 self
._instances
.remove(label
)
124 class SubCommand(object):
125 """A SubCommand template base class"""
126 name
= None # Subcommand name
127 def shared_args(self
, parser
):
128 parser
.add_argument("--quiet", action
="store_true",
129 help="Run quietly unless an error occured")
131 def args(self
, parser
):
132 """Setup argument parser"""
134 def run(self
, args
, argv
):
136 args: parsed argument by argument parser.
137 argv: remaining arguments from sys.argv.
141 class RunCommand(SubCommand
):
142 """Invoke docker run and take care of cleaning up"""
144 def args(self
, parser
):
145 parser
.add_argument("--keep", action
="store_true",
146 help="Don't remove image when command completes")
147 def run(self
, args
, argv
):
148 return Docker().run(argv
, args
.keep
, quiet
=args
.quiet
)
150 class BuildCommand(SubCommand
):
151 """ Build docker image out of a dockerfile. Arguments: <tag> <dockerfile>"""
153 def args(self
, parser
):
154 parser
.add_argument("tag",
156 parser
.add_argument("dockerfile",
157 help="Dockerfile name")
159 def run(self
, args
, argv
):
160 dockerfile
= open(args
.dockerfile
, "rb").read()
164 if dkr
.image_matches_dockerfile(tag
, dockerfile
):
166 print "Image is up to date."
169 dkr
.build_image(tag
, dockerfile
, args
.dockerfile
,
170 quiet
=args
.quiet
, argv
=argv
)
173 class CleanCommand(SubCommand
):
174 """Clean up docker instances"""
176 def run(self
, args
, argv
):
181 parser
= argparse
.ArgumentParser(description
="A Docker helper",
182 usage
="%s <subcommand> ..." % os
.path
.basename(sys
.argv
[0]))
183 subparsers
= parser
.add_subparsers(title
="subcommands", help=None)
184 for cls
in SubCommand
.__subclasses
__():
186 subp
= subparsers
.add_parser(cmd
.name
, help=cmd
.__doc
__)
187 cmd
.shared_args(subp
)
189 subp
.set_defaults(cmdobj
=cmd
)
190 args
, argv
= parser
.parse_known_args()
191 return args
.cmdobj
.run(args
, argv
)
193 if __name__
== "__main__":