We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
docker_image_availability check fails, repo url divided into single character urls
#### Description

Looks to be a tokenizing error in the `docker_image_availability` check
```
Failure summary:


  1. Hosts:    infra.lab.variantweb.net, master.lab.variantweb.net, node1.lab.variantweb.net
     Play:     Verify Requirements
     Task:     openshift_health_check
     Message:  One or more checks failed
     Details:  check "docker_image_availability":
               One or more required Docker images are not available:
                   openshift3/ose-deployer:v3.7.0-0.127.0,
                   openshift3/ose-docker-registry:v3.7.0-0.127.0,
                   openshift3/ose-haproxy-router:v3.7.0-0.127.0,
                   openshift3/ose-pod:v3.7.0-0.127.0
               Configured registries: r (unreachable), e (unreachable), g (unreachable), i (unreachable), s (unreachable), t (unreachable), r (unreachable), y (unreachable), . (unreachable), o (unreachable), p (unreachable), s (unreachable), . (unreachable), o (unreachable), p (unreachable), e (unreachable), n (unreachable), s (unreachable), h (unreachable), i (unreachable), f (unreachable), t (unreachable), . (unreachable), c (unreachable), o (unreachable), m (unreachable), registry.access.redhat.com
               Checked by: timeout 10 skopeo inspect --tls-verify=false docker://{registry}/{image}
```

##### Version

```
$ ansible --version
ansible 2.3.2.0
  config file = /home/sjennings/projects/go/src/github.com/openshift/openshift-ansible/ansible.cfg
  configured module search path = Default w/o overrides
  python version = 2.7.13 (default, Jun 26 2017, 10:20:05) [GCC 7.1.1 20170622 (Red Hat 7.1.1-3)]

$ git describe
openshift-ansible-3.7.0-0.135.0-10-g62cb2a8d5
```

##### Steps To Reproduce
1. run playbook/byo/config.yml
2. playbook fails with above error

##### Expected Results
Installation check succeeds and each character of the `openshift_docker_additional_registries` is not treated as an individual repo url.

@sdodson 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `roles/openshift_health_checker/openshift_checks/docker_image_availability.py`
Content:
```
1 """Check that required Docker images are available."""
2 
3 from openshift_checks import OpenShiftCheck
4 from openshift_checks.mixins import DockerHostMixin
5 
6 
7 NODE_IMAGE_SUFFIXES = ["haproxy-router", "docker-registry", "deployer", "pod"]
8 DEPLOYMENT_IMAGE_INFO = {
9     "origin": {
10         "namespace": "openshift",
11         "name": "origin",
12         "registry_console_image": "cockpit/kubernetes",
13     },
14     "openshift-enterprise": {
15         "namespace": "openshift3",
16         "name": "ose",
17         "registry_console_image": "registry.access.redhat.com/openshift3/registry-console",
18     },
19 }
20 
21 
22 class DockerImageAvailability(DockerHostMixin, OpenShiftCheck):
23     """Check that required Docker images are available.
24 
25     Determine docker images that an install would require and check that they
26     are either present in the host's docker index, or available for the host to pull
27     with known registries as defined in our inventory file (or defaults).
28     """
29 
30     name = "docker_image_availability"
31     tags = ["preflight"]
32     # we use python-docker-py to check local docker for images, and skopeo
33     # to look for images available remotely without waiting to pull them.
34     dependencies = ["python-docker-py", "skopeo"]
35     skopeo_img_check_command = "timeout 10 skopeo inspect --tls-verify=false docker://{registry}/{image}"
36 
37     def __init__(self, *args, **kwargs):
38         super(DockerImageAvailability, self).__init__(*args, **kwargs)
39         # record whether we could reach a registry or not (and remember results)
40         self.reachable_registries = {}
41 
42     def is_active(self):
43         """Skip hosts with unsupported deployment types."""
44         deployment_type = self.get_var("openshift_deployment_type")
45         has_valid_deployment_type = deployment_type in DEPLOYMENT_IMAGE_INFO
46 
47         return super(DockerImageAvailability, self).is_active() and has_valid_deployment_type
48 
49     def run(self):
50         msg, failed = self.ensure_dependencies()
51         if failed:
52             return {
53                 "failed": True,
54                 "msg": "Some dependencies are required in order to check Docker image availability.\n" + msg
55             }
56 
57         required_images = self.required_images()
58         missing_images = set(required_images) - set(self.local_images(required_images))
59 
60         # exit early if all images were found locally
61         if not missing_images:
62             return {}
63 
64         registries = self.known_docker_registries()
65         if not registries:
66             return {"failed": True, "msg": "Unable to retrieve any docker registries."}
67 
68         available_images = self.available_images(missing_images, registries)
69         unavailable_images = set(missing_images) - set(available_images)
70 
71         if unavailable_images:
72             registries = [
73                 reg if self.reachable_registries.get(reg, True) else reg + " (unreachable)"
74                 for reg in registries
75             ]
76             msg = (
77                 "One or more required Docker images are not available:\n    {}\n"
78                 "Configured registries: {}\n"
79                 "Checked by: {}"
80             ).format(
81                 ",\n    ".join(sorted(unavailable_images)),
82                 ", ".join(registries),
83                 self.skopeo_img_check_command
84             )
85 
86             return dict(failed=True, msg=msg)
87 
88         return {}
89 
90     def required_images(self):
91         """
92         Determine which images we expect to need for this host.
93         Returns: a set of required images like 'openshift/origin:v3.6'
94 
95         The thorny issue of determining the image names from the variables is under consideration
96         via https://github.com/openshift/openshift-ansible/issues/4415
97 
98         For now we operate as follows:
99         * For containerized components (master, node, ...) we look at the deployment type and
100           use openshift/origin or openshift3/ose as the base for those component images. The
101           version is openshift_image_tag as determined by the openshift_version role.
102         * For OpenShift-managed infrastructure (router, registry...) we use oreg_url if
103           it is defined; otherwise we again use the base that depends on the deployment type.
104         Registry is not included in constructed images. It may be in oreg_url or etcd image.
105         """
106         required = set()
107         deployment_type = self.get_var("openshift_deployment_type")
108         host_groups = self.get_var("group_names")
109         # containerized etcd may not have openshift_image_tag, see bz 1466622
110         image_tag = self.get_var("openshift_image_tag", default="latest")
111         image_info = DEPLOYMENT_IMAGE_INFO[deployment_type]
112 
113         # template for images that run on top of OpenShift
114         image_url = "{}/{}-{}:{}".format(image_info["namespace"], image_info["name"], "${component}", "${version}")
115         image_url = self.get_var("oreg_url", default="") or image_url
116         if 'nodes' in host_groups:
117             for suffix in NODE_IMAGE_SUFFIXES:
118                 required.add(image_url.replace("${component}", suffix).replace("${version}", image_tag))
119             # The registry-console is for some reason not prefixed with ose- like the other components.
120             # Nor is it versioned the same, so just look for latest.
121             # Also a completely different name is used for Origin.
122             required.add(image_info["registry_console_image"])
123 
124         # images for containerized components
125         if self.get_var("openshift", "common", "is_containerized"):
126             components = set()
127             if 'nodes' in host_groups:
128                 components.update(["node", "openvswitch"])
129             if 'masters' in host_groups:  # name is "origin" or "ose"
130                 components.add(image_info["name"])
131             for component in components:
132                 required.add("{}/{}:{}".format(image_info["namespace"], component, image_tag))
133             if 'etcd' in host_groups:  # special case, note it is the same for origin/enterprise
134                 required.add("registry.access.redhat.com/rhel7/etcd")  # and no image tag
135 
136         return required
137 
138     def local_images(self, images):
139         """Filter a list of images and return those available locally."""
140         registries = self.known_docker_registries()
141         found_images = []
142         for image in images:
143             # docker could have the image name as-is or prefixed with any registry
144             imglist = [image] + [reg + "/" + image for reg in registries]
145             if self.is_image_local(imglist):
146                 found_images.append(image)
147         return found_images
148 
149     def is_image_local(self, image):
150         """Check if image is already in local docker index."""
151         result = self.execute_module("docker_image_facts", {"name": image})
152         return bool(result.get("images")) and not result.get("failed")
153 
154     def known_docker_registries(self):
155         """Build a list of docker registries available according to inventory vars."""
156         regs = list(self.get_var("openshift_docker_additional_registries", default=[]))
157 
158         deployment_type = self.get_var("openshift_deployment_type")
159         if deployment_type == "origin" and "docker.io" not in regs:
160             regs.append("docker.io")
161         elif deployment_type == 'openshift-enterprise' and "registry.access.redhat.com" not in regs:
162             regs.append("registry.access.redhat.com")
163 
164         return regs
165 
166     def available_images(self, images, default_registries):
167         """Search remotely for images. Returns: list of images found."""
168         return [
169             image for image in images
170             if self.is_available_skopeo_image(image, default_registries)
171         ]
172 
173     def is_available_skopeo_image(self, image, default_registries):
174         """Use Skopeo to determine if required image exists in known registry(s)."""
175         registries = default_registries
176 
177         # If image already includes a registry, only use that.
178         # NOTE: This logic would incorrectly identify images that do not use a namespace, e.g.
179         # registry.access.redhat.com/rhel7 as if the registry were a namespace.
180         # It's not clear that there's any way to distinguish them, but fortunately
181         # the current set of images all look like [registry/]namespace/name[:version].
182         if image.count("/") > 1:
183             registry, image = image.split("/", 1)
184             registries = [registry]
185 
186         for registry in registries:
187             if registry not in self.reachable_registries:
188                 self.reachable_registries[registry] = self.connect_to_registry(registry)
189             if not self.reachable_registries[registry]:
190                 continue
191 
192             args = {"_raw_params": self.skopeo_img_check_command.format(registry=registry, image=image)}
193             result = self.execute_module_with_retries("command", args)
194             if result.get("rc", 0) == 0 and not result.get("failed"):
195                 return True
196             if result.get("rc") == 124:  # RC 124 == timed out; mark unreachable
197                 self.reachable_registries[registry] = False
198 
199         return False
200 
201     def connect_to_registry(self, registry):
202         """Use ansible wait_for module to test connectivity from host to registry. Returns bool."""
203         # test a simple TCP connection
204         host, _, port = registry.partition(":")
205         port = port or 443
206         args = dict(host=host, port=port, state="started", timeout=30)
207         result = self.execute_module("wait_for", args)
208         return result.get("rc", 0) == 0 and not result.get("failed")
209
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/roles/openshift_health_checker/openshift_checks/docker_image_availability.py b/roles/openshift_health_checker/openshift_checks/docker_image_availability.py
--- a/roles/openshift_health_checker/openshift_checks/docker_image_availability.py
+++ b/roles/openshift_health_checker/openshift_checks/docker_image_availability.py
@@ -1,5 +1,6 @@
 """Check that required Docker images are available."""
 
+from ansible.module_utils import six
 from openshift_checks import OpenShiftCheck
 from openshift_checks.mixins import DockerHostMixin
 
@@ -153,7 +154,15 @@
 
     def known_docker_registries(self):
         """Build a list of docker registries available according to inventory vars."""
-        regs = list(self.get_var("openshift_docker_additional_registries", default=[]))
+        regs = self.get_var("openshift_docker_additional_registries", default=[])
+        # https://bugzilla.redhat.com/show_bug.cgi?id=1497274
+        # if the result was a string type, place it into a list. We must do this
+        # as using list() on a string will split the string into its characters.
+        if isinstance(regs, six.string_types):
+            regs = [regs]
+        else:
+            # Otherwise cast to a list as was done previously
+            regs = list(regs)
 
         deployment_type = self.get_var("openshift_deployment_type")
         if deployment_type == "origin" and "docker.io" not in regs:

{"golden_diff": "diff --git a/roles/openshift_health_checker/openshift_checks/docker_image_availability.py b/roles/openshift_health_checker/openshift_checks/docker_image_availability.py\n--- a/roles/openshift_health_checker/openshift_checks/docker_image_availability.py\n+++ b/roles/openshift_health_checker/openshift_checks/docker_image_availability.py\n@@ -1,5 +1,6 @@\n \"\"\"Check that required Docker images are available.\"\"\"\n \n+from ansible.module_utils import six\n from openshift_checks import OpenShiftCheck\n from openshift_checks.mixins import DockerHostMixin\n \n@@ -153,7 +154,15 @@\n \n     def known_docker_registries(self):\n         \"\"\"Build a list of docker registries available according to inventory vars.\"\"\"\n-        regs = list(self.get_var(\"openshift_docker_additional_registries\", default=[]))\n+        regs = self.get_var(\"openshift_docker_additional_registries\", default=[])\n+        # https://bugzilla.redhat.com/show_bug.cgi?id=1497274\n+        # if the result was a string type, place it into a list. We must do this\n+        # as using list() on a string will split the string into its characters.\n+        if isinstance(regs, six.string_types):\n+            regs = [regs]\n+        else:\n+            # Otherwise cast to a list as was done previously\n+            regs = list(regs)\n \n         deployment_type = self.get_var(\"openshift_deployment_type\")\n         if deployment_type == \"origin\" and \"docker.io\" not in regs:\n", "issue": "docker_image_availability check fails, repo url divided into single character urls\n#### Description\r\n\r\nLooks to be a tokenizing error in the `docker_image_availability` check\r\n```\r\nFailure summary:\r\n\r\n\r\n  1. Hosts:    infra.lab.variantweb.net, master.lab.variantweb.net, node1.lab.variantweb.net\r\n     Play:     Verify Requirements\r\n     Task:     openshift_health_check\r\n     Message:  One or more checks failed\r\n     Details:  check \"docker_image_availability\":\r\n               One or more required Docker images are not available:\r\n                   openshift3/ose-deployer:v3.7.0-0.127.0,\r\n                   openshift3/ose-docker-registry:v3.7.0-0.127.0,\r\n                   openshift3/ose-haproxy-router:v3.7.0-0.127.0,\r\n                   openshift3/ose-pod:v3.7.0-0.127.0\r\n               Configured registries: r (unreachable), e (unreachable), g (unreachable), i (unreachable), s (unreachable), t (unreachable), r (unreachable), y (unreachable), . (unreachable), o (unreachable), p (unreachable), s (unreachable), . (unreachable), o (unreachable), p (unreachable), e (unreachable), n (unreachable), s (unreachable), h (unreachable), i (unreachable), f (unreachable), t (unreachable), . (unreachable), c (unreachable), o (unreachable), m (unreachable), registry.access.redhat.com\r\n               Checked by: timeout 10 skopeo inspect --tls-verify=false docker://{registry}/{image}\r\n```\r\n\r\n##### Version\r\n\r\n```\r\n$ ansible --version\r\nansible 2.3.2.0\r\n  config file = /home/sjennings/projects/go/src/github.com/openshift/openshift-ansible/ansible.cfg\r\n  configured module search path = Default w/o overrides\r\n  python version = 2.7.13 (default, Jun 26 2017, 10:20:05) [GCC 7.1.1 20170622 (Red Hat 7.1.1-3)]\r\n\r\n$ git describe\r\nopenshift-ansible-3.7.0-0.135.0-10-g62cb2a8d5\r\n```\r\n\r\n##### Steps To Reproduce\r\n1. run playbook/byo/config.yml\r\n2. playbook fails with above error\r\n\r\n##### Expected Results\r\nInstallation check succeeds and each character of the `openshift_docker_additional_registries` is not treated as an individual repo url.\r\n\r\n@sdodson \n", "before_files": [{"content": "\"\"\"Check that required Docker images are available.\"\"\"\n\nfrom openshift_checks import OpenShiftCheck\nfrom openshift_checks.mixins import DockerHostMixin\n\n\nNODE_IMAGE_SUFFIXES = [\"haproxy-router\", \"docker-registry\", \"deployer\", \"pod\"]\nDEPLOYMENT_IMAGE_INFO = {\n    \"origin\": {\n        \"namespace\": \"openshift\",\n        \"name\": \"origin\",\n        \"registry_console_image\": \"cockpit/kubernetes\",\n    },\n    \"openshift-enterprise\": {\n        \"namespace\": \"openshift3\",\n        \"name\": \"ose\",\n        \"registry_console_image\": \"registry.access.redhat.com/openshift3/registry-console\",\n    },\n}\n\n\nclass DockerImageAvailability(DockerHostMixin, OpenShiftCheck):\n    \"\"\"Check that required Docker images are available.\n\n    Determine docker images that an install would require and check that they\n    are either present in the host's docker index, or available for the host to pull\n    with known registries as defined in our inventory file (or defaults).\n    \"\"\"\n\n    name = \"docker_image_availability\"\n    tags = [\"preflight\"]\n    # we use python-docker-py to check local docker for images, and skopeo\n    # to look for images available remotely without waiting to pull them.\n    dependencies = [\"python-docker-py\", \"skopeo\"]\n    skopeo_img_check_command = \"timeout 10 skopeo inspect --tls-verify=false docker://{registry}/{image}\"\n\n    def __init__(self, *args, **kwargs):\n        super(DockerImageAvailability, self).__init__(*args, **kwargs)\n        # record whether we could reach a registry or not (and remember results)\n        self.reachable_registries = {}\n\n    def is_active(self):\n        \"\"\"Skip hosts with unsupported deployment types.\"\"\"\n        deployment_type = self.get_var(\"openshift_deployment_type\")\n        has_valid_deployment_type = deployment_type in DEPLOYMENT_IMAGE_INFO\n\n        return super(DockerImageAvailability, self).is_active() and has_valid_deployment_type\n\n    def run(self):\n        msg, failed = self.ensure_dependencies()\n        if failed:\n            return {\n                \"failed\": True,\n                \"msg\": \"Some dependencies are required in order to check Docker image availability.\\n\" + msg\n            }\n\n        required_images = self.required_images()\n        missing_images = set(required_images) - set(self.local_images(required_images))\n\n        # exit early if all images were found locally\n        if not missing_images:\n            return {}\n\n        registries = self.known_docker_registries()\n        if not registries:\n            return {\"failed\": True, \"msg\": \"Unable to retrieve any docker registries.\"}\n\n        available_images = self.available_images(missing_images, registries)\n        unavailable_images = set(missing_images) - set(available_images)\n\n        if unavailable_images:\n            registries = [\n                reg if self.reachable_registries.get(reg, True) else reg + \" (unreachable)\"\n                for reg in registries\n            ]\n            msg = (\n                \"One or more required Docker images are not available:\\n    {}\\n\"\n                \"Configured registries: {}\\n\"\n                \"Checked by: {}\"\n            ).format(\n                \",\\n    \".join(sorted(unavailable_images)),\n                \", \".join(registries),\n                self.skopeo_img_check_command\n            )\n\n            return dict(failed=True, msg=msg)\n\n        return {}\n\n    def required_images(self):\n        \"\"\"\n        Determine which images we expect to need for this host.\n        Returns: a set of required images like 'openshift/origin:v3.6'\n\n        The thorny issue of determining the image names from the variables is under consideration\n        via https://github.com/openshift/openshift-ansible/issues/4415\n\n        For now we operate as follows:\n        * For containerized components (master, node, ...) we look at the deployment type and\n          use openshift/origin or openshift3/ose as the base for those component images. The\n          version is openshift_image_tag as determined by the openshift_version role.\n        * For OpenShift-managed infrastructure (router, registry...) we use oreg_url if\n          it is defined; otherwise we again use the base that depends on the deployment type.\n        Registry is not included in constructed images. It may be in oreg_url or etcd image.\n        \"\"\"\n        required = set()\n        deployment_type = self.get_var(\"openshift_deployment_type\")\n        host_groups = self.get_var(\"group_names\")\n        # containerized etcd may not have openshift_image_tag, see bz 1466622\n        image_tag = self.get_var(\"openshift_image_tag\", default=\"latest\")\n        image_info = DEPLOYMENT_IMAGE_INFO[deployment_type]\n\n        # template for images that run on top of OpenShift\n        image_url = \"{}/{}-{}:{}\".format(image_info[\"namespace\"], image_info[\"name\"], \"${component}\", \"${version}\")\n        image_url = self.get_var(\"oreg_url\", default=\"\") or image_url\n        if 'nodes' in host_groups:\n            for suffix in NODE_IMAGE_SUFFIXES:\n                required.add(image_url.replace(\"${component}\", suffix).replace(\"${version}\", image_tag))\n            # The registry-console is for some reason not prefixed with ose- like the other components.\n            # Nor is it versioned the same, so just look for latest.\n            # Also a completely different name is used for Origin.\n            required.add(image_info[\"registry_console_image\"])\n\n        # images for containerized components\n        if self.get_var(\"openshift\", \"common\", \"is_containerized\"):\n            components = set()\n            if 'nodes' in host_groups:\n                components.update([\"node\", \"openvswitch\"])\n            if 'masters' in host_groups:  # name is \"origin\" or \"ose\"\n                components.add(image_info[\"name\"])\n            for component in components:\n                required.add(\"{}/{}:{}\".format(image_info[\"namespace\"], component, image_tag))\n            if 'etcd' in host_groups:  # special case, note it is the same for origin/enterprise\n                required.add(\"registry.access.redhat.com/rhel7/etcd\")  # and no image tag\n\n        return required\n\n    def local_images(self, images):\n        \"\"\"Filter a list of images and return those available locally.\"\"\"\n        registries = self.known_docker_registries()\n        found_images = []\n        for image in images:\n            # docker could have the image name as-is or prefixed with any registry\n            imglist = [image] + [reg + \"/\" + image for reg in registries]\n            if self.is_image_local(imglist):\n                found_images.append(image)\n        return found_images\n\n    def is_image_local(self, image):\n        \"\"\"Check if image is already in local docker index.\"\"\"\n        result = self.execute_module(\"docker_image_facts\", {\"name\": image})\n        return bool(result.get(\"images\")) and not result.get(\"failed\")\n\n    def known_docker_registries(self):\n        \"\"\"Build a list of docker registries available according to inventory vars.\"\"\"\n        regs = list(self.get_var(\"openshift_docker_additional_registries\", default=[]))\n\n        deployment_type = self.get_var(\"openshift_deployment_type\")\n        if deployment_type == \"origin\" and \"docker.io\" not in regs:\n            regs.append(\"docker.io\")\n        elif deployment_type == 'openshift-enterprise' and \"registry.access.redhat.com\" not in regs:\n            regs.append(\"registry.access.redhat.com\")\n\n        return regs\n\n    def available_images(self, images, default_registries):\n        \"\"\"Search remotely for images. Returns: list of images found.\"\"\"\n        return [\n            image for image in images\n            if self.is_available_skopeo_image(image, default_registries)\n        ]\n\n    def is_available_skopeo_image(self, image, default_registries):\n        \"\"\"Use Skopeo to determine if required image exists in known registry(s).\"\"\"\n        registries = default_registries\n\n        # If image already includes a registry, only use that.\n        # NOTE: This logic would incorrectly identify images that do not use a namespace, e.g.\n        # registry.access.redhat.com/rhel7 as if the registry were a namespace.\n        # It's not clear that there's any way to distinguish them, but fortunately\n        # the current set of images all look like [registry/]namespace/name[:version].\n        if image.count(\"/\") > 1:\n            registry, image = image.split(\"/\", 1)\n            registries = [registry]\n\n        for registry in registries:\n            if registry not in self.reachable_registries:\n                self.reachable_registries[registry] = self.connect_to_registry(registry)\n            if not self.reachable_registries[registry]:\n                continue\n\n            args = {\"_raw_params\": self.skopeo_img_check_command.format(registry=registry, image=image)}\n            result = self.execute_module_with_retries(\"command\", args)\n            if result.get(\"rc\", 0) == 0 and not result.get(\"failed\"):\n                return True\n            if result.get(\"rc\") == 124:  # RC 124 == timed out; mark unreachable\n                self.reachable_registries[registry] = False\n\n        return False\n\n    def connect_to_registry(self, registry):\n        \"\"\"Use ansible wait_for module to test connectivity from host to registry. Returns bool.\"\"\"\n        # test a simple TCP connection\n        host, _, port = registry.partition(\":\")\n        port = port or 443\n        args = dict(host=host, port=port, state=\"started\", timeout=30)\n        result = self.execute_module(\"wait_for\", args)\n        return result.get(\"rc\", 0) == 0 and not result.get(\"failed\")\n", "path": "roles/openshift_health_checker/openshift_checks/docker_image_availability.py"}], "after_files": [{"content": "\"\"\"Check that required Docker images are available.\"\"\"\n\nfrom ansible.module_utils import six\nfrom openshift_checks import OpenShiftCheck\nfrom openshift_checks.mixins import DockerHostMixin\n\n\nNODE_IMAGE_SUFFIXES = [\"haproxy-router\", \"docker-registry\", \"deployer\", \"pod\"]\nDEPLOYMENT_IMAGE_INFO = {\n    \"origin\": {\n        \"namespace\": \"openshift\",\n        \"name\": \"origin\",\n        \"registry_console_image\": \"cockpit/kubernetes\",\n    },\n    \"openshift-enterprise\": {\n        \"namespace\": \"openshift3\",\n        \"name\": \"ose\",\n        \"registry_console_image\": \"registry.access.redhat.com/openshift3/registry-console\",\n    },\n}\n\n\nclass DockerImageAvailability(DockerHostMixin, OpenShiftCheck):\n    \"\"\"Check that required Docker images are available.\n\n    Determine docker images that an install would require and check that they\n    are either present in the host's docker index, or available for the host to pull\n    with known registries as defined in our inventory file (or defaults).\n    \"\"\"\n\n    name = \"docker_image_availability\"\n    tags = [\"preflight\"]\n    # we use python-docker-py to check local docker for images, and skopeo\n    # to look for images available remotely without waiting to pull them.\n    dependencies = [\"python-docker-py\", \"skopeo\"]\n    skopeo_img_check_command = \"timeout 10 skopeo inspect --tls-verify=false docker://{registry}/{image}\"\n\n    def __init__(self, *args, **kwargs):\n        super(DockerImageAvailability, self).__init__(*args, **kwargs)\n        # record whether we could reach a registry or not (and remember results)\n        self.reachable_registries = {}\n\n    def is_active(self):\n        \"\"\"Skip hosts with unsupported deployment types.\"\"\"\n        deployment_type = self.get_var(\"openshift_deployment_type\")\n        has_valid_deployment_type = deployment_type in DEPLOYMENT_IMAGE_INFO\n\n        return super(DockerImageAvailability, self).is_active() and has_valid_deployment_type\n\n    def run(self):\n        msg, failed = self.ensure_dependencies()\n        if failed:\n            return {\n                \"failed\": True,\n                \"msg\": \"Some dependencies are required in order to check Docker image availability.\\n\" + msg\n            }\n\n        required_images = self.required_images()\n        missing_images = set(required_images) - set(self.local_images(required_images))\n\n        # exit early if all images were found locally\n        if not missing_images:\n            return {}\n\n        registries = self.known_docker_registries()\n        if not registries:\n            return {\"failed\": True, \"msg\": \"Unable to retrieve any docker registries.\"}\n\n        available_images = self.available_images(missing_images, registries)\n        unavailable_images = set(missing_images) - set(available_images)\n\n        if unavailable_images:\n            registries = [\n                reg if self.reachable_registries.get(reg, True) else reg + \" (unreachable)\"\n                for reg in registries\n            ]\n            msg = (\n                \"One or more required Docker images are not available:\\n    {}\\n\"\n                \"Configured registries: {}\\n\"\n                \"Checked by: {}\"\n            ).format(\n                \",\\n    \".join(sorted(unavailable_images)),\n                \", \".join(registries),\n                self.skopeo_img_check_command\n            )\n\n            return dict(failed=True, msg=msg)\n\n        return {}\n\n    def required_images(self):\n        \"\"\"\n        Determine which images we expect to need for this host.\n        Returns: a set of required images like 'openshift/origin:v3.6'\n\n        The thorny issue of determining the image names from the variables is under consideration\n        via https://github.com/openshift/openshift-ansible/issues/4415\n\n        For now we operate as follows:\n        * For containerized components (master, node, ...) we look at the deployment type and\n          use openshift/origin or openshift3/ose as the base for those component images. The\n          version is openshift_image_tag as determined by the openshift_version role.\n        * For OpenShift-managed infrastructure (router, registry...) we use oreg_url if\n          it is defined; otherwise we again use the base that depends on the deployment type.\n        Registry is not included in constructed images. It may be in oreg_url or etcd image.\n        \"\"\"\n        required = set()\n        deployment_type = self.get_var(\"openshift_deployment_type\")\n        host_groups = self.get_var(\"group_names\")\n        # containerized etcd may not have openshift_image_tag, see bz 1466622\n        image_tag = self.get_var(\"openshift_image_tag\", default=\"latest\")\n        image_info = DEPLOYMENT_IMAGE_INFO[deployment_type]\n\n        # template for images that run on top of OpenShift\n        image_url = \"{}/{}-{}:{}\".format(image_info[\"namespace\"], image_info[\"name\"], \"${component}\", \"${version}\")\n        image_url = self.get_var(\"oreg_url\", default=\"\") or image_url\n        if 'nodes' in host_groups:\n            for suffix in NODE_IMAGE_SUFFIXES:\n                required.add(image_url.replace(\"${component}\", suffix).replace(\"${version}\", image_tag))\n            # The registry-console is for some reason not prefixed with ose- like the other components.\n            # Nor is it versioned the same, so just look for latest.\n            # Also a completely different name is used for Origin.\n            required.add(image_info[\"registry_console_image\"])\n\n        # images for containerized components\n        if self.get_var(\"openshift\", \"common\", \"is_containerized\"):\n            components = set()\n            if 'nodes' in host_groups:\n                components.update([\"node\", \"openvswitch\"])\n            if 'masters' in host_groups:  # name is \"origin\" or \"ose\"\n                components.add(image_info[\"name\"])\n            for component in components:\n                required.add(\"{}/{}:{}\".format(image_info[\"namespace\"], component, image_tag))\n            if 'etcd' in host_groups:  # special case, note it is the same for origin/enterprise\n                required.add(\"registry.access.redhat.com/rhel7/etcd\")  # and no image tag\n\n        return required\n\n    def local_images(self, images):\n        \"\"\"Filter a list of images and return those available locally.\"\"\"\n        registries = self.known_docker_registries()\n        found_images = []\n        for image in images:\n            # docker could have the image name as-is or prefixed with any registry\n            imglist = [image] + [reg + \"/\" + image for reg in registries]\n            if self.is_image_local(imglist):\n                found_images.append(image)\n        return found_images\n\n    def is_image_local(self, image):\n        \"\"\"Check if image is already in local docker index.\"\"\"\n        result = self.execute_module(\"docker_image_facts\", {\"name\": image})\n        return bool(result.get(\"images\")) and not result.get(\"failed\")\n\n    def known_docker_registries(self):\n        \"\"\"Build a list of docker registries available according to inventory vars.\"\"\"\n        regs = self.get_var(\"openshift_docker_additional_registries\", default=[])\n        # https://bugzilla.redhat.com/show_bug.cgi?id=1497274\n        # if the result was a string type, place it into a list. We must do this\n        # as using list() on a string will split the string into its characters.\n        if isinstance(regs, six.string_types):\n            regs = [regs]\n        else:\n            # Otherwise cast to a list as was done previously\n            regs = list(regs)\n\n        deployment_type = self.get_var(\"openshift_deployment_type\")\n        if deployment_type == \"origin\" and \"docker.io\" not in regs:\n            regs.append(\"docker.io\")\n        elif deployment_type == 'openshift-enterprise' and \"registry.access.redhat.com\" not in regs:\n            regs.append(\"registry.access.redhat.com\")\n\n        return regs\n\n    def available_images(self, images, default_registries):\n        \"\"\"Search remotely for images. Returns: list of images found.\"\"\"\n        return [\n            image for image in images\n            if self.is_available_skopeo_image(image, default_registries)\n        ]\n\n    def is_available_skopeo_image(self, image, default_registries):\n        \"\"\"Use Skopeo to determine if required image exists in known registry(s).\"\"\"\n        registries = default_registries\n\n        # If image already includes a registry, only use that.\n        # NOTE: This logic would incorrectly identify images that do not use a namespace, e.g.\n        # registry.access.redhat.com/rhel7 as if the registry were a namespace.\n        # It's not clear that there's any way to distinguish them, but fortunately\n        # the current set of images all look like [registry/]namespace/name[:version].\n        if image.count(\"/\") > 1:\n            registry, image = image.split(\"/\", 1)\n            registries = [registry]\n\n        for registry in registries:\n            if registry not in self.reachable_registries:\n                self.reachable_registries[registry] = self.connect_to_registry(registry)\n            if not self.reachable_registries[registry]:\n                continue\n\n            args = {\"_raw_params\": self.skopeo_img_check_command.format(registry=registry, image=image)}\n            result = self.execute_module_with_retries(\"command\", args)\n            if result.get(\"rc\", 0) == 0 and not result.get(\"failed\"):\n                return True\n            if result.get(\"rc\") == 124:  # RC 124 == timed out; mark unreachable\n                self.reachable_registries[registry] = False\n\n        return False\n\n    def connect_to_registry(self, registry):\n        \"\"\"Use ansible wait_for module to test connectivity from host to registry. Returns bool.\"\"\"\n        # test a simple TCP connection\n        host, _, port = registry.partition(\":\")\n        port = port or 443\n        args = dict(host=host, port=port, state=\"started\", timeout=30)\n        result = self.execute_module(\"wait_for\", args)\n        return result.get(\"rc\", 0) == 0 and not result.get(\"failed\")\n", "path": "roles/openshift_health_checker/openshift_checks/docker_image_availability.py"}]}