Repository: buildbot/buildbot

Issue:
DockerLatentWorker: pull specified image if it is not yet on the system
This is merely an enhancement, but it would be nice if the specified image is pulled from dockerhub if it is not on yet on the host system. 
This should be pretty easy to accomplish using docker-py, but I don't know if this a desired feature.


Discussion:
Generally +1, but I'm voting for config-option also, to disable/enable behavior. It is not always desired, because sometime LatentWorker should take identical custom docker-image, not latest one from dockerHub. And in case there is no one - host is bit miss-configured and it s an error :)
in my understanding, this is already the case. If its not, this code is pretty simple, and you should easily be able to fix it
Yeah, I looked into the code yesterday but didn't have the time to figure it out, also a spotted another bug with docker images, but that one I can fix myself

Resolving patch:
diff --git a/master/buildbot/worker/docker.py b/master/buildbot/worker/docker.py
--- a/master/buildbot/worker/docker.py
+++ b/master/buildbot/worker/docker.py
@@ -117,7 +117,7 @@ class DockerLatentWorker(DockerBaseWorker):
 
     def checkConfig(self, name, password, docker_host, image=None, command=None,
                     volumes=None, dockerfile=None, version=None, tls=None, followStartupLogs=False,
-                    masterFQDN=None, hostconfig=None, **kwargs):
+                    masterFQDN=None, hostconfig=None, autopull=False, **kwargs):
 
         DockerBaseWorker.checkConfig(self, name, password, image, masterFQDN, **kwargs)
 
@@ -145,7 +145,7 @@ def checkConfig(self, name, password, docker_host, image=None, command=None,
     @defer.inlineCallbacks
     def reconfigService(self, name, password, docker_host, image=None, command=None,
                         volumes=None, dockerfile=None, version=None, tls=None, followStartupLogs=False,
-                        masterFQDN=None, hostconfig=None, **kwargs):
+                        masterFQDN=None, hostconfig=None, autopull=False, **kwargs):
 
         yield DockerBaseWorker.reconfigService(self, name, password, image, masterFQDN, **kwargs)
         self.volumes = volumes or []
@@ -154,6 +154,7 @@ def reconfigService(self, name, password, docker_host, image=None, command=None,
         self.command = command or []
         self.dockerfile = dockerfile
         self.hostconfig = hostconfig or {}
+        self.autopull = autopull
         # Prepare the parameters for the Docker Client object.
         self.client_args = {'base_url': docker_host}
         if version is not None:
@@ -233,6 +234,11 @@ def _thd_start_instance(self, image, dockerfile, volumes):
                 for streamline in _handle_stream_line(line):
                     log.msg(streamline)
 
+        if ((not self._image_exists(docker_client, image))) and self.autopull:
+            log.msg("Image '%s' not found, pulling from registry" %
+                    image)
+            docker_client.pull(image)
+
         if (not self._image_exists(docker_client, image)):
             log.msg("Image '%s' not found" % image)
             raise LatentWorkerFailedToSubstantiate(


Test patch:
diff --git a/master/buildbot/test/fake/docker.py b/master/buildbot/test/fake/docker.py
--- a/master/buildbot/test/fake/docker.py
+++ b/master/buildbot/test/fake/docker.py
@@ -28,6 +28,7 @@ def __init__(self, base_url):
         self.call_args_create_host_config = []
         self.called_class_name = None
         self._images = [{'RepoTags': ['busybox:latest', 'worker:latest']}]
+        self._pullable = ['alpine:latest']
         self._containers = {}
 
     def images(self):
@@ -51,6 +52,10 @@ def build(self, fileobj, tag):
                 yield line
             self._images.append({'RepoTags': [tag + ':latest']})
 
+    def pull(self, image, *args, **kwargs):
+        if image in self._pullable:
+            self._images.append({'RepoTags': [image]})
+
     def containers(self, filters=None, *args, **kwargs):
         if filters is not None:
             return [
diff --git a/master/buildbot/test/unit/test_worker_docker.py b/master/buildbot/test/unit/test_worker_docker.py
--- a/master/buildbot/test/unit/test_worker_docker.py
+++ b/master/buildbot/test/unit/test_worker_docker.py
@@ -220,6 +220,12 @@ def test_start_instance_noimage_gooddockerfile(self):
         id, name = self.successResultOf(bs.start_instance(self.build))
         self.assertEqual(name, 'customworker')
 
+    def test_start_instance_noimage_pull(self):
+        bs = self.setupWorker(
+            'bot', 'pass', 'tcp://1234:2375', 'alpine:latest', autopull=True)
+        id, name = self.successResultOf(bs.start_instance(self.build))
+        self.assertEqual(name, 'alpine:latest')
+
     def test_start_instance_noimage_renderabledockerfile(self):
         bs = self.setupWorker(
             'bot', 'pass', 'tcp://1234:2375', 'customworker',