image.py
Go to the documentation of this file.
1 import os
2 import re
3 import subprocess
4 import docker
5 import json
6 import logging
7 import rospkg
8 import sys
9 
10 logging.getLogger("docker").setLevel(logging.INFO)
11 logging.getLogger("urllib3").setLevel(logging.INFO)
12 if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
13  DEBUG = True
14 else:
15  DEBUG = False
16 
17 
19  return ['roslaunch', 'rosrun']
20 
21 
22 class DockeROSImage():
23  """
24  Remotely deploys a docker container with a user specified image of a
25  rospackage
26 
27  Example:
28  >>> import image
29  >>> obj = image.DockeROSImage(roscommand, config)
30  >>> obj.make_client()
31  >>> obj.build()
32  >>> obj.run()
33  """
34 
35  def __init__(self, roscommand, config, dockerfile=None):
36  """
37  Initilizes the commands with the rospkg configuration and roscommands allowed in this context
38  Args:
39  roscommand: any of suppoorted roscommands (rosrun/roslaunch)
40  config: configuraion for rospkg and connection to docker host
41  """
42  # how to reach the client?
43  # Version info
44  logging.debug("Python Version: " + sys.version + "\ndocker (library) Version: " + docker.__version__)
45 
46  # What is the ros command?
47  assert isinstance(roscommand, list), "roscommand should be a list"
48  assert isinstance(roscommand[0], str), "roscommand should be a list of strings"
49  self.roscommand = roscommand
50  if roscommand[0] in _get_allowed_roscommands():
51  self.roscommand = roscommand[:]
52  self.rospackage = roscommand[1]
53  else:
54  raise NotImplementedError(
55  'The ros command >' + roscommand[0] + '< is currently not supported.'
56  )
57 
58  # Where is the package?
59  rp = rospkg.RosPack()
60  logging.debug("The config is:\n"+json.dumps(config, indent=2))
61  self.dockeros_path = rp.get_path('dockeros')
62 
63  self.path = None
64  try:
65  self.path = rp.get_path(self.rospackage)
66  except rospkg.common.ResourceNotFound as e:
67  try:
68  self.check_rosdep()
69  logging.info('This is a system package to be installed from:\n> ' + self.deb_package)
70  except:
71  logging.info('Can not find package: ' + self.rospackage)
72  self.user_package = False
73 
74  if self.path: # on system
75  if self.path.startswith('/opt/ros'):
76  self.check_rosdep()
77  logging.info('This is a system package to be installed from:\n> ' + self.deb_package)
78  self.user_package = False
79  else:
80  logging.info('This is a user package at:\n> ' + self.path)
81  self.user_package = True
82 
83  # What Dockerfile should be used?
84  self.dockerfile = None
85 
86  if dockerfile: # DOCKERFILE defined by user
87  self.dockerfile = dockerfile
88  logging.info('Dockerfile given by user: ' + self.dockerfile)
89  elif self.path: # DOCKERFILE in package folder
90  for fs in os.walk(self.path):
91  for f in fs[2]:
92  fname = fs[0] + "/" + f
93  if re.match(r".*Dockerfile.*", f):
94  self.dockerfile = fname
95  logging.info('This package has a Dockerfile at:\n> ' + self.dockerfile)
96  break
97  if not self.dockerfile: #NO DOCKERFILE
98  logging.info('This package does not have a Dockerfile')
99  if self.user_package:
100  self.dockerfile = self.dockeros_path + '/config/source_Dockerfile'
101  logging.info('Using source Dockerfile:\n> ' + self.dockerfile)
102  else: # system package
103  self.dockerfile = self.dockeros_path + '/config/default_Dockerfile'
104  logging.info('Using default Dockerfile:\n> ' + self.dockerfile)
105 
106  # What is the image name going to be?
107  if "registry" in config.keys():
108  self.registry_string = config['registry'] + '/'
109  else:
110  self.registry_string = ""
111  for s in roscommand:
112  assert not "__" in s, "Double underscores lead to ambigous image names"
113  self.name = "__".join(self.roscommand).replace('.', '-').replace('~', '-').replace('=', '-')
114  self.tag = self.registry_string + self.name
115  logging.info("name: \n> " + self.name)
116  logging.info("tag: \n> " + self.tag)
117 
118  # The actual image:
119  self.image = None
120 
121  def check_rosdep(self):
122  """
123  Checking system dependencies required by ROS packages
124  """
125  out = subprocess.check_output(
126  " ".join(
127  ["rosdep", "resolve", self.rospackage]),
128  shell=True)
129  logging.debug(out)
130  self.deb_package = out.split("\n")[1].strip()
131 
132  def replaceDockerfile(self, in_fname, dockerfile_fname):
133  if os.path.exists(dockerfile_fname):
134  if os.path.samefile(in_fname, dockerfile_fname):
135  logging.warning("Using Dockerfile without replacing (%s)"%dockerfile_fname)
136  return
137 
138  with open(in_fname, 'r') as in_file:
139  with open(dockerfile_fname, 'w+') as out_file:
140  out_file.truncate()
141  for l in in_file:
142  l = l.replace("#####DEB_PACKAGE#####", self.deb_package)
143  l = l.replace("#####ROS_PACKAGE#####", self.rospackage)
144  l = l.replace("#####CMD#####", "[\""+"\", \"".join(
145  ["/ros_entrypoint.sh"] + self.roscommand
146  )+"\"]" )
147  out_file.write(l)
148  out_file.close()
149  in_file.close()
150 
151  if DEBUG:
152  with open(dockerfile_fname, 'r') as dockerfile:
153  print "Dockerfile used: ############################################"
154  for l in dockerfile:
155  print l.strip()
156  print "#############################################################"
157  dockerfile.close()
158 
159  def logIt(self, logging_fun, it):
160  for l in it:
161  logging_fun('| '+(l['stream'].strip() if ('stream' in l.keys()) else ''))
162 
163 
164  def build(self):
165  """
166  Compiles a baseDocker image with specific image of a rospackage
167  """
168  in_fname = self.dockerfile
169  dockerfile = None
170  if self.user_package:
171  self.deb_package = ""
172  path = self.path
173  logging.info(" path is " + path)
174  dockerfile_fname = self.path + "/Dockerfile"
175  logging.info(" Dockerfile is at " + dockerfile_fname)
176  else: # system package
177  assert self.deb_package, "Debian package needs to be available"
178  path = None
179  dockerfile_fname = '/tmp/tmp_Dockerfile'
180 
181  self.replaceDockerfile(in_fname, dockerfile_fname)
182 
183  logging.info("Please wait while the image is being built\n (This may take a while ...)")
184  with open(dockerfile_fname, 'r') as dockerfile:
185  it = []
186  try:
187  self.image, it = self.docker_client.images.build(
188  path=path,
189  tag=self.tag,
190  custom_context=False,
191  fileobj=(None if self.user_package else dockerfile)
192  )
193  self.logIt(logging.debug, it)
194  logging.info("Image was created. Tags are: " + ', '.join(self.image.tags))
195  except docker.errors.BuildError as e:
196  logging.error(e)
197  self.logIt(logging.error, it)
198 
199  def run(self):
200  """
201  Run the Image on Host
202  """
203  logging.info("Starting:\n > " + " ".join(self.roscommand) + " <")
204  self.docker_client.containers.run(
205  image=self.tag,
206  name=self.name,
207  network='host',
208  detach=True
209  )
210 
211  def stop(self):
212  """
213  Stop the container
214  """
215  logging.info("Stopping:\n > " + " ".join(self.roscommand) + " <")
216  try:
217  cont = self.docker_client.containers.get(self.name)
218  try:
219  cont.stop()
220  except Exception as e:
221  cont.kill()
222  logging.error(e)
223  cont.remove()
224  logging.info("Removed:\n > " + " ".join(self.roscommand) + " <")
225  except Exception as e:
226  logging.error(e)
227 
228  def push(self):
229  """
230  Push the image to a registry defined in the config
231  """
232  if not self.registry_string:
233  logging.error("Your config has no registry. Pushing makes no sense.")
234  else:
235  stream = self.docker_client.images.push(self.tag, stream=True)
236  for l in stream:
237  print(l)
238 
239 
240  def make_client(self, ip=None, port=None):
241  """
242  Initialize a docker client either using local or remote docker deamon.
243  When you give no parameters, the local enviornment is used.
244  So either your local deamon or the one defined in the DOCKER_HOST environment variable.(see https://dockr.ly/2zMPc17 for details)
245  (see https://dockr.ly/2zMPc17 for details)
246  Args:
247  ip: ip or host of the remote deamon (give None to use local environment)
248  port: port of the remote deamon
249  """
250  if not ip:
251  self.docker_client=docker.from_env()
252  else:
253  self.docker_client=docker.client("tcp:/"+":".join([ip.strip(), port.strip()]))
def replaceDockerfile(self, in_fname, dockerfile_fname)
Definition: image.py:132
def logIt(self, logging_fun, it)
Definition: image.py:159
def _get_allowed_roscommands()
Definition: image.py:18
def make_client(self, ip=None, port=None)
Definition: image.py:240
def __init__(self, roscommand, config, dockerfile=None)
Definition: image.py:35


dockeros
Author(s):
autogenerated on Mon Dec 9 2019 21:17:31