Browse Source

remove skopeo dependency on docker-py

juanvallejo 8 years ago
parent
commit
820c100ad4

+ 70 - 78
roles/openshift_health_checker/openshift_checks/docker_image_availability.py

@@ -13,7 +13,7 @@ class DockerImageAvailability(OpenShiftCheck):
     name = "docker_image_availability"
     tags = ["preflight"]
 
-    skopeo_image = "openshift/openshift-ansible"
+    dependencies = ["skopeo", "python-docker-py"]
 
     deployment_image_info = {
         "origin": {
@@ -35,44 +35,32 @@ class DockerImageAvailability(OpenShiftCheck):
         return super(DockerImageAvailability, cls).is_active(task_vars) and has_valid_deployment_type
 
     def run(self, tmp, task_vars):
-        required_images = self.required_images(task_vars)
-        missing_images = set(required_images) - set(self.local_images(required_images, task_vars))
-
-        # exit early if all images were found locally
-        if not missing_images:
-            return {"changed": False}
-
-        msg, failed, changed = self.update_skopeo_image(task_vars)
+        msg, failed, changed = self.ensure_dependencies(task_vars)
 
         # exit early if Skopeo update fails
         if failed:
-            if "Error connecting: Error while fetching server API version" in msg:
-                msg = (
-                    "It appears Docker is not running.\n"
-                    "Please start Docker on this host before running this check.\n"
-                    "The full error reported was:\n  " + msg
-                    )
-
-            elif "Failed to import docker-py" in msg:
-                msg = (
-                    "The required Python docker-py module is not installed.\n"
-                    "Suggestion: install the python-docker-py package on this host."
-                    )
-            else:
-                msg = "The full message reported by the docker_image module was:\n" + msg
+            if "No package matching" in msg:
+                msg = "Ensure that all required dependencies can be installed via `yum`.\n"
             return {
                 "failed": True,
                 "changed": changed,
                 "msg": (
-                    "Unable to update the {img_name} image on this host;\n"
-                    "This is required in order to check Docker image availability.\n"
-                    "{msg}"
-                    ).format(img_name=self.skopeo_image, msg=msg),
+                    "Unable to update or install required dependency packages on this host;\n"
+                    "These are required in order to check Docker image availability:"
+                    "\n    {deps}\n{msg}"
+                ).format(deps=',\n    '.join(self.dependencies), msg=msg),
             }
 
+        required_images = self.required_images(task_vars)
+        missing_images = set(required_images) - set(self.local_images(required_images, task_vars))
+
+        # exit early if all images were found locally
+        if not missing_images:
+            return {"changed": changed}
+
         registries = self.known_docker_registries(task_vars)
         if not registries:
-            return {"failed": True, "msg": "Unable to retrieve any docker registries."}
+            return {"failed": True, "msg": "Unable to retrieve any docker registries.", "changed": changed}
 
         available_images = self.available_images(missing_images, registries, task_vars)
         unavailable_images = set(missing_images) - set(available_images)
@@ -81,9 +69,9 @@ class DockerImageAvailability(OpenShiftCheck):
             return {
                 "failed": True,
                 "msg": (
-                    "One or more required images are not available: {}.\n"
+                    "One or more required Docker images are not available:\n    {}\n"
                     "Configured registries: {}"
-                ).format(", ".join(sorted(unavailable_images)), ", ".join(registries)),
+                ).format(",\n    ".join(sorted(unavailable_images)), ", ".join(registries)),
                 "changed": changed,
             }
 
@@ -94,24 +82,47 @@ class DockerImageAvailability(OpenShiftCheck):
         image_info = self.deployment_image_info[deployment_type]
 
         openshift_release = get_var(task_vars, "openshift_release", default="latest")
-        openshift_image_tag = get_var(task_vars, "openshift_image_tag", default=openshift_release)
-        if openshift_image_tag and openshift_image_tag[0] != 'v':
-            openshift_image_tag = 'v' + openshift_image_tag
-
+        openshift_image_tag = get_var(task_vars, "openshift_image_tag")
         is_containerized = get_var(task_vars, "openshift", "common", "is_containerized")
-        images = set(self.non_qualified_docker_images(image_info["namespace"], image_info["name"], openshift_release,
-                                                      is_containerized))
+
+        images = set(self.required_docker_images(
+            image_info["namespace"],
+            image_info["name"],
+            ["registry-console"] if "enterprise" in deployment_type else [],  # include enterprise-only image names
+            openshift_release,
+            is_containerized,
+        ))
 
         # append images with qualified image tags to our list of required images.
         # these are images with a (v0.0.0.0) tag, rather than a standard release
         # format tag (v0.0). We want to check this set in both containerized and
         # non-containerized installations.
         images.update(
-            self.qualified_docker_images(image_info["namespace"], image_info["name"], openshift_image_tag)
+            self.required_qualified_docker_images(
+                image_info["namespace"],
+                image_info["name"],
+                openshift_image_tag,
+            ),
         )
 
         return images
 
+    @staticmethod
+    def required_docker_images(namespace, name, additional_image_names, version, is_containerized):
+        if is_containerized:
+            return ["{}/{}:{}".format(namespace, name, version)] if name else []
+
+        # include additional non-containerized images specific to the current deployment type
+        return ["{}/{}:{}".format(namespace, img_name, version) for img_name in additional_image_names]
+
+    @staticmethod
+    def required_qualified_docker_images(namespace, name, version):
+        # pylint: disable=invalid-name
+        return [
+            "{}/{}-{}:{}".format(namespace, name, suffix, version)
+            for suffix in ["haproxy-router", "docker-registry", "deployer", "pod"]
+        ]
+
     def local_images(self, images, task_vars):
         """Filter a list of images and return those available locally."""
         return [
@@ -126,28 +137,26 @@ class DockerImageAvailability(OpenShiftCheck):
 
         return bool(result.get("images", []))
 
-    def known_docker_registries(self, task_vars):
-        result = self.module_executor("docker_info", {}, task_vars)
-        if result.get("failed", False):
-            return False
+    @staticmethod
+    def known_docker_registries(task_vars):
+        docker_facts = get_var(task_vars, "openshift", "docker")
+        regs = set(docker_facts["additional_registries"])
+
+        deployment_type = get_var(task_vars, "openshift_deployment_type")
+        if deployment_type == "origin":
+            regs.update(["docker.io"])
+        elif "enterprise" in deployment_type:
+            regs.update(["registry.access.redhat.com"])
 
-        docker_info = result.get("info", {})
-        return [registry.get("Name", "") for registry in docker_info.get("Registries", {})]
+        return list(regs)
 
     def available_images(self, images, registries, task_vars):
         """Inspect existing images using Skopeo and return all images successfully inspected."""
         return [
             image for image in images
-            if self.is_image_available(image, registries, task_vars)
+            if any(self.is_available_skopeo_image(image, registry, task_vars) for registry in registries)
         ]
 
-    def is_image_available(self, image, registries, task_vars):
-        for registry in registries:
-            if self.is_available_skopeo_image(image, registry, task_vars):
-                return True
-
-        return False
-
     def is_available_skopeo_image(self, image, registry, task_vars):
         """Uses Skopeo to determine if required image exists in a given registry."""
 
@@ -156,32 +165,15 @@ class DockerImageAvailability(OpenShiftCheck):
             image=image,
         )
 
-        args = {
-            "name": "skopeo_inspect",
-            "image": self.skopeo_image,
-            "command": cmd_str,
-            "detach": False,
-            "cleanup": True,
-        }
-        result = self.module_executor("docker_container", args, task_vars)
-        return not result.get("failed", False)
+        args = {"_raw_params": cmd_str}
+        result = self.module_executor("command", args, task_vars)
+        return not result.get("failed", False) and result.get("rc", 0) == 0
 
-    @staticmethod
-    def non_qualified_docker_images(namespace, name, version, is_containerized):
-        if is_containerized:
-            return ["{}/{}:{}".format(namespace, name, version)] if name else []
-
-        return ["{}/{}:{}".format(namespace, name, version)] if name else []
-
-    @staticmethod
-    def qualified_docker_images(namespace, name, version):
-        return [
-            "{}/{}-{}:{}".format(namespace, name, suffix, version)
-            for suffix in ["haproxy-router", "docker-registry", "deployer", "pod"]
-        ]
+    # ensures that the skopeo and python-docker-py packages exist
+    # check is skipped on atomic installations
+    def ensure_dependencies(self, task_vars):
+        if get_var(task_vars, "openshift", "common", "is_atomic"):
+            return "", False, False
 
-    # ensures that the skopeo docker image exists, and updates it
-    # with latest if image was already present locally.
-    def update_skopeo_image(self, task_vars):
-        result = self.module_executor("docker_image", {"name": self.skopeo_image}, task_vars)
-        return result.get("msg", ""), result.get("failed", False), result.get("changed", False)
+        result = self.module_executor("yum", {"name": self.dependencies, "state": "latest"}, task_vars)
+        return result.get("msg", ""), result.get("failed", False) or result.get("rc", 0) != 0, result.get("changed")

+ 73 - 74
roles/openshift_health_checker/test/docker_image_availability_test.py

@@ -18,12 +18,17 @@ def test_is_active(deployment_type, is_active):
     assert DockerImageAvailability.is_active(task_vars=task_vars) == is_active
 
 
-@pytest.mark.parametrize("is_containerized", [
-    True,
-    False,
+@pytest.mark.parametrize("is_containerized,is_atomic", [
+    (True, True),
+    (False, False),
+    (True, False),
+    (False, True),
 ])
-def test_all_images_available_locally(is_containerized):
+def test_all_images_available_locally(is_containerized, is_atomic):
     def execute_module(module_name, args, task_vars):
+        if module_name == "yum":
+            return {"changed": True}
+
         assert module_name == "docker_image_facts"
         assert 'name' in args
         assert args['name']
@@ -32,10 +37,14 @@ def test_all_images_available_locally(is_containerized):
         }
 
     result = DockerImageAvailability(execute_module=execute_module).run(tmp=None, task_vars=dict(
-        openshift=dict(common=dict(
-            service_type='origin',
-            is_containerized=is_containerized,
-        )),
+        openshift=dict(
+            common=dict(
+                service_type='origin',
+                is_containerized=is_containerized,
+                is_atomic=is_atomic,
+            ),
+            docker=dict(additional_registries=["docker.io"]),
+        ),
         openshift_deployment_type='origin',
         openshift_release='v3.4',
         openshift_image_tag='3.4',
@@ -44,24 +53,28 @@ def test_all_images_available_locally(is_containerized):
     assert not result.get('failed', False)
 
 
-@pytest.mark.parametrize("module_failure", [
-    True,
+@pytest.mark.parametrize("available_locally", [
     False,
+    True,
 ])
-def test_all_images_available_remotely(module_failure):
+def test_all_images_available_remotely(available_locally):
     def execute_module(module_name, args, task_vars):
-        return {
-            'docker_image_facts': {'images': [], 'failed': module_failure},
-            'docker_info': {'info': {'Registries': [{'Name': 'docker.io'}, {'Name': 'registry.access.redhat.com'}]}},
-        }.get(module_name, {'changed': False})
+        if module_name == 'docker_image_facts':
+            return {'images': [], 'failed': available_locally}
+        return {'changed': False}
 
     result = DockerImageAvailability(execute_module=execute_module).run(tmp=None, task_vars=dict(
-        openshift=dict(common=dict(
-            service_type='origin',
-            is_containerized=False,
-        )),
+        openshift=dict(
+            common=dict(
+                service_type='origin',
+                is_containerized=False,
+                is_atomic=False,
+            ),
+            docker=dict(additional_registries=["docker.io", "registry.access.redhat.com"]),
+        ),
         openshift_deployment_type='origin',
-        openshift_release='3.4'
+        openshift_release='3.4',
+        openshift_image_tag='v3.4',
     ))
 
     assert not result.get('failed', False)
@@ -69,21 +82,7 @@ def test_all_images_available_remotely(module_failure):
 
 def test_all_images_unavailable():
     def execute_module(module_name=None, module_args=None, tmp=None, task_vars=None):
-        if module_name == "docker_info":
-            return {
-                'info': {
-                    'Registries': [
-                        {
-                            'Name': 'docker.io'
-                        },
-                        {
-                            'Name': 'registry.access.redhat.com'
-                        }
-                    ]
-                }
-            }
-
-        if module_name == "docker_container":
+        if module_name == "command":
             return {
                 'failed': True,
             }
@@ -94,16 +93,21 @@ def test_all_images_unavailable():
 
     check = DockerImageAvailability(execute_module=execute_module)
     actual = check.run(tmp=None, task_vars=dict(
-        openshift=dict(common=dict(
-            service_type='origin',
-            is_containerized=False,
-        )),
+        openshift=dict(
+            common=dict(
+                service_type='origin',
+                is_containerized=False,
+                is_atomic=False,
+            ),
+            docker=dict(additional_registries=["docker.io"]),
+        ),
         openshift_deployment_type="openshift-enterprise",
         openshift_release=None,
+        openshift_image_tag='latest'
     ))
 
     assert actual['failed']
-    assert "required images are not available" in actual['msg']
+    assert "required Docker images are not available" in actual['msg']
 
 
 @pytest.mark.parametrize("message,extra_words", [
@@ -112,34 +116,33 @@ def test_all_images_unavailable():
         ["docker image update failure"],
     ),
     (
-        "Error connecting: Error while fetching server API version",
-        ["Docker is not running"]
+        "No package matching 'skopeo' found available, installed or updated",
+        ["dependencies can be installed via `yum`"]
     ),
-    (
-        "Failed to import docker-py",
-        ["docker-py module is not installed", "install the python docker-py package"]
-    )
 ])
 def test_skopeo_update_failure(message, extra_words):
     def execute_module(module_name=None, module_args=None, tmp=None, task_vars=None):
-        if module_name == "docker_image":
+        if module_name == "yum":
             return {
                 "failed": True,
                 "msg": message,
                 "changed": False,
             }
 
-        return {
-            'changed': False,
-        }
+        return {'changed': False}
 
     actual = DockerImageAvailability(execute_module=execute_module).run(tmp=None, task_vars=dict(
-        openshift=dict(common=dict(
-            service_type='origin',
-            is_containerized=False,
-        )),
+        openshift=dict(
+            common=dict(
+                service_type='origin',
+                is_containerized=False,
+                is_atomic=False,
+            ),
+            docker=dict(additional_registries=["unknown.io"]),
+        ),
         openshift_deployment_type="openshift-enterprise",
         openshift_release='',
+        openshift_image_tag='',
     ))
 
     assert actual["failed"]
@@ -147,33 +150,29 @@ def test_skopeo_update_failure(message, extra_words):
         assert word in actual["msg"]
 
 
-@pytest.mark.parametrize("module_failure", [
-    True,
-    False,
+@pytest.mark.parametrize("deployment_type,registries", [
+    ("origin", ["unknown.io"]),
+    ("openshift-enterprise", ["registry.access.redhat.com"]),
+    ("openshift-enterprise", []),
 ])
-def test_no_registries_available(module_failure):
+def test_registry_availability(deployment_type, registries):
     def execute_module(module_name=None, module_args=None, tmp=None, task_vars=None):
-        if module_name == "docker_info":
-            return {
-                'changed': False,
-                'failed': module_failure,
-                'info': {
-                    'Registries': [],
-                }
-            }
-
         return {
             'changed': False,
         }
 
     actual = DockerImageAvailability(execute_module=execute_module).run(tmp=None, task_vars=dict(
-        openshift=dict(common=dict(
-            service_type='origin',
-            is_containerized=False,
-        )),
-        openshift_deployment_type="openshift-enterprise",
+        openshift=dict(
+            common=dict(
+                service_type='origin',
+                is_containerized=False,
+                is_atomic=False,
+            ),
+            docker=dict(additional_registries=registries),
+        ),
+        openshift_deployment_type=deployment_type,
         openshift_release='',
+        openshift_image_tag='',
     ))
 
-    assert actual['failed']
-    assert "docker registries" in actual['msg']
+    assert not actual.get("failed", False)