From 82ef5bc291006b51207b9e32626251a03c776548 Mon Sep 17 00:00:00 2001
From: Rodolfo Carvalho <rhcarvalho@gmail.com>
Date: Mon, 19 Dec 2016 14:47:10 +0100
Subject: Add RPM checks as an adhoc playbook

---
 ansible.cfg.example                      |   4 +
 callback_plugins/default_plus_summary.py |  94 ++++++++++++++++++
 library/aos_version.py                   | 100 +++++++++++++++++++
 library/check_yum_update.py              | 116 ++++++++++++++++++++++
 playbooks/adhoc/preflight/README.md      |  51 ++++++++++
 playbooks/adhoc/preflight/check.yml      | 159 +++++++++++++++++++++++++++++++
 playbooks/adhoc/preflight/library        |   1 +
 7 files changed, 525 insertions(+)
 create mode 100644 callback_plugins/default_plus_summary.py
 create mode 100755 library/aos_version.py
 create mode 100755 library/check_yum_update.py
 create mode 100644 playbooks/adhoc/preflight/README.md
 create mode 100644 playbooks/adhoc/preflight/check.yml
 create mode 120000 playbooks/adhoc/preflight/library

diff --git a/ansible.cfg.example b/ansible.cfg.example
index 6a7722ad8..c8cc5db03 100644
--- a/ansible.cfg.example
+++ b/ansible.cfg.example
@@ -13,6 +13,10 @@ roles_path = roles/
 # Set the log_path
 log_path = /tmp/ansible.log
 
+# prints a descriptive summary of failed tasks
+callback_plugins = ./callback_plugins
+stdout_callback = default_plus_summary
+
 # Uncomment to use the provided BYO inventory
 #hostfile = inventory/byo/hosts
 
diff --git a/callback_plugins/default_plus_summary.py b/callback_plugins/default_plus_summary.py
new file mode 100644
index 000000000..610a3e98a
--- /dev/null
+++ b/callback_plugins/default_plus_summary.py
@@ -0,0 +1,94 @@
+# vim: expandtab:tabstop=4:shiftwidth=4
+'''
+Ansible callback plugin.
+'''
+
+from ansible.plugins.callback.default import CallbackModule as CallbackModule_default
+from ansible import constants as C
+from ansible.utils.color import stringc
+
+
+class CallbackModule(CallbackModule_default):
+    '''
+    This is like the default callback plugin, but also stores results and
+    summarizes failures.
+    '''
+
+    CALLBACK_VERSION = 2.0
+    CALLBACK_TYPE = 'stdout'
+    CALLBACK_NAME = 'default_plus_summary'
+
+    def __init__(self):
+        super(CallbackModule, self).__init__()
+        self.__failures = []
+
+    def v2_runner_on_failed(self, result, ignore_errors=False):
+        super(CallbackModule, self).v2_runner_on_failed(result, ignore_errors)
+        self.__failures.append(dict(result=result, ignore_errors=ignore_errors))
+
+    def v2_playbook_on_stats(self, stats):
+        super(CallbackModule, self).v2_playbook_on_stats(stats)
+        # TODO: update condition to consider a host var or env var to
+        # enable/disable the summary, so that we can control the output from a
+        # play.
+        if self.__failures:
+            self._print_failure_summary()
+
+    def _print_failure_summary(self):
+        '''Print a summary of failed tasks (including ignored failures).'''
+        self._display.display(u'\nFailure summary:\n')
+
+        # TODO: group failures by host or by task. If grouped by host, it is
+        # easy to see all problems of a given host. If grouped by task, it is
+        # easy to see what hosts needs the same fix.
+
+        width = len(str(len(self.__failures)))
+        initial_indent_format = u'  {{:>{width}}}. '.format(width=width)
+        initial_indent_len = len(initial_indent_format.format(0))
+        subsequent_indent = u' ' * initial_indent_len
+        subsequent_extra_indent = u' ' * (initial_indent_len + 10)
+
+        for i, failure in enumerate(self.__failures, 1):
+            lines = _format_failure(failure)
+            self._display.display(u'\n{}{}'.format(initial_indent_format.format(i), lines[0]))
+            for line in lines[1:]:
+                line = line.replace(u'\n', u'\n' + subsequent_extra_indent)
+                indented = u'{}{}'.format(subsequent_indent, line)
+                self._display.display(indented)
+
+
+# Reason: disable pylint protected-access because we need to access _*
+#         attributes of a task result to implement this method.
+# Status: permanently disabled unless Ansible's API changes.
+# pylint: disable=protected-access
+def _format_failure(failure):
+    '''Return a list of pretty-formatted lines describing a failure, including
+    relevant information about it. Line separators are not included.'''
+    result = failure['result']
+    host = result._host.get_name()
+    play = _get_play(result._task)
+    if play:
+        play = play.get_name()
+    task = result._task.get_name()
+    msg = result._result.get('msg', u'???')
+    rows = (
+        (u'Host', host),
+        (u'Play', play),
+        (u'Task', task),
+        (u'Message', stringc(msg, C.COLOR_ERROR)),
+    )
+    row_format = '{:10}{}'
+    return [row_format.format(header + u':', body) for header, body in rows]
+
+
+# Reason: disable pylint protected-access because we need to access _*
+#         attributes of obj to implement this function.
+#         This is inspired by ansible.playbook.base.Base.dump_me.
+# Status: permanently disabled unless Ansible's API changes.
+# pylint: disable=protected-access
+def _get_play(obj):
+    '''Given a task or block, recursively tries to find its parent play.'''
+    if hasattr(obj, '_play'):
+        return obj._play
+    if getattr(obj, '_parent'):
+        return _get_play(obj._parent)
diff --git a/library/aos_version.py b/library/aos_version.py
new file mode 100755
index 000000000..f7fcb6da5
--- /dev/null
+++ b/library/aos_version.py
@@ -0,0 +1,100 @@
+#!/usr/bin/python
+# vim: expandtab:tabstop=4:shiftwidth=4
+'''
+An ansible module for determining if more than one minor version
+of any atomic-openshift package is available, which would indicate
+that multiple repos are enabled for different versions of the same
+thing which may cause problems.
+
+Also, determine if the version requested is available down to the
+precision requested.
+'''
+
+# import os
+# import sys
+import yum  # pylint: disable=import-error
+from ansible.module_utils.basic import AnsibleModule
+
+
+def main():  # pylint: disable=missing-docstring
+    module = AnsibleModule(
+        argument_spec=dict(
+            version=dict(required=True)
+        ),
+        supports_check_mode=True
+    )
+
+    # NOTE(rhcarvalho): sosiouxme added _unmute, but I couldn't find a case yet
+    # for when it is actually necessary. Leaving it commented out for now,
+    # though this comment and the commented out code related to _unmute should
+    # be deleted later if not proven necessary.
+
+    # sys.stdout = os.devnull  # mute yum so it doesn't break our output
+    # sys.stderr = os.devnull  # mute yum so it doesn't break our output
+
+    # def _unmute():  # pylint: disable=missing-docstring
+    #     sys.stdout = sys.__stdout__
+
+    def bail(error):  # pylint: disable=missing-docstring
+        # _unmute()
+        module.fail_json(msg=error)
+
+    yb = yum.YumBase()  # pylint: disable=invalid-name
+
+    # search for package versions available for aos pkgs
+    expected_pkgs = [
+        'atomic-openshift',
+        'atomic-openshift-master',
+        'atomic-openshift-node',
+    ]
+    try:
+        pkgs = yb.pkgSack.returnPackages(patterns=expected_pkgs)
+    except yum.Errors.PackageSackError as e:  # pylint: disable=invalid-name
+        # you only hit this if *none* of the packages are available
+        bail('Unable to find any atomic-openshift packages. \nCheck your subscription and repo settings. \n%s' % e)
+
+    # determine what level of precision we're expecting for the version
+    expected_version = module.params['version']
+    if expected_version.startswith('v'):  # v3.3 => 3.3
+        expected_version = expected_version[1:]
+    num_dots = expected_version.count('.')
+
+    pkgs_by_name_version = {}
+    pkgs_precise_version_found = {}
+    for pkg in pkgs:
+        # get expected version precision
+        match_version = '.'.join(pkg.version.split('.')[:num_dots + 1])
+        if match_version == expected_version:
+            pkgs_precise_version_found[pkg.name] = True
+        # get x.y version precision
+        minor_version = '.'.join(pkg.version.split('.')[:2])
+        if pkg.name not in pkgs_by_name_version:
+            pkgs_by_name_version[pkg.name] = {}
+        pkgs_by_name_version[pkg.name][minor_version] = True
+
+    # see if any packages couldn't be found at requested version
+    # see if any packages are available in more than one minor version
+    not_found = []
+    multi_found = []
+    for name in expected_pkgs:
+        if name not in pkgs_precise_version_found:
+            not_found.append(name)
+        if name in pkgs_by_name_version and len(pkgs_by_name_version[name]) > 1:
+            multi_found.append(name)
+    if not_found:
+        msg = 'Not all of the required packages are available at requested version %s:\n' % expected_version
+        for name in not_found:
+            msg += '  %s\n' % name
+        bail(msg + 'Please check your subscriptions and enabled repositories.')
+    if multi_found:
+        msg = 'Multiple minor versions of these packages are available\n'
+        for name in multi_found:
+            msg += '  %s\n' % name
+        bail(msg + "There should only be one OpenShift version's repository enabled at a time.")
+
+    # _unmute()
+    module.exit_json(changed=False)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/library/check_yum_update.py b/library/check_yum_update.py
new file mode 100755
index 000000000..0c1974324
--- /dev/null
+++ b/library/check_yum_update.py
@@ -0,0 +1,116 @@
+#!/usr/bin/python
+# vim: expandtab:tabstop=4:shiftwidth=4
+'''
+Ansible module to test whether a yum update or install will succeed,
+without actually performing it or running yum.
+parameters:
+  packages: (optional) A list of package names to install or update.
+            If omitted, all installed RPMs are considered for updates.
+'''
+
+# import os
+import sys
+import yum  # pylint: disable=import-error
+from ansible.module_utils.basic import AnsibleModule
+
+
+def main():  # pylint: disable=missing-docstring,too-many-branches
+    module = AnsibleModule(
+        argument_spec=dict(
+            packages=dict(type='list', default=[])
+        ),
+        supports_check_mode=True
+    )
+
+    # NOTE(rhcarvalho): sosiouxme added _unmute, but I couldn't find a case yet
+    # for when it is actually necessary. Leaving it commented out for now,
+    # though this comment and the commented out code related to _unmute should
+    # be deleted later if not proven necessary.
+
+    # sys.stdout = os.devnull  # mute yum so it doesn't break our output
+
+    # def _unmute():  # pylint: disable=missing-docstring
+    #     sys.stdout = sys.__stdout__
+
+    def bail(error):  # pylint: disable=missing-docstring
+        # _unmute()
+        module.fail_json(msg=error)
+
+    yb = yum.YumBase()  # pylint: disable=invalid-name
+    # determine if the existing yum configuration is valid
+    try:
+        yb.repos.populateSack(mdtype='metadata', cacheonly=1)
+    # for error of type:
+    #   1. can't reach the repo URL(s)
+    except yum.Errors.NoMoreMirrorsRepoError as e:  # pylint: disable=invalid-name
+        bail('Error getting data from at least one yum repository: %s' % e)
+    #   2. invalid repo definition
+    except yum.Errors.RepoError as e:  # pylint: disable=invalid-name
+        bail('Error with yum repository configuration: %s' % e)
+    #   3. other/unknown
+    #    * just report the problem verbatim
+    except:  # pylint: disable=bare-except
+        bail('Unexpected error with yum repository: %s' % sys.exc_info()[1])
+
+    packages = module.params['packages']
+    no_such_pkg = []
+    for pkg in packages:
+        try:
+            yb.install(name=pkg)
+        except yum.Errors.InstallError as e:  # pylint: disable=invalid-name
+            no_such_pkg.append(pkg)
+        except:  # pylint: disable=bare-except
+            bail('Unexpected error with yum install/update: %s' %
+                 sys.exc_info()[1])
+    if not packages:
+        # no packages requested means test a yum update of everything
+        yb.update()
+    elif no_such_pkg:
+        # wanted specific packages to install but some aren't available
+        user_msg = 'Cannot install all of the necessary packages. Unavailable:\n'
+        for pkg in no_such_pkg:
+            user_msg += '  %s\n' % pkg
+        user_msg += 'You may need to enable one or more yum repositories to make this content available.'
+        bail(user_msg)
+
+    try:
+        txn_result, txn_msgs = yb.buildTransaction()
+    except:  # pylint: disable=bare-except
+        bail('Unexpected error during dependency resolution for yum update: \n %s' %
+             sys.exc_info()[1])
+
+    # find out if there are any errors with the update/install
+    if txn_result == 0:  # 'normal exit' meaning there's nothing to install/update
+        pass
+    elif txn_result == 1:  # error with transaction
+        user_msg = 'Could not perform a yum update.\n'
+        if len(txn_msgs) > 0:
+            user_msg += 'Errors from dependency resolution:\n'
+            for msg in txn_msgs:
+                user_msg += '  %s\n' % msg
+            user_msg += 'You should resolve these issues before proceeding with an install.\n'
+            user_msg += 'You may need to remove or downgrade packages or enable/disable yum repositories.'
+        bail(user_msg)
+    # TODO: it would be nice depending on the problem:
+    #   1. dependency for update not found
+    #    * construct the dependency tree
+    #    * find the installed package(s) that required the missing dep
+    #    * determine if any of these packages matter to openshift
+    #    * build helpful error output
+    #   2. conflicts among packages in available content
+    #    * analyze dependency tree and build helpful error output
+    #   3. other/unknown
+    #    * report the problem verbatim
+    #    * add to this list as we come across problems we can clearly diagnose
+    elif txn_result == 2:  # everything resolved fine
+        pass
+    else:
+        bail('Unknown error(s) from dependency resolution. Exit Code: %d:\n%s' %
+             (txn_result, txn_msgs))
+
+    # _unmute()
+    module.exit_json(changed=False)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/playbooks/adhoc/preflight/README.md b/playbooks/adhoc/preflight/README.md
new file mode 100644
index 000000000..871c6af01
--- /dev/null
+++ b/playbooks/adhoc/preflight/README.md
@@ -0,0 +1,51 @@
+# Preflight checks
+
+Here we provide an Ansible playbook for detecting potential roadblocks prior to
+an install or upgrade.
+
+Ansible's default operation mode is to fail fast, on the first error. However,
+when performing checks, it is useful to gather as much information about
+problems as possible in a single run.
+
+The `check.yml` playbook runs a battery of checks against the inventory hosts
+and tells Ansible to ignore intermediate errors, thus giving a more complete
+diagnostic of the state of each host. Still, if any check failed, the playbook
+run will be marked as having failed.
+
+To facilitate understanding the problems that were encountered, we provide a
+custom callback plugin to summarize execution errors at the end of a playbook
+run.
+
+---
+
+*Note that currently the `check.yml` playbook is only useful for RPM-based
+installations. Containerized installs are excluded from checks for now, but
+might be included in the future if there is demand for that.*
+
+---
+
+## Running
+
+With an installation of Ansible 2.2 or greater, run the playbook directly
+against your inventory file. Here is the step-by-step:
+
+1. If you haven't done it yet, clone this repository:
+
+    ```console
+    $ git clone https://github.com/openshift/openshift-ansible
+    $ cd openshift-ansible
+    ```
+
+2. Configure a custom callback plugin to get a summary of problems at the end of
+the playbook run:
+
+    ```console
+    $ export ANSIBLE_CALLBACK_PLUGINS=callback_plugins \
+             ANSIBLE_STDOUT_CALLBACK=default_plus_summary
+    ```
+
+3. Run the playbook:
+
+    ```console
+    $ ansible-playbook -i <inventory file> playbooks/adhoc/preflight/check.yml
+    ```
diff --git a/playbooks/adhoc/preflight/check.yml b/playbooks/adhoc/preflight/check.yml
new file mode 100644
index 000000000..b66c1a824
--- /dev/null
+++ b/playbooks/adhoc/preflight/check.yml
@@ -0,0 +1,159 @@
+---
+- hosts: OSEv3
+  gather_facts: no
+  tasks:
+    - set_fact:
+        deployment_type: "{{ deployment_type | default('openshift-enterprise') }}"
+        containerized: "{{ containerized | default('no') | bool }}"
+        openshift_release: "{{ openshift_release | default('3.3') }}"
+        oo_preflight_check_results: "{{ oo_preflight_check_results | default([]) }}"
+
+- hosts: OSEv3
+  name: check content available on all hosts
+  gather_facts: no
+  ignore_errors: yes
+  tasks:
+    - when:
+        - not containerized
+      block:
+
+        - name: determine if yum update will work
+          action: check_yum_update
+          register: r
+
+        - set_fact:
+            oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'check content available on all hosts'})] }}"
+
+        - name: determine if expected version matches what is available
+          aos_version:
+            version: "{{ openshift_release }}"
+          when:
+            - deployment_type == "openshift-enterprise"
+          register: r
+
+        - set_fact:
+            oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'determine if expected version matches what is available'})] }}"
+
+- hosts: masters
+  name: determine if yum install of master pkgs will work
+  gather_facts: no
+  ignore_errors: yes
+  tasks:
+    - when:
+        - not containerized
+      block:
+
+        - name: main packages for enterprise
+          when:
+            - deployment_type == "openshift-enterprise"
+          check_yum_update:
+            packages:
+              - atomic-openshift
+              - atomic-openshift-clients
+              - atomic-openshift-master
+          register: r
+
+        - set_fact:
+            oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'main packages for enterprise'})] }}"
+
+        - name: main packages for origin
+          when:
+            - deployment_type == "origin"
+          check_yum_update:
+            packages:
+              - origin
+              - origin-clients
+              - origin-master
+          register: r
+
+        - set_fact:
+            oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'main packages for origin'})] }}"
+
+        - name: other master packages
+          check_yum_update:
+            packages:
+              - etcd
+              - bash-completion
+              - cockpit-bridge
+              - cockpit-docker
+              - cockpit-kubernetes
+              - cockpit-shell
+              - cockpit-ws
+              - httpd-tools
+          register: r
+
+        - set_fact:
+            oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'other master packages'})] }}"
+
+- hosts: nodes
+  name: determine if yum install of node pkgs will work
+  gather_facts: no
+  ignore_errors: yes
+  tasks:
+    - when:
+        - not containerized
+      block:
+
+        - name: main packages for enterprise
+          when:
+            - deployment_type == "openshift-enterprise"
+          check_yum_update:
+            packages:
+              - atomic-openshift
+              - atomic-openshift-node
+              - atomic-openshift-sdn-ovs
+          register: r
+
+        - set_fact:
+            oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'main packages for enterprise'})] }}"
+
+        - name: main packages for origin
+          when:
+            - deployment_type == "origin"
+          check_yum_update:
+            packages:
+              - origin
+              - origin-node
+              - origin-sdn-ovs
+          register: r
+
+        - set_fact:
+            oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'main packages for origin'})] }}"
+
+        - name: other node packages
+          check_yum_update:
+            packages:
+              - docker
+              - PyYAML
+              - firewalld
+              - iptables
+              - iptables-services
+              - nfs-utils
+              - ntp
+              - yum-utils
+              - dnsmasq
+              - libselinux-python
+              - ceph-common
+              - glusterfs-fuse
+              - iscsi-initiator-utils
+              - pyparted
+              - python-httplib2
+              - openssl
+              - flannel
+              - bind
+          register: r
+
+        - set_fact:
+            oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'other node packages'})] }}"
+
+- hosts: OSEv3
+  name: verify check results
+  gather_facts: no
+  tasks:
+
+    - set_fact:
+        oo_preflight_check_failures: "{{ oo_preflight_check_results | select('failed', 'equalto', True) | list }}"
+
+    - name: ensure all checks succeed
+      action: fail
+      when: oo_preflight_check_failures
diff --git a/playbooks/adhoc/preflight/library b/playbooks/adhoc/preflight/library
new file mode 120000
index 000000000..ba40d2f56
--- /dev/null
+++ b/playbooks/adhoc/preflight/library
@@ -0,0 +1 @@
+../../../library
\ No newline at end of file
-- 
cgit v1.2.3


From 80e824c00c73f15a5958680d73d68b505da4d327 Mon Sep 17 00:00:00 2001
From: Rodolfo Carvalho <rhcarvalho@gmail.com>
Date: Fri, 6 Jan 2017 12:26:53 +0100
Subject: Make callback plugin an always-on aggregate plugin

This makes it so that one doesn't need to explicitly enable nor replace
the default callback plugin.
---
 ansible.cfg.example                      |  1 -
 callback_plugins/default_plus_summary.py | 94 --------------------------------
 callback_plugins/failure_summary.py      | 94 ++++++++++++++++++++++++++++++++
 3 files changed, 94 insertions(+), 95 deletions(-)
 delete mode 100644 callback_plugins/default_plus_summary.py
 create mode 100644 callback_plugins/failure_summary.py

diff --git a/ansible.cfg.example b/ansible.cfg.example
index c8cc5db03..b07705ef1 100644
--- a/ansible.cfg.example
+++ b/ansible.cfg.example
@@ -15,7 +15,6 @@ log_path = /tmp/ansible.log
 
 # prints a descriptive summary of failed tasks
 callback_plugins = ./callback_plugins
-stdout_callback = default_plus_summary
 
 # Uncomment to use the provided BYO inventory
 #hostfile = inventory/byo/hosts
diff --git a/callback_plugins/default_plus_summary.py b/callback_plugins/default_plus_summary.py
deleted file mode 100644
index 610a3e98a..000000000
--- a/callback_plugins/default_plus_summary.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# vim: expandtab:tabstop=4:shiftwidth=4
-'''
-Ansible callback plugin.
-'''
-
-from ansible.plugins.callback.default import CallbackModule as CallbackModule_default
-from ansible import constants as C
-from ansible.utils.color import stringc
-
-
-class CallbackModule(CallbackModule_default):
-    '''
-    This is like the default callback plugin, but also stores results and
-    summarizes failures.
-    '''
-
-    CALLBACK_VERSION = 2.0
-    CALLBACK_TYPE = 'stdout'
-    CALLBACK_NAME = 'default_plus_summary'
-
-    def __init__(self):
-        super(CallbackModule, self).__init__()
-        self.__failures = []
-
-    def v2_runner_on_failed(self, result, ignore_errors=False):
-        super(CallbackModule, self).v2_runner_on_failed(result, ignore_errors)
-        self.__failures.append(dict(result=result, ignore_errors=ignore_errors))
-
-    def v2_playbook_on_stats(self, stats):
-        super(CallbackModule, self).v2_playbook_on_stats(stats)
-        # TODO: update condition to consider a host var or env var to
-        # enable/disable the summary, so that we can control the output from a
-        # play.
-        if self.__failures:
-            self._print_failure_summary()
-
-    def _print_failure_summary(self):
-        '''Print a summary of failed tasks (including ignored failures).'''
-        self._display.display(u'\nFailure summary:\n')
-
-        # TODO: group failures by host or by task. If grouped by host, it is
-        # easy to see all problems of a given host. If grouped by task, it is
-        # easy to see what hosts needs the same fix.
-
-        width = len(str(len(self.__failures)))
-        initial_indent_format = u'  {{:>{width}}}. '.format(width=width)
-        initial_indent_len = len(initial_indent_format.format(0))
-        subsequent_indent = u' ' * initial_indent_len
-        subsequent_extra_indent = u' ' * (initial_indent_len + 10)
-
-        for i, failure in enumerate(self.__failures, 1):
-            lines = _format_failure(failure)
-            self._display.display(u'\n{}{}'.format(initial_indent_format.format(i), lines[0]))
-            for line in lines[1:]:
-                line = line.replace(u'\n', u'\n' + subsequent_extra_indent)
-                indented = u'{}{}'.format(subsequent_indent, line)
-                self._display.display(indented)
-
-
-# Reason: disable pylint protected-access because we need to access _*
-#         attributes of a task result to implement this method.
-# Status: permanently disabled unless Ansible's API changes.
-# pylint: disable=protected-access
-def _format_failure(failure):
-    '''Return a list of pretty-formatted lines describing a failure, including
-    relevant information about it. Line separators are not included.'''
-    result = failure['result']
-    host = result._host.get_name()
-    play = _get_play(result._task)
-    if play:
-        play = play.get_name()
-    task = result._task.get_name()
-    msg = result._result.get('msg', u'???')
-    rows = (
-        (u'Host', host),
-        (u'Play', play),
-        (u'Task', task),
-        (u'Message', stringc(msg, C.COLOR_ERROR)),
-    )
-    row_format = '{:10}{}'
-    return [row_format.format(header + u':', body) for header, body in rows]
-
-
-# Reason: disable pylint protected-access because we need to access _*
-#         attributes of obj to implement this function.
-#         This is inspired by ansible.playbook.base.Base.dump_me.
-# Status: permanently disabled unless Ansible's API changes.
-# pylint: disable=protected-access
-def _get_play(obj):
-    '''Given a task or block, recursively tries to find its parent play.'''
-    if hasattr(obj, '_play'):
-        return obj._play
-    if getattr(obj, '_parent'):
-        return _get_play(obj._parent)
diff --git a/callback_plugins/failure_summary.py b/callback_plugins/failure_summary.py
new file mode 100644
index 000000000..bcb0123b0
--- /dev/null
+++ b/callback_plugins/failure_summary.py
@@ -0,0 +1,94 @@
+# vim: expandtab:tabstop=4:shiftwidth=4
+'''
+Ansible callback plugin.
+'''
+
+from ansible.plugins.callback import CallbackBase
+from ansible import constants as C
+from ansible.utils.color import stringc
+
+
+class CallbackModule(CallbackBase):
+    '''
+    This callback plugin stores task results and summarizes failures.
+    '''
+
+    CALLBACK_VERSION = 2.0
+    CALLBACK_TYPE = 'aggregate'
+    CALLBACK_NAME = 'failure_summary'
+    CALLBACK_NEEDS_WHITELIST = False
+
+    def __init__(self):
+        super(CallbackModule, self).__init__()
+        self.__failures = []
+
+    def v2_runner_on_failed(self, result, ignore_errors=False):
+        super(CallbackModule, self).v2_runner_on_failed(result, ignore_errors)
+        self.__failures.append(dict(result=result, ignore_errors=ignore_errors))
+
+    def v2_playbook_on_stats(self, stats):
+        super(CallbackModule, self).v2_playbook_on_stats(stats)
+        # TODO: update condition to consider a host var or env var to
+        # enable/disable the summary, so that we can control the output from a
+        # play.
+        if self.__failures:
+            self._print_failure_summary()
+
+    def _print_failure_summary(self):
+        '''Print a summary of failed tasks (including ignored failures).'''
+        self._display.display(u'\nFailure summary:\n')
+
+        # TODO: group failures by host or by task. If grouped by host, it is
+        # easy to see all problems of a given host. If grouped by task, it is
+        # easy to see what hosts needs the same fix.
+
+        width = len(str(len(self.__failures)))
+        initial_indent_format = u'  {{:>{width}}}. '.format(width=width)
+        initial_indent_len = len(initial_indent_format.format(0))
+        subsequent_indent = u' ' * initial_indent_len
+        subsequent_extra_indent = u' ' * (initial_indent_len + 10)
+
+        for i, failure in enumerate(self.__failures, 1):
+            lines = _format_failure(failure)
+            self._display.display(u'\n{}{}'.format(initial_indent_format.format(i), lines[0]))
+            for line in lines[1:]:
+                line = line.replace(u'\n', u'\n' + subsequent_extra_indent)
+                indented = u'{}{}'.format(subsequent_indent, line)
+                self._display.display(indented)
+
+
+# Reason: disable pylint protected-access because we need to access _*
+#         attributes of a task result to implement this method.
+# Status: permanently disabled unless Ansible's API changes.
+# pylint: disable=protected-access
+def _format_failure(failure):
+    '''Return a list of pretty-formatted lines describing a failure, including
+    relevant information about it. Line separators are not included.'''
+    result = failure['result']
+    host = result._host.get_name()
+    play = _get_play(result._task)
+    if play:
+        play = play.get_name()
+    task = result._task.get_name()
+    msg = result._result.get('msg', u'???')
+    rows = (
+        (u'Host', host),
+        (u'Play', play),
+        (u'Task', task),
+        (u'Message', stringc(msg, C.COLOR_ERROR)),
+    )
+    row_format = '{:10}{}'
+    return [row_format.format(header + u':', body) for header, body in rows]
+
+
+# Reason: disable pylint protected-access because we need to access _*
+#         attributes of obj to implement this function.
+#         This is inspired by ansible.playbook.base.Base.dump_me.
+# Status: permanently disabled unless Ansible's API changes.
+# pylint: disable=protected-access
+def _get_play(obj):
+    '''Given a task or block, recursively tries to find its parent play.'''
+    if hasattr(obj, '_play'):
+        return obj._play
+    if getattr(obj, '_parent'):
+        return _get_play(obj._parent)
-- 
cgit v1.2.3


From feeeb48f45ecbde3e5bcc2a66c7f947080d433e5 Mon Sep 17 00:00:00 2001
From: Rodolfo Carvalho <rhcarvalho@gmail.com>
Date: Fri, 6 Jan 2017 18:54:47 +0100
Subject: Make flake8 (py35) happy on bare except

---
 library/check_yum_update.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/library/check_yum_update.py b/library/check_yum_update.py
index 0c1974324..296ebd44f 100755
--- a/library/check_yum_update.py
+++ b/library/check_yum_update.py
@@ -49,7 +49,7 @@ def main():  # pylint: disable=missing-docstring,too-many-branches
         bail('Error with yum repository configuration: %s' % e)
     #   3. other/unknown
     #    * just report the problem verbatim
-    except:  # pylint: disable=bare-except
+    except:  # pylint: disable=bare-except; # noqa
         bail('Unexpected error with yum repository: %s' % sys.exc_info()[1])
 
     packages = module.params['packages']
@@ -59,7 +59,7 @@ def main():  # pylint: disable=missing-docstring,too-many-branches
             yb.install(name=pkg)
         except yum.Errors.InstallError as e:  # pylint: disable=invalid-name
             no_such_pkg.append(pkg)
-        except:  # pylint: disable=bare-except
+        except:  # pylint: disable=bare-except; # noqa
             bail('Unexpected error with yum install/update: %s' %
                  sys.exc_info()[1])
     if not packages:
@@ -75,7 +75,7 @@ def main():  # pylint: disable=missing-docstring,too-many-branches
 
     try:
         txn_result, txn_msgs = yb.buildTransaction()
-    except:  # pylint: disable=bare-except
+    except:  # pylint: disable=bare-except; # noqa
         bail('Unexpected error during dependency resolution for yum update: \n %s' %
              sys.exc_info()[1])
 
-- 
cgit v1.2.3


From 9041a816210132b1dad86217ecfef5f9fec4a8d2 Mon Sep 17 00:00:00 2001
From: Rodolfo Carvalho <rhcarvalho@gmail.com>
Date: Fri, 6 Jan 2017 20:53:05 +0100
Subject: Refactor preflight check into roles

---
 ansible.cfg.example                                |   3 -
 callback_plugins/failure_summary.py                |  94 -------------
 playbooks/adhoc/preflight/README.md                |  10 +-
 playbooks/adhoc/preflight/check.yml                | 153 ++-------------------
 playbooks/adhoc/preflight/library                  |   1 -
 roles/openshift_preflight/README.md                |  53 +++++++
 roles/openshift_preflight/common/library           |   1 +
 roles/openshift_preflight/common/tasks/main.yml    |  22 +++
 roles/openshift_preflight/facts/tasks/main.yml     |   7 +
 roles/openshift_preflight/masters/library          |   1 +
 roles/openshift_preflight/masters/tasks/main.yml   |  47 +++++++
 roles/openshift_preflight/nodes/library            |   1 +
 roles/openshift_preflight/nodes/tasks/main.yml     |  57 ++++++++
 .../callback_plugins/zz_failure_summary.py         |  96 +++++++++++++
 .../verify_status/tasks/main.yml                   |   8 ++
 15 files changed, 307 insertions(+), 247 deletions(-)
 delete mode 100644 callback_plugins/failure_summary.py
 delete mode 120000 playbooks/adhoc/preflight/library
 create mode 100644 roles/openshift_preflight/README.md
 create mode 120000 roles/openshift_preflight/common/library
 create mode 100644 roles/openshift_preflight/common/tasks/main.yml
 create mode 100644 roles/openshift_preflight/facts/tasks/main.yml
 create mode 120000 roles/openshift_preflight/masters/library
 create mode 100644 roles/openshift_preflight/masters/tasks/main.yml
 create mode 120000 roles/openshift_preflight/nodes/library
 create mode 100644 roles/openshift_preflight/nodes/tasks/main.yml
 create mode 100644 roles/openshift_preflight/verify_status/callback_plugins/zz_failure_summary.py
 create mode 100644 roles/openshift_preflight/verify_status/tasks/main.yml

diff --git a/ansible.cfg.example b/ansible.cfg.example
index b07705ef1..6a7722ad8 100644
--- a/ansible.cfg.example
+++ b/ansible.cfg.example
@@ -13,9 +13,6 @@ roles_path = roles/
 # Set the log_path
 log_path = /tmp/ansible.log
 
-# prints a descriptive summary of failed tasks
-callback_plugins = ./callback_plugins
-
 # Uncomment to use the provided BYO inventory
 #hostfile = inventory/byo/hosts
 
diff --git a/callback_plugins/failure_summary.py b/callback_plugins/failure_summary.py
deleted file mode 100644
index bcb0123b0..000000000
--- a/callback_plugins/failure_summary.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# vim: expandtab:tabstop=4:shiftwidth=4
-'''
-Ansible callback plugin.
-'''
-
-from ansible.plugins.callback import CallbackBase
-from ansible import constants as C
-from ansible.utils.color import stringc
-
-
-class CallbackModule(CallbackBase):
-    '''
-    This callback plugin stores task results and summarizes failures.
-    '''
-
-    CALLBACK_VERSION = 2.0
-    CALLBACK_TYPE = 'aggregate'
-    CALLBACK_NAME = 'failure_summary'
-    CALLBACK_NEEDS_WHITELIST = False
-
-    def __init__(self):
-        super(CallbackModule, self).__init__()
-        self.__failures = []
-
-    def v2_runner_on_failed(self, result, ignore_errors=False):
-        super(CallbackModule, self).v2_runner_on_failed(result, ignore_errors)
-        self.__failures.append(dict(result=result, ignore_errors=ignore_errors))
-
-    def v2_playbook_on_stats(self, stats):
-        super(CallbackModule, self).v2_playbook_on_stats(stats)
-        # TODO: update condition to consider a host var or env var to
-        # enable/disable the summary, so that we can control the output from a
-        # play.
-        if self.__failures:
-            self._print_failure_summary()
-
-    def _print_failure_summary(self):
-        '''Print a summary of failed tasks (including ignored failures).'''
-        self._display.display(u'\nFailure summary:\n')
-
-        # TODO: group failures by host or by task. If grouped by host, it is
-        # easy to see all problems of a given host. If grouped by task, it is
-        # easy to see what hosts needs the same fix.
-
-        width = len(str(len(self.__failures)))
-        initial_indent_format = u'  {{:>{width}}}. '.format(width=width)
-        initial_indent_len = len(initial_indent_format.format(0))
-        subsequent_indent = u' ' * initial_indent_len
-        subsequent_extra_indent = u' ' * (initial_indent_len + 10)
-
-        for i, failure in enumerate(self.__failures, 1):
-            lines = _format_failure(failure)
-            self._display.display(u'\n{}{}'.format(initial_indent_format.format(i), lines[0]))
-            for line in lines[1:]:
-                line = line.replace(u'\n', u'\n' + subsequent_extra_indent)
-                indented = u'{}{}'.format(subsequent_indent, line)
-                self._display.display(indented)
-
-
-# Reason: disable pylint protected-access because we need to access _*
-#         attributes of a task result to implement this method.
-# Status: permanently disabled unless Ansible's API changes.
-# pylint: disable=protected-access
-def _format_failure(failure):
-    '''Return a list of pretty-formatted lines describing a failure, including
-    relevant information about it. Line separators are not included.'''
-    result = failure['result']
-    host = result._host.get_name()
-    play = _get_play(result._task)
-    if play:
-        play = play.get_name()
-    task = result._task.get_name()
-    msg = result._result.get('msg', u'???')
-    rows = (
-        (u'Host', host),
-        (u'Play', play),
-        (u'Task', task),
-        (u'Message', stringc(msg, C.COLOR_ERROR)),
-    )
-    row_format = '{:10}{}'
-    return [row_format.format(header + u':', body) for header, body in rows]
-
-
-# Reason: disable pylint protected-access because we need to access _*
-#         attributes of obj to implement this function.
-#         This is inspired by ansible.playbook.base.Base.dump_me.
-# Status: permanently disabled unless Ansible's API changes.
-# pylint: disable=protected-access
-def _get_play(obj):
-    '''Given a task or block, recursively tries to find its parent play.'''
-    if hasattr(obj, '_play'):
-        return obj._play
-    if getattr(obj, '_parent'):
-        return _get_play(obj._parent)
diff --git a/playbooks/adhoc/preflight/README.md b/playbooks/adhoc/preflight/README.md
index 871c6af01..7636ecca3 100644
--- a/playbooks/adhoc/preflight/README.md
+++ b/playbooks/adhoc/preflight/README.md
@@ -36,15 +36,7 @@ against your inventory file. Here is the step-by-step:
     $ cd openshift-ansible
     ```
 
-2. Configure a custom callback plugin to get a summary of problems at the end of
-the playbook run:
-
-    ```console
-    $ export ANSIBLE_CALLBACK_PLUGINS=callback_plugins \
-             ANSIBLE_STDOUT_CALLBACK=default_plus_summary
-    ```
-
-3. Run the playbook:
+2. Run the playbook:
 
     ```console
     $ ansible-playbook -i <inventory file> playbooks/adhoc/preflight/check.yml
diff --git a/playbooks/adhoc/preflight/check.yml b/playbooks/adhoc/preflight/check.yml
index b66c1a824..460ba4ce4 100644
--- a/playbooks/adhoc/preflight/check.yml
+++ b/playbooks/adhoc/preflight/check.yml
@@ -1,159 +1,32 @@
 ---
 - hosts: OSEv3
   gather_facts: no
-  tasks:
-    - set_fact:
-        deployment_type: "{{ deployment_type | default('openshift-enterprise') }}"
-        containerized: "{{ containerized | default('no') | bool }}"
-        openshift_release: "{{ openshift_release | default('3.3') }}"
-        oo_preflight_check_results: "{{ oo_preflight_check_results | default([]) }}"
+  roles:
+    - openshift_preflight/facts
 
 - hosts: OSEv3
-  name: check content available on all hosts
+  name: checks that apply to all hosts
   gather_facts: no
   ignore_errors: yes
-  tasks:
-    - when:
-        - not containerized
-      block:
-
-        - name: determine if yum update will work
-          action: check_yum_update
-          register: r
-
-        - set_fact:
-            oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'check content available on all hosts'})] }}"
-
-        - name: determine if expected version matches what is available
-          aos_version:
-            version: "{{ openshift_release }}"
-          when:
-            - deployment_type == "openshift-enterprise"
-          register: r
-
-        - set_fact:
-            oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'determine if expected version matches what is available'})] }}"
+  roles:
+    - openshift_preflight/common
 
 - hosts: masters
-  name: determine if yum install of master pkgs will work
+  name: checks that apply to masters
   gather_facts: no
   ignore_errors: yes
-  tasks:
-    - when:
-        - not containerized
-      block:
-
-        - name: main packages for enterprise
-          when:
-            - deployment_type == "openshift-enterprise"
-          check_yum_update:
-            packages:
-              - atomic-openshift
-              - atomic-openshift-clients
-              - atomic-openshift-master
-          register: r
-
-        - set_fact:
-            oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'main packages for enterprise'})] }}"
-
-        - name: main packages for origin
-          when:
-            - deployment_type == "origin"
-          check_yum_update:
-            packages:
-              - origin
-              - origin-clients
-              - origin-master
-          register: r
-
-        - set_fact:
-            oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'main packages for origin'})] }}"
-
-        - name: other master packages
-          check_yum_update:
-            packages:
-              - etcd
-              - bash-completion
-              - cockpit-bridge
-              - cockpit-docker
-              - cockpit-kubernetes
-              - cockpit-shell
-              - cockpit-ws
-              - httpd-tools
-          register: r
-
-        - set_fact:
-            oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'other master packages'})] }}"
+  roles:
+    - openshift_preflight/masters
 
 - hosts: nodes
-  name: determine if yum install of node pkgs will work
+  name: checks that apply to nodes
   gather_facts: no
   ignore_errors: yes
-  tasks:
-    - when:
-        - not containerized
-      block:
-
-        - name: main packages for enterprise
-          when:
-            - deployment_type == "openshift-enterprise"
-          check_yum_update:
-            packages:
-              - atomic-openshift
-              - atomic-openshift-node
-              - atomic-openshift-sdn-ovs
-          register: r
-
-        - set_fact:
-            oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'main packages for enterprise'})] }}"
-
-        - name: main packages for origin
-          when:
-            - deployment_type == "origin"
-          check_yum_update:
-            packages:
-              - origin
-              - origin-node
-              - origin-sdn-ovs
-          register: r
-
-        - set_fact:
-            oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'main packages for origin'})] }}"
-
-        - name: other node packages
-          check_yum_update:
-            packages:
-              - docker
-              - PyYAML
-              - firewalld
-              - iptables
-              - iptables-services
-              - nfs-utils
-              - ntp
-              - yum-utils
-              - dnsmasq
-              - libselinux-python
-              - ceph-common
-              - glusterfs-fuse
-              - iscsi-initiator-utils
-              - pyparted
-              - python-httplib2
-              - openssl
-              - flannel
-              - bind
-          register: r
-
-        - set_fact:
-            oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'other node packages'})] }}"
+  roles:
+    - openshift_preflight/nodes
 
 - hosts: OSEv3
   name: verify check results
   gather_facts: no
-  tasks:
-
-    - set_fact:
-        oo_preflight_check_failures: "{{ oo_preflight_check_results | select('failed', 'equalto', True) | list }}"
-
-    - name: ensure all checks succeed
-      action: fail
-      when: oo_preflight_check_failures
+  roles:
+    - openshift_preflight/verify_status
diff --git a/playbooks/adhoc/preflight/library b/playbooks/adhoc/preflight/library
deleted file mode 120000
index ba40d2f56..000000000
--- a/playbooks/adhoc/preflight/library
+++ /dev/null
@@ -1 +0,0 @@
-../../../library
\ No newline at end of file
diff --git a/roles/openshift_preflight/README.md b/roles/openshift_preflight/README.md
new file mode 100644
index 000000000..a3d734e32
--- /dev/null
+++ b/roles/openshift_preflight/README.md
@@ -0,0 +1,53 @@
+OpenShift Preflight Checks
+==========================
+
+This role detects common problems prior to installing OpenShift.
+
+Requirements
+------------
+
+* Ansible 2.2+
+
+Role Variables
+--------------
+
+None
+
+Dependencies
+------------
+
+None
+
+Example Playbook
+----------------
+
+```yaml
+---
+- hosts: OSEv3
+  gather_facts: no
+  roles:
+    - openshift_preflight/facts
+
+- hosts: OSEv3
+  name: checks that apply to all hosts
+  gather_facts: no
+  ignore_errors: yes
+  roles:
+    - openshift_preflight/common
+
+- hosts: OSEv3
+  name: verify check results
+  gather_facts: no
+  roles:
+    - openshift_preflight/verify_status
+```
+
+License
+-------
+
+Apache License Version 2.0
+
+Author Information
+------------------
+
+Customer Success team (dev@lists.openshift.redhat.com)
diff --git a/roles/openshift_preflight/common/library b/roles/openshift_preflight/common/library
new file mode 120000
index 000000000..ba40d2f56
--- /dev/null
+++ b/roles/openshift_preflight/common/library
@@ -0,0 +1 @@
+../../../library
\ No newline at end of file
diff --git a/roles/openshift_preflight/common/tasks/main.yml b/roles/openshift_preflight/common/tasks/main.yml
new file mode 100644
index 000000000..423b13586
--- /dev/null
+++ b/roles/openshift_preflight/common/tasks/main.yml
@@ -0,0 +1,22 @@
+---
+# check content available on all hosts
+- when:
+    - not containerized
+  block:
+
+    - name: determine if yum update will work
+      action: check_yum_update
+      register: r
+
+    - set_fact:
+        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'check content available on all hosts'})] }}"
+
+    - name: determine if expected version matches what is available
+      aos_version:
+        version: "{{ openshift_release }}"
+      when:
+        - deployment_type == "openshift-enterprise"
+      register: r
+
+    - set_fact:
+        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'determine if expected version matches what is available'})] }}"
diff --git a/roles/openshift_preflight/facts/tasks/main.yml b/roles/openshift_preflight/facts/tasks/main.yml
new file mode 100644
index 000000000..15a108510
--- /dev/null
+++ b/roles/openshift_preflight/facts/tasks/main.yml
@@ -0,0 +1,7 @@
+---
+- name: set common variables
+  set_fact:
+    deployment_type: "{{ deployment_type | default('openshift-enterprise') }}"
+    containerized: "{{ containerized | default('no') | bool }}"
+    openshift_release: "{{ openshift_release | default('3.3') }}"
+    oo_preflight_check_results: "{{ oo_preflight_check_results | default([]) }}"
diff --git a/roles/openshift_preflight/masters/library b/roles/openshift_preflight/masters/library
new file mode 120000
index 000000000..ba40d2f56
--- /dev/null
+++ b/roles/openshift_preflight/masters/library
@@ -0,0 +1 @@
+../../../library
\ No newline at end of file
diff --git a/roles/openshift_preflight/masters/tasks/main.yml b/roles/openshift_preflight/masters/tasks/main.yml
new file mode 100644
index 000000000..ed42f69fa
--- /dev/null
+++ b/roles/openshift_preflight/masters/tasks/main.yml
@@ -0,0 +1,47 @@
+---
+# determine if yum install of master pkgs will work
+- when:
+    - not containerized
+  block:
+
+    - name: main packages for enterprise
+      when:
+        - deployment_type == "openshift-enterprise"
+      check_yum_update:
+        packages:
+          - atomic-openshift
+          - atomic-openshift-clients
+          - atomic-openshift-master
+      register: r
+
+    - set_fact:
+        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'main packages for enterprise'})] }}"
+
+    - name: main packages for origin
+      when:
+        - deployment_type == "origin"
+      check_yum_update:
+        packages:
+          - origin
+          - origin-clients
+          - origin-master
+      register: r
+
+    - set_fact:
+        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'main packages for origin'})] }}"
+
+    - name: other master packages
+      check_yum_update:
+        packages:
+          - etcd
+          - bash-completion
+          - cockpit-bridge
+          - cockpit-docker
+          - cockpit-kubernetes
+          - cockpit-shell
+          - cockpit-ws
+          - httpd-tools
+      register: r
+
+    - set_fact:
+        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'other master packages'})] }}"
diff --git a/roles/openshift_preflight/nodes/library b/roles/openshift_preflight/nodes/library
new file mode 120000
index 000000000..ba40d2f56
--- /dev/null
+++ b/roles/openshift_preflight/nodes/library
@@ -0,0 +1 @@
+../../../library
\ No newline at end of file
diff --git a/roles/openshift_preflight/nodes/tasks/main.yml b/roles/openshift_preflight/nodes/tasks/main.yml
new file mode 100644
index 000000000..16c7212d4
--- /dev/null
+++ b/roles/openshift_preflight/nodes/tasks/main.yml
@@ -0,0 +1,57 @@
+---
+# determine if yum install of node pkgs will work
+- when:
+    - not containerized
+  block:
+
+    - name: main packages for enterprise
+      when:
+        - deployment_type == "openshift-enterprise"
+      check_yum_update:
+        packages:
+          - atomic-openshift
+          - atomic-openshift-node
+          - atomic-openshift-sdn-ovs
+      register: r
+
+    - set_fact:
+        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'main packages for enterprise'})] }}"
+
+    - name: main packages for origin
+      when:
+        - deployment_type == "origin"
+      check_yum_update:
+        packages:
+          - origin
+          - origin-node
+          - origin-sdn-ovs
+      register: r
+
+    - set_fact:
+        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'main packages for origin'})] }}"
+
+    - name: other node packages
+      check_yum_update:
+        packages:
+          - docker
+          - PyYAML
+          - firewalld
+          - iptables
+          - iptables-services
+          - nfs-utils
+          - ntp
+          - yum-utils
+          - dnsmasq
+          - libselinux-python
+          - ceph-common
+          - glusterfs-fuse
+          - iscsi-initiator-utils
+          - pyparted
+          - python-httplib2
+          - openssl
+          - flannel
+          - bind
+      register: r
+
+    - set_fact:
+        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'other node packages'})] }}"
diff --git a/roles/openshift_preflight/verify_status/callback_plugins/zz_failure_summary.py b/roles/openshift_preflight/verify_status/callback_plugins/zz_failure_summary.py
new file mode 100644
index 000000000..180ed8d8f
--- /dev/null
+++ b/roles/openshift_preflight/verify_status/callback_plugins/zz_failure_summary.py
@@ -0,0 +1,96 @@
+# vim: expandtab:tabstop=4:shiftwidth=4
+'''
+Ansible callback plugin.
+'''
+
+from ansible.plugins.callback import CallbackBase
+from ansible import constants as C
+from ansible.utils.color import stringc
+
+
+class CallbackModule(CallbackBase):
+    '''
+    This callback plugin stores task results and summarizes failures.
+    The file name is prefixed with `zz_` to make this plugin be loaded last by
+    Ansible, thus making its output the last thing that users see.
+    '''
+
+    CALLBACK_VERSION = 2.0
+    CALLBACK_TYPE = 'aggregate'
+    CALLBACK_NAME = 'failure_summary'
+    CALLBACK_NEEDS_WHITELIST = False
+
+    def __init__(self):
+        super(CallbackModule, self).__init__()
+        self.__failures = []
+
+    def v2_runner_on_failed(self, result, ignore_errors=False):
+        super(CallbackModule, self).v2_runner_on_failed(result, ignore_errors)
+        self.__failures.append(dict(result=result, ignore_errors=ignore_errors))
+
+    def v2_playbook_on_stats(self, stats):
+        super(CallbackModule, self).v2_playbook_on_stats(stats)
+        # TODO: update condition to consider a host var or env var to
+        # enable/disable the summary, so that we can control the output from a
+        # play.
+        if self.__failures:
+            self._print_failure_summary()
+
+    def _print_failure_summary(self):
+        '''Print a summary of failed tasks (including ignored failures).'''
+        self._display.display(u'\nFailure summary:\n')
+
+        # TODO: group failures by host or by task. If grouped by host, it is
+        # easy to see all problems of a given host. If grouped by task, it is
+        # easy to see what hosts needs the same fix.
+
+        width = len(str(len(self.__failures)))
+        initial_indent_format = u'  {{:>{width}}}. '.format(width=width)
+        initial_indent_len = len(initial_indent_format.format(0))
+        subsequent_indent = u' ' * initial_indent_len
+        subsequent_extra_indent = u' ' * (initial_indent_len + 10)
+
+        for i, failure in enumerate(self.__failures, 1):
+            lines = _format_failure(failure)
+            self._display.display(u'\n{}{}'.format(initial_indent_format.format(i), lines[0]))
+            for line in lines[1:]:
+                line = line.replace(u'\n', u'\n' + subsequent_extra_indent)
+                indented = u'{}{}'.format(subsequent_indent, line)
+                self._display.display(indented)
+
+
+# Reason: disable pylint protected-access because we need to access _*
+#         attributes of a task result to implement this method.
+# Status: permanently disabled unless Ansible's API changes.
+# pylint: disable=protected-access
+def _format_failure(failure):
+    '''Return a list of pretty-formatted lines describing a failure, including
+    relevant information about it. Line separators are not included.'''
+    result = failure['result']
+    host = result._host.get_name()
+    play = _get_play(result._task)
+    if play:
+        play = play.get_name()
+    task = result._task.get_name()
+    msg = result._result.get('msg', u'???')
+    rows = (
+        (u'Host', host),
+        (u'Play', play),
+        (u'Task', task),
+        (u'Message', stringc(msg, C.COLOR_ERROR)),
+    )
+    row_format = '{:10}{}'
+    return [row_format.format(header + u':', body) for header, body in rows]
+
+
+# Reason: disable pylint protected-access because we need to access _*
+#         attributes of obj to implement this function.
+#         This is inspired by ansible.playbook.base.Base.dump_me.
+# Status: permanently disabled unless Ansible's API changes.
+# pylint: disable=protected-access
+def _get_play(obj):
+    '''Given a task or block, recursively tries to find its parent play.'''
+    if hasattr(obj, '_play'):
+        return obj._play
+    if getattr(obj, '_parent'):
+        return _get_play(obj._parent)
diff --git a/roles/openshift_preflight/verify_status/tasks/main.yml b/roles/openshift_preflight/verify_status/tasks/main.yml
new file mode 100644
index 000000000..36ccf648a
--- /dev/null
+++ b/roles/openshift_preflight/verify_status/tasks/main.yml
@@ -0,0 +1,8 @@
+---
+- name: find check failures
+  set_fact:
+    oo_preflight_check_failures: "{{ oo_preflight_check_results | select('failed', 'equalto', True) | list }}"
+
+- name: ensure all checks succeed
+  action: fail
+  when: oo_preflight_check_failures
-- 
cgit v1.2.3


From 22d60dcccf62abd52caf9233fdcf0835d6343681 Mon Sep 17 00:00:00 2001
From: Rodolfo Carvalho <rhcarvalho@gmail.com>
Date: Thu, 12 Jan 2017 17:07:04 +0100
Subject: Move playbook to BYO

Because that's the main playbook directory in use.
---
 playbooks/adhoc/preflight/README.md         | 43 -----------------------------
 playbooks/adhoc/preflight/check.yml         | 32 ---------------------
 playbooks/byo/openshift-preflight/README.md | 43 +++++++++++++++++++++++++++++
 playbooks/byo/openshift-preflight/check.yml | 32 +++++++++++++++++++++
 4 files changed, 75 insertions(+), 75 deletions(-)
 delete mode 100644 playbooks/adhoc/preflight/README.md
 delete mode 100644 playbooks/adhoc/preflight/check.yml
 create mode 100644 playbooks/byo/openshift-preflight/README.md
 create mode 100644 playbooks/byo/openshift-preflight/check.yml

diff --git a/playbooks/adhoc/preflight/README.md b/playbooks/adhoc/preflight/README.md
deleted file mode 100644
index 7636ecca3..000000000
--- a/playbooks/adhoc/preflight/README.md
+++ /dev/null
@@ -1,43 +0,0 @@
-# Preflight checks
-
-Here we provide an Ansible playbook for detecting potential roadblocks prior to
-an install or upgrade.
-
-Ansible's default operation mode is to fail fast, on the first error. However,
-when performing checks, it is useful to gather as much information about
-problems as possible in a single run.
-
-The `check.yml` playbook runs a battery of checks against the inventory hosts
-and tells Ansible to ignore intermediate errors, thus giving a more complete
-diagnostic of the state of each host. Still, if any check failed, the playbook
-run will be marked as having failed.
-
-To facilitate understanding the problems that were encountered, we provide a
-custom callback plugin to summarize execution errors at the end of a playbook
-run.
-
----
-
-*Note that currently the `check.yml` playbook is only useful for RPM-based
-installations. Containerized installs are excluded from checks for now, but
-might be included in the future if there is demand for that.*
-
----
-
-## Running
-
-With an installation of Ansible 2.2 or greater, run the playbook directly
-against your inventory file. Here is the step-by-step:
-
-1. If you haven't done it yet, clone this repository:
-
-    ```console
-    $ git clone https://github.com/openshift/openshift-ansible
-    $ cd openshift-ansible
-    ```
-
-2. Run the playbook:
-
-    ```console
-    $ ansible-playbook -i <inventory file> playbooks/adhoc/preflight/check.yml
-    ```
diff --git a/playbooks/adhoc/preflight/check.yml b/playbooks/adhoc/preflight/check.yml
deleted file mode 100644
index 460ba4ce4..000000000
--- a/playbooks/adhoc/preflight/check.yml
+++ /dev/null
@@ -1,32 +0,0 @@
----
-- hosts: OSEv3
-  gather_facts: no
-  roles:
-    - openshift_preflight/facts
-
-- hosts: OSEv3
-  name: checks that apply to all hosts
-  gather_facts: no
-  ignore_errors: yes
-  roles:
-    - openshift_preflight/common
-
-- hosts: masters
-  name: checks that apply to masters
-  gather_facts: no
-  ignore_errors: yes
-  roles:
-    - openshift_preflight/masters
-
-- hosts: nodes
-  name: checks that apply to nodes
-  gather_facts: no
-  ignore_errors: yes
-  roles:
-    - openshift_preflight/nodes
-
-- hosts: OSEv3
-  name: verify check results
-  gather_facts: no
-  roles:
-    - openshift_preflight/verify_status
diff --git a/playbooks/byo/openshift-preflight/README.md b/playbooks/byo/openshift-preflight/README.md
new file mode 100644
index 000000000..b50292eac
--- /dev/null
+++ b/playbooks/byo/openshift-preflight/README.md
@@ -0,0 +1,43 @@
+# OpenShift preflight checks
+
+Here we provide an Ansible playbook for detecting potential roadblocks prior to
+an install or upgrade.
+
+Ansible's default operation mode is to fail fast, on the first error. However,
+when performing checks, it is useful to gather as much information about
+problems as possible in a single run.
+
+The `check.yml` playbook runs a battery of checks against the inventory hosts
+and tells Ansible to ignore intermediate errors, thus giving a more complete
+diagnostic of the state of each host. Still, if any check failed, the playbook
+run will be marked as having failed.
+
+To facilitate understanding the problems that were encountered, we provide a
+custom callback plugin to summarize execution errors at the end of a playbook
+run.
+
+---
+
+*Note that currently the `check.yml` playbook is only useful for RPM-based
+installations. Containerized installs are excluded from checks for now, but
+might be included in the future if there is demand for that.*
+
+---
+
+## Running
+
+With an installation of Ansible 2.2 or greater, run the playbook directly
+against your inventory file. Here is the step-by-step:
+
+1. If you haven't done it yet, clone this repository:
+
+    ```console
+    $ git clone https://github.com/openshift/openshift-ansible
+    $ cd openshift-ansible
+    ```
+
+2. Run the playbook:
+
+    ```console
+    $ ansible-playbook -i <inventory file> playbooks/byo/openshift-preflight/check.yml
+    ```
diff --git a/playbooks/byo/openshift-preflight/check.yml b/playbooks/byo/openshift-preflight/check.yml
new file mode 100644
index 000000000..460ba4ce4
--- /dev/null
+++ b/playbooks/byo/openshift-preflight/check.yml
@@ -0,0 +1,32 @@
+---
+- hosts: OSEv3
+  gather_facts: no
+  roles:
+    - openshift_preflight/facts
+
+- hosts: OSEv3
+  name: checks that apply to all hosts
+  gather_facts: no
+  ignore_errors: yes
+  roles:
+    - openshift_preflight/common
+
+- hosts: masters
+  name: checks that apply to masters
+  gather_facts: no
+  ignore_errors: yes
+  roles:
+    - openshift_preflight/masters
+
+- hosts: nodes
+  name: checks that apply to nodes
+  gather_facts: no
+  ignore_errors: yes
+  roles:
+    - openshift_preflight/nodes
+
+- hosts: OSEv3
+  name: verify check results
+  gather_facts: no
+  roles:
+    - openshift_preflight/verify_status
-- 
cgit v1.2.3


From 3c6b9fe8641c067f2f010d637b69e4e84b94017b Mon Sep 17 00:00:00 2001
From: Rodolfo Carvalho <rhcarvalho@gmail.com>
Date: Thu, 12 Jan 2017 18:01:14 +0100
Subject: Catch DBus exceptions on class initialization

I came across this:

Traceback (most recent call last):
  File "/tmp/ansible_BhXpTE/ansible_module_openshift_facts.py", line 2409, in <module>
    main()
  File "/tmp/ansible_BhXpTE/ansible_module_openshift_facts.py", line 2396, in main
    protected_facts_to_overwrite)
  File "/tmp/ansible_BhXpTE/ansible_module_openshift_facts.py", line 1855, in __init__
    protected_facts_to_overwrite)
  File "/tmp/ansible_BhXpTE/ansible_module_openshift_facts.py", line 1892, in generate_facts
    defaults = self.get_defaults(roles, deployment_type, deployment_subtype)
  File "/tmp/ansible_BhXpTE/ansible_module_openshift_facts.py", line 1989, in get_defaults
    version_info = get_docker_version_info()
  File "/tmp/ansible_BhXpTE/ansible_module_openshift_facts.py", line 1290, in get_docker_version_info
    if is_service_running('docker'):
  File "/tmp/ansible_BhXpTE/ansible_module_openshift_facts.py", line 1249, in is_service_running
    bus = SystemBus()
  File "/usr/lib64/python2.7/site-packages/dbus/_dbus.py", line 194, in __new__
    private=private)
  File "/usr/lib64/python2.7/site-packages/dbus/_dbus.py", line 100, in __new__
    bus = BusConnection.__new__(subclass, bus_type, mainloop=mainloop)
  File "/usr/lib64/python2.7/site-packages/dbus/bus.py", line 122, in __new__
    bus = cls._new_for_bus(address_or_type, mainloop=mainloop)
dbus.exceptions.DBusException: org.freedesktop.DBus.Error.FileNotFound: Failed to connect to socket /var/run/dbus/system_bus_socket: No such file or directory
---
 roles/openshift_facts/library/openshift_facts.py | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/roles/openshift_facts/library/openshift_facts.py b/roles/openshift_facts/library/openshift_facts.py
index 10e30f1c4..616b41c7b 100755
--- a/roles/openshift_facts/library/openshift_facts.py
+++ b/roles/openshift_facts/library/openshift_facts.py
@@ -1246,10 +1246,10 @@ def build_api_server_args(facts):
 def is_service_running(service):
     """ Queries systemd through dbus to see if the service is running """
     service_running = False
-    bus = SystemBus()
-    systemd = bus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1')
-    manager = Interface(systemd, dbus_interface='org.freedesktop.systemd1.Manager')
     try:
+        bus = SystemBus()
+        systemd = bus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1')
+        manager = Interface(systemd, dbus_interface='org.freedesktop.systemd1.Manager')
         service_unit = service if service.endswith('.service') else manager.GetUnit('{0}.service'.format(service))
         service_proxy = bus.get_object('org.freedesktop.systemd1', str(service_unit))
         service_properties = Interface(service_proxy, dbus_interface='org.freedesktop.DBus.Properties')
@@ -1258,6 +1258,8 @@ def is_service_running(service):
         if service_load_state == 'loaded' and service_active_state == 'active':
             service_running = True
     except DBusException:
+        # TODO: do not swallow exception, as it may be hiding useful debugging
+        # information.
         pass
 
     return service_running
-- 
cgit v1.2.3


From c4aec0fbd112fd1c8503e935ac954da268a2fdff Mon Sep 17 00:00:00 2001
From: Rodolfo Carvalho <rhcarvalho@gmail.com>
Date: Thu, 12 Jan 2017 18:30:24 +0100
Subject: Replace custom variables with openshift_facts

Note: on a simple example run of ansible-playbook against a single
docker-based host, I saw the execution time jump from 7s to 17s. That's
unfortunate, but it is probably better to reuse openshift_facts, than to
come up with new variables.
---
 playbooks/byo/openshift-preflight/check.yml      | 1 -
 roles/openshift_preflight/common/tasks/main.yml  | 3 +--
 roles/openshift_preflight/facts/meta/main.yml    | 3 +++
 roles/openshift_preflight/facts/tasks/main.yml   | 3 ---
 roles/openshift_preflight/masters/tasks/main.yml | 3 +--
 roles/openshift_preflight/nodes/tasks/main.yml   | 3 +--
 6 files changed, 6 insertions(+), 10 deletions(-)
 create mode 100644 roles/openshift_preflight/facts/meta/main.yml

diff --git a/playbooks/byo/openshift-preflight/check.yml b/playbooks/byo/openshift-preflight/check.yml
index 460ba4ce4..94f53eb84 100644
--- a/playbooks/byo/openshift-preflight/check.yml
+++ b/playbooks/byo/openshift-preflight/check.yml
@@ -1,6 +1,5 @@
 ---
 - hosts: OSEv3
-  gather_facts: no
   roles:
     - openshift_preflight/facts
 
diff --git a/roles/openshift_preflight/common/tasks/main.yml b/roles/openshift_preflight/common/tasks/main.yml
index 423b13586..bb54d0380 100644
--- a/roles/openshift_preflight/common/tasks/main.yml
+++ b/roles/openshift_preflight/common/tasks/main.yml
@@ -1,7 +1,6 @@
 ---
 # check content available on all hosts
-- when:
-    - not containerized
+- when: not openshift.common.is_containerized | bool
   block:
 
     - name: determine if yum update will work
diff --git a/roles/openshift_preflight/facts/meta/main.yml b/roles/openshift_preflight/facts/meta/main.yml
new file mode 100644
index 000000000..0bbeadd34
--- /dev/null
+++ b/roles/openshift_preflight/facts/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+  - role: openshift_facts
diff --git a/roles/openshift_preflight/facts/tasks/main.yml b/roles/openshift_preflight/facts/tasks/main.yml
index 15a108510..bf2d82196 100644
--- a/roles/openshift_preflight/facts/tasks/main.yml
+++ b/roles/openshift_preflight/facts/tasks/main.yml
@@ -1,7 +1,4 @@
 ---
 - name: set common variables
   set_fact:
-    deployment_type: "{{ deployment_type | default('openshift-enterprise') }}"
-    containerized: "{{ containerized | default('no') | bool }}"
-    openshift_release: "{{ openshift_release | default('3.3') }}"
     oo_preflight_check_results: "{{ oo_preflight_check_results | default([]) }}"
diff --git a/roles/openshift_preflight/masters/tasks/main.yml b/roles/openshift_preflight/masters/tasks/main.yml
index ed42f69fa..80d7af208 100644
--- a/roles/openshift_preflight/masters/tasks/main.yml
+++ b/roles/openshift_preflight/masters/tasks/main.yml
@@ -1,7 +1,6 @@
 ---
 # determine if yum install of master pkgs will work
-- when:
-    - not containerized
+- when: not openshift.common.is_containerized | bool
   block:
 
     - name: main packages for enterprise
diff --git a/roles/openshift_preflight/nodes/tasks/main.yml b/roles/openshift_preflight/nodes/tasks/main.yml
index 16c7212d4..c98b02bf5 100644
--- a/roles/openshift_preflight/nodes/tasks/main.yml
+++ b/roles/openshift_preflight/nodes/tasks/main.yml
@@ -1,7 +1,6 @@
 ---
 # determine if yum install of node pkgs will work
-- when:
-    - not containerized
+- when: not openshift.common.is_containerized | bool
   block:
 
     - name: main packages for enterprise
-- 
cgit v1.2.3


From c6febeb11af247f156218542f98f52566cf22931 Mon Sep 17 00:00:00 2001
From: Rodolfo Carvalho <rhcarvalho@gmail.com>
Date: Thu, 12 Jan 2017 19:16:03 +0100
Subject: Reduce code duplication using variable

We can use openshift_facts's service_type to determine the package
names.
---
 roles/openshift_preflight/masters/tasks/main.yml | 29 ++++++------------------
 roles/openshift_preflight/nodes/tasks/main.yml   | 29 ++++++------------------
 2 files changed, 14 insertions(+), 44 deletions(-)

diff --git a/roles/openshift_preflight/masters/tasks/main.yml b/roles/openshift_preflight/masters/tasks/main.yml
index 80d7af208..35fb1e3ca 100644
--- a/roles/openshift_preflight/masters/tasks/main.yml
+++ b/roles/openshift_preflight/masters/tasks/main.yml
@@ -3,33 +3,18 @@
 - when: not openshift.common.is_containerized | bool
   block:
 
-    - name: main packages for enterprise
-      when:
-        - deployment_type == "openshift-enterprise"
+    - name: main master packages availability
       check_yum_update:
         packages:
-          - atomic-openshift
-          - atomic-openshift-clients
-          - atomic-openshift-master
+          - "{{ openshift.common.service_type }}"
+          - "{{ openshift.common.service_type }}-clients"
+          - "{{ openshift.common.service_type }}-master"
       register: r
 
     - set_fact:
-        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'main packages for enterprise'})] }}"
+        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'main master packages availability'})] }}"
 
-    - name: main packages for origin
-      when:
-        - deployment_type == "origin"
-      check_yum_update:
-        packages:
-          - origin
-          - origin-clients
-          - origin-master
-      register: r
-
-    - set_fact:
-        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'main packages for origin'})] }}"
-
-    - name: other master packages
+    - name: other master packages availability
       check_yum_update:
         packages:
           - etcd
@@ -43,4 +28,4 @@
       register: r
 
     - set_fact:
-        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'other master packages'})] }}"
+        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'other master packages availability'})] }}"
diff --git a/roles/openshift_preflight/nodes/tasks/main.yml b/roles/openshift_preflight/nodes/tasks/main.yml
index c98b02bf5..a10e69024 100644
--- a/roles/openshift_preflight/nodes/tasks/main.yml
+++ b/roles/openshift_preflight/nodes/tasks/main.yml
@@ -3,33 +3,18 @@
 - when: not openshift.common.is_containerized | bool
   block:
 
-    - name: main packages for enterprise
-      when:
-        - deployment_type == "openshift-enterprise"
+    - name: main node packages availability
       check_yum_update:
         packages:
-          - atomic-openshift
-          - atomic-openshift-node
-          - atomic-openshift-sdn-ovs
+          - "{{ openshift.common.service_type }}"
+          - "{{ openshift.common.service_type }}-node"
+          - "{{ openshift.common.service_type }}-sdn-ovs"
       register: r
 
     - set_fact:
-        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'main packages for enterprise'})] }}"
+        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'main node packages availability'})] }}"
 
-    - name: main packages for origin
-      when:
-        - deployment_type == "origin"
-      check_yum_update:
-        packages:
-          - origin
-          - origin-node
-          - origin-sdn-ovs
-      register: r
-
-    - set_fact:
-        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'main packages for origin'})] }}"
-
-    - name: other node packages
+    - name: other node packages availability
       check_yum_update:
         packages:
           - docker
@@ -53,4 +38,4 @@
       register: r
 
     - set_fact:
-        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'other node packages'})] }}"
+        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'other node packages availability'})] }}"
-- 
cgit v1.2.3


From ac4db698d82d405f3b213f222ae22f62213af000 Mon Sep 17 00:00:00 2001
From: Rodolfo Carvalho <rhcarvalho@gmail.com>
Date: Thu, 12 Jan 2017 19:18:55 +0100
Subject: Fix inconsistent task name

---
 roles/openshift_preflight/common/tasks/main.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/roles/openshift_preflight/common/tasks/main.yml b/roles/openshift_preflight/common/tasks/main.yml
index bb54d0380..f1a4a160e 100644
--- a/roles/openshift_preflight/common/tasks/main.yml
+++ b/roles/openshift_preflight/common/tasks/main.yml
@@ -8,7 +8,7 @@
       register: r
 
     - set_fact:
-        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'check content available on all hosts'})] }}"
+        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'determine if yum update will work'})] }}"
 
     - name: determine if expected version matches what is available
       aos_version:
-- 
cgit v1.2.3


From 43b22a73c43d8975403994b5f085735e1d3786ba Mon Sep 17 00:00:00 2001
From: Rodolfo Carvalho <rhcarvalho@gmail.com>
Date: Fri, 13 Jan 2017 10:06:40 +0100
Subject: Move Python modules into role

Allow reuse via role dependency.
---
 library/aos_version.py                             | 100 ------------------
 library/check_yum_update.py                        | 116 ---------------------
 .../base/library/aos_version.py                    | 100 ++++++++++++++++++
 .../base/library/check_yum_update.py               | 116 +++++++++++++++++++++
 roles/openshift_preflight/common/library           |   1 -
 roles/openshift_preflight/common/meta/main.yml     |   3 +
 roles/openshift_preflight/masters/library          |   1 -
 roles/openshift_preflight/masters/meta/main.yml    |   3 +
 roles/openshift_preflight/nodes/library            |   1 -
 roles/openshift_preflight/nodes/meta/main.yml      |   3 +
 10 files changed, 225 insertions(+), 219 deletions(-)
 delete mode 100755 library/aos_version.py
 delete mode 100755 library/check_yum_update.py
 create mode 100755 roles/openshift_preflight/base/library/aos_version.py
 create mode 100755 roles/openshift_preflight/base/library/check_yum_update.py
 delete mode 120000 roles/openshift_preflight/common/library
 create mode 100644 roles/openshift_preflight/common/meta/main.yml
 delete mode 120000 roles/openshift_preflight/masters/library
 create mode 100644 roles/openshift_preflight/masters/meta/main.yml
 delete mode 120000 roles/openshift_preflight/nodes/library
 create mode 100644 roles/openshift_preflight/nodes/meta/main.yml

diff --git a/library/aos_version.py b/library/aos_version.py
deleted file mode 100755
index f7fcb6da5..000000000
--- a/library/aos_version.py
+++ /dev/null
@@ -1,100 +0,0 @@
-#!/usr/bin/python
-# vim: expandtab:tabstop=4:shiftwidth=4
-'''
-An ansible module for determining if more than one minor version
-of any atomic-openshift package is available, which would indicate
-that multiple repos are enabled for different versions of the same
-thing which may cause problems.
-
-Also, determine if the version requested is available down to the
-precision requested.
-'''
-
-# import os
-# import sys
-import yum  # pylint: disable=import-error
-from ansible.module_utils.basic import AnsibleModule
-
-
-def main():  # pylint: disable=missing-docstring
-    module = AnsibleModule(
-        argument_spec=dict(
-            version=dict(required=True)
-        ),
-        supports_check_mode=True
-    )
-
-    # NOTE(rhcarvalho): sosiouxme added _unmute, but I couldn't find a case yet
-    # for when it is actually necessary. Leaving it commented out for now,
-    # though this comment and the commented out code related to _unmute should
-    # be deleted later if not proven necessary.
-
-    # sys.stdout = os.devnull  # mute yum so it doesn't break our output
-    # sys.stderr = os.devnull  # mute yum so it doesn't break our output
-
-    # def _unmute():  # pylint: disable=missing-docstring
-    #     sys.stdout = sys.__stdout__
-
-    def bail(error):  # pylint: disable=missing-docstring
-        # _unmute()
-        module.fail_json(msg=error)
-
-    yb = yum.YumBase()  # pylint: disable=invalid-name
-
-    # search for package versions available for aos pkgs
-    expected_pkgs = [
-        'atomic-openshift',
-        'atomic-openshift-master',
-        'atomic-openshift-node',
-    ]
-    try:
-        pkgs = yb.pkgSack.returnPackages(patterns=expected_pkgs)
-    except yum.Errors.PackageSackError as e:  # pylint: disable=invalid-name
-        # you only hit this if *none* of the packages are available
-        bail('Unable to find any atomic-openshift packages. \nCheck your subscription and repo settings. \n%s' % e)
-
-    # determine what level of precision we're expecting for the version
-    expected_version = module.params['version']
-    if expected_version.startswith('v'):  # v3.3 => 3.3
-        expected_version = expected_version[1:]
-    num_dots = expected_version.count('.')
-
-    pkgs_by_name_version = {}
-    pkgs_precise_version_found = {}
-    for pkg in pkgs:
-        # get expected version precision
-        match_version = '.'.join(pkg.version.split('.')[:num_dots + 1])
-        if match_version == expected_version:
-            pkgs_precise_version_found[pkg.name] = True
-        # get x.y version precision
-        minor_version = '.'.join(pkg.version.split('.')[:2])
-        if pkg.name not in pkgs_by_name_version:
-            pkgs_by_name_version[pkg.name] = {}
-        pkgs_by_name_version[pkg.name][minor_version] = True
-
-    # see if any packages couldn't be found at requested version
-    # see if any packages are available in more than one minor version
-    not_found = []
-    multi_found = []
-    for name in expected_pkgs:
-        if name not in pkgs_precise_version_found:
-            not_found.append(name)
-        if name in pkgs_by_name_version and len(pkgs_by_name_version[name]) > 1:
-            multi_found.append(name)
-    if not_found:
-        msg = 'Not all of the required packages are available at requested version %s:\n' % expected_version
-        for name in not_found:
-            msg += '  %s\n' % name
-        bail(msg + 'Please check your subscriptions and enabled repositories.')
-    if multi_found:
-        msg = 'Multiple minor versions of these packages are available\n'
-        for name in multi_found:
-            msg += '  %s\n' % name
-        bail(msg + "There should only be one OpenShift version's repository enabled at a time.")
-
-    # _unmute()
-    module.exit_json(changed=False)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/library/check_yum_update.py b/library/check_yum_update.py
deleted file mode 100755
index 296ebd44f..000000000
--- a/library/check_yum_update.py
+++ /dev/null
@@ -1,116 +0,0 @@
-#!/usr/bin/python
-# vim: expandtab:tabstop=4:shiftwidth=4
-'''
-Ansible module to test whether a yum update or install will succeed,
-without actually performing it or running yum.
-parameters:
-  packages: (optional) A list of package names to install or update.
-            If omitted, all installed RPMs are considered for updates.
-'''
-
-# import os
-import sys
-import yum  # pylint: disable=import-error
-from ansible.module_utils.basic import AnsibleModule
-
-
-def main():  # pylint: disable=missing-docstring,too-many-branches
-    module = AnsibleModule(
-        argument_spec=dict(
-            packages=dict(type='list', default=[])
-        ),
-        supports_check_mode=True
-    )
-
-    # NOTE(rhcarvalho): sosiouxme added _unmute, but I couldn't find a case yet
-    # for when it is actually necessary. Leaving it commented out for now,
-    # though this comment and the commented out code related to _unmute should
-    # be deleted later if not proven necessary.
-
-    # sys.stdout = os.devnull  # mute yum so it doesn't break our output
-
-    # def _unmute():  # pylint: disable=missing-docstring
-    #     sys.stdout = sys.__stdout__
-
-    def bail(error):  # pylint: disable=missing-docstring
-        # _unmute()
-        module.fail_json(msg=error)
-
-    yb = yum.YumBase()  # pylint: disable=invalid-name
-    # determine if the existing yum configuration is valid
-    try:
-        yb.repos.populateSack(mdtype='metadata', cacheonly=1)
-    # for error of type:
-    #   1. can't reach the repo URL(s)
-    except yum.Errors.NoMoreMirrorsRepoError as e:  # pylint: disable=invalid-name
-        bail('Error getting data from at least one yum repository: %s' % e)
-    #   2. invalid repo definition
-    except yum.Errors.RepoError as e:  # pylint: disable=invalid-name
-        bail('Error with yum repository configuration: %s' % e)
-    #   3. other/unknown
-    #    * just report the problem verbatim
-    except:  # pylint: disable=bare-except; # noqa
-        bail('Unexpected error with yum repository: %s' % sys.exc_info()[1])
-
-    packages = module.params['packages']
-    no_such_pkg = []
-    for pkg in packages:
-        try:
-            yb.install(name=pkg)
-        except yum.Errors.InstallError as e:  # pylint: disable=invalid-name
-            no_such_pkg.append(pkg)
-        except:  # pylint: disable=bare-except; # noqa
-            bail('Unexpected error with yum install/update: %s' %
-                 sys.exc_info()[1])
-    if not packages:
-        # no packages requested means test a yum update of everything
-        yb.update()
-    elif no_such_pkg:
-        # wanted specific packages to install but some aren't available
-        user_msg = 'Cannot install all of the necessary packages. Unavailable:\n'
-        for pkg in no_such_pkg:
-            user_msg += '  %s\n' % pkg
-        user_msg += 'You may need to enable one or more yum repositories to make this content available.'
-        bail(user_msg)
-
-    try:
-        txn_result, txn_msgs = yb.buildTransaction()
-    except:  # pylint: disable=bare-except; # noqa
-        bail('Unexpected error during dependency resolution for yum update: \n %s' %
-             sys.exc_info()[1])
-
-    # find out if there are any errors with the update/install
-    if txn_result == 0:  # 'normal exit' meaning there's nothing to install/update
-        pass
-    elif txn_result == 1:  # error with transaction
-        user_msg = 'Could not perform a yum update.\n'
-        if len(txn_msgs) > 0:
-            user_msg += 'Errors from dependency resolution:\n'
-            for msg in txn_msgs:
-                user_msg += '  %s\n' % msg
-            user_msg += 'You should resolve these issues before proceeding with an install.\n'
-            user_msg += 'You may need to remove or downgrade packages or enable/disable yum repositories.'
-        bail(user_msg)
-    # TODO: it would be nice depending on the problem:
-    #   1. dependency for update not found
-    #    * construct the dependency tree
-    #    * find the installed package(s) that required the missing dep
-    #    * determine if any of these packages matter to openshift
-    #    * build helpful error output
-    #   2. conflicts among packages in available content
-    #    * analyze dependency tree and build helpful error output
-    #   3. other/unknown
-    #    * report the problem verbatim
-    #    * add to this list as we come across problems we can clearly diagnose
-    elif txn_result == 2:  # everything resolved fine
-        pass
-    else:
-        bail('Unknown error(s) from dependency resolution. Exit Code: %d:\n%s' %
-             (txn_result, txn_msgs))
-
-    # _unmute()
-    module.exit_json(changed=False)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/roles/openshift_preflight/base/library/aos_version.py b/roles/openshift_preflight/base/library/aos_version.py
new file mode 100755
index 000000000..f7fcb6da5
--- /dev/null
+++ b/roles/openshift_preflight/base/library/aos_version.py
@@ -0,0 +1,100 @@
+#!/usr/bin/python
+# vim: expandtab:tabstop=4:shiftwidth=4
+'''
+An ansible module for determining if more than one minor version
+of any atomic-openshift package is available, which would indicate
+that multiple repos are enabled for different versions of the same
+thing which may cause problems.
+
+Also, determine if the version requested is available down to the
+precision requested.
+'''
+
+# import os
+# import sys
+import yum  # pylint: disable=import-error
+from ansible.module_utils.basic import AnsibleModule
+
+
+def main():  # pylint: disable=missing-docstring
+    module = AnsibleModule(
+        argument_spec=dict(
+            version=dict(required=True)
+        ),
+        supports_check_mode=True
+    )
+
+    # NOTE(rhcarvalho): sosiouxme added _unmute, but I couldn't find a case yet
+    # for when it is actually necessary. Leaving it commented out for now,
+    # though this comment and the commented out code related to _unmute should
+    # be deleted later if not proven necessary.
+
+    # sys.stdout = os.devnull  # mute yum so it doesn't break our output
+    # sys.stderr = os.devnull  # mute yum so it doesn't break our output
+
+    # def _unmute():  # pylint: disable=missing-docstring
+    #     sys.stdout = sys.__stdout__
+
+    def bail(error):  # pylint: disable=missing-docstring
+        # _unmute()
+        module.fail_json(msg=error)
+
+    yb = yum.YumBase()  # pylint: disable=invalid-name
+
+    # search for package versions available for aos pkgs
+    expected_pkgs = [
+        'atomic-openshift',
+        'atomic-openshift-master',
+        'atomic-openshift-node',
+    ]
+    try:
+        pkgs = yb.pkgSack.returnPackages(patterns=expected_pkgs)
+    except yum.Errors.PackageSackError as e:  # pylint: disable=invalid-name
+        # you only hit this if *none* of the packages are available
+        bail('Unable to find any atomic-openshift packages. \nCheck your subscription and repo settings. \n%s' % e)
+
+    # determine what level of precision we're expecting for the version
+    expected_version = module.params['version']
+    if expected_version.startswith('v'):  # v3.3 => 3.3
+        expected_version = expected_version[1:]
+    num_dots = expected_version.count('.')
+
+    pkgs_by_name_version = {}
+    pkgs_precise_version_found = {}
+    for pkg in pkgs:
+        # get expected version precision
+        match_version = '.'.join(pkg.version.split('.')[:num_dots + 1])
+        if match_version == expected_version:
+            pkgs_precise_version_found[pkg.name] = True
+        # get x.y version precision
+        minor_version = '.'.join(pkg.version.split('.')[:2])
+        if pkg.name not in pkgs_by_name_version:
+            pkgs_by_name_version[pkg.name] = {}
+        pkgs_by_name_version[pkg.name][minor_version] = True
+
+    # see if any packages couldn't be found at requested version
+    # see if any packages are available in more than one minor version
+    not_found = []
+    multi_found = []
+    for name in expected_pkgs:
+        if name not in pkgs_precise_version_found:
+            not_found.append(name)
+        if name in pkgs_by_name_version and len(pkgs_by_name_version[name]) > 1:
+            multi_found.append(name)
+    if not_found:
+        msg = 'Not all of the required packages are available at requested version %s:\n' % expected_version
+        for name in not_found:
+            msg += '  %s\n' % name
+        bail(msg + 'Please check your subscriptions and enabled repositories.')
+    if multi_found:
+        msg = 'Multiple minor versions of these packages are available\n'
+        for name in multi_found:
+            msg += '  %s\n' % name
+        bail(msg + "There should only be one OpenShift version's repository enabled at a time.")
+
+    # _unmute()
+    module.exit_json(changed=False)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/openshift_preflight/base/library/check_yum_update.py b/roles/openshift_preflight/base/library/check_yum_update.py
new file mode 100755
index 000000000..296ebd44f
--- /dev/null
+++ b/roles/openshift_preflight/base/library/check_yum_update.py
@@ -0,0 +1,116 @@
+#!/usr/bin/python
+# vim: expandtab:tabstop=4:shiftwidth=4
+'''
+Ansible module to test whether a yum update or install will succeed,
+without actually performing it or running yum.
+parameters:
+  packages: (optional) A list of package names to install or update.
+            If omitted, all installed RPMs are considered for updates.
+'''
+
+# import os
+import sys
+import yum  # pylint: disable=import-error
+from ansible.module_utils.basic import AnsibleModule
+
+
+def main():  # pylint: disable=missing-docstring,too-many-branches
+    module = AnsibleModule(
+        argument_spec=dict(
+            packages=dict(type='list', default=[])
+        ),
+        supports_check_mode=True
+    )
+
+    # NOTE(rhcarvalho): sosiouxme added _unmute, but I couldn't find a case yet
+    # for when it is actually necessary. Leaving it commented out for now,
+    # though this comment and the commented out code related to _unmute should
+    # be deleted later if not proven necessary.
+
+    # sys.stdout = os.devnull  # mute yum so it doesn't break our output
+
+    # def _unmute():  # pylint: disable=missing-docstring
+    #     sys.stdout = sys.__stdout__
+
+    def bail(error):  # pylint: disable=missing-docstring
+        # _unmute()
+        module.fail_json(msg=error)
+
+    yb = yum.YumBase()  # pylint: disable=invalid-name
+    # determine if the existing yum configuration is valid
+    try:
+        yb.repos.populateSack(mdtype='metadata', cacheonly=1)
+    # for error of type:
+    #   1. can't reach the repo URL(s)
+    except yum.Errors.NoMoreMirrorsRepoError as e:  # pylint: disable=invalid-name
+        bail('Error getting data from at least one yum repository: %s' % e)
+    #   2. invalid repo definition
+    except yum.Errors.RepoError as e:  # pylint: disable=invalid-name
+        bail('Error with yum repository configuration: %s' % e)
+    #   3. other/unknown
+    #    * just report the problem verbatim
+    except:  # pylint: disable=bare-except; # noqa
+        bail('Unexpected error with yum repository: %s' % sys.exc_info()[1])
+
+    packages = module.params['packages']
+    no_such_pkg = []
+    for pkg in packages:
+        try:
+            yb.install(name=pkg)
+        except yum.Errors.InstallError as e:  # pylint: disable=invalid-name
+            no_such_pkg.append(pkg)
+        except:  # pylint: disable=bare-except; # noqa
+            bail('Unexpected error with yum install/update: %s' %
+                 sys.exc_info()[1])
+    if not packages:
+        # no packages requested means test a yum update of everything
+        yb.update()
+    elif no_such_pkg:
+        # wanted specific packages to install but some aren't available
+        user_msg = 'Cannot install all of the necessary packages. Unavailable:\n'
+        for pkg in no_such_pkg:
+            user_msg += '  %s\n' % pkg
+        user_msg += 'You may need to enable one or more yum repositories to make this content available.'
+        bail(user_msg)
+
+    try:
+        txn_result, txn_msgs = yb.buildTransaction()
+    except:  # pylint: disable=bare-except; # noqa
+        bail('Unexpected error during dependency resolution for yum update: \n %s' %
+             sys.exc_info()[1])
+
+    # find out if there are any errors with the update/install
+    if txn_result == 0:  # 'normal exit' meaning there's nothing to install/update
+        pass
+    elif txn_result == 1:  # error with transaction
+        user_msg = 'Could not perform a yum update.\n'
+        if len(txn_msgs) > 0:
+            user_msg += 'Errors from dependency resolution:\n'
+            for msg in txn_msgs:
+                user_msg += '  %s\n' % msg
+            user_msg += 'You should resolve these issues before proceeding with an install.\n'
+            user_msg += 'You may need to remove or downgrade packages or enable/disable yum repositories.'
+        bail(user_msg)
+    # TODO: it would be nice depending on the problem:
+    #   1. dependency for update not found
+    #    * construct the dependency tree
+    #    * find the installed package(s) that required the missing dep
+    #    * determine if any of these packages matter to openshift
+    #    * build helpful error output
+    #   2. conflicts among packages in available content
+    #    * analyze dependency tree and build helpful error output
+    #   3. other/unknown
+    #    * report the problem verbatim
+    #    * add to this list as we come across problems we can clearly diagnose
+    elif txn_result == 2:  # everything resolved fine
+        pass
+    else:
+        bail('Unknown error(s) from dependency resolution. Exit Code: %d:\n%s' %
+             (txn_result, txn_msgs))
+
+    # _unmute()
+    module.exit_json(changed=False)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/openshift_preflight/common/library b/roles/openshift_preflight/common/library
deleted file mode 120000
index ba40d2f56..000000000
--- a/roles/openshift_preflight/common/library
+++ /dev/null
@@ -1 +0,0 @@
-../../../library
\ No newline at end of file
diff --git a/roles/openshift_preflight/common/meta/main.yml b/roles/openshift_preflight/common/meta/main.yml
new file mode 100644
index 000000000..6f23cbf3b
--- /dev/null
+++ b/roles/openshift_preflight/common/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+  - role: openshift_preflight/base
diff --git a/roles/openshift_preflight/masters/library b/roles/openshift_preflight/masters/library
deleted file mode 120000
index ba40d2f56..000000000
--- a/roles/openshift_preflight/masters/library
+++ /dev/null
@@ -1 +0,0 @@
-../../../library
\ No newline at end of file
diff --git a/roles/openshift_preflight/masters/meta/main.yml b/roles/openshift_preflight/masters/meta/main.yml
new file mode 100644
index 000000000..6f23cbf3b
--- /dev/null
+++ b/roles/openshift_preflight/masters/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+  - role: openshift_preflight/base
diff --git a/roles/openshift_preflight/nodes/library b/roles/openshift_preflight/nodes/library
deleted file mode 120000
index ba40d2f56..000000000
--- a/roles/openshift_preflight/nodes/library
+++ /dev/null
@@ -1 +0,0 @@
-../../../library
\ No newline at end of file
diff --git a/roles/openshift_preflight/nodes/meta/main.yml b/roles/openshift_preflight/nodes/meta/main.yml
new file mode 100644
index 000000000..6f23cbf3b
--- /dev/null
+++ b/roles/openshift_preflight/nodes/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+  - role: openshift_preflight/base
-- 
cgit v1.2.3


From 01046c33e0bdcaf44dbd9bfcdf84f1a9a335437c Mon Sep 17 00:00:00 2001
From: Rodolfo Carvalho <rhcarvalho@gmail.com>
Date: Fri, 13 Jan 2017 17:06:57 +0100
Subject: Rename subrole facts -> init

Trying to improve the name, `init` needs to be loaded before calling other
subroles.

We don't make `init` a dependency of `common`, `masters` and `nodes` to
avoid running the relatively slow `openshift_facts` multiple times.
---
 playbooks/byo/openshift-preflight/check.yml    | 2 +-
 roles/openshift_preflight/README.md            | 3 +--
 roles/openshift_preflight/facts/meta/main.yml  | 3 ---
 roles/openshift_preflight/facts/tasks/main.yml | 4 ----
 roles/openshift_preflight/init/meta/main.yml   | 3 +++
 roles/openshift_preflight/init/tasks/main.yml  | 4 ++++
 6 files changed, 9 insertions(+), 10 deletions(-)
 delete mode 100644 roles/openshift_preflight/facts/meta/main.yml
 delete mode 100644 roles/openshift_preflight/facts/tasks/main.yml
 create mode 100644 roles/openshift_preflight/init/meta/main.yml
 create mode 100644 roles/openshift_preflight/init/tasks/main.yml

diff --git a/playbooks/byo/openshift-preflight/check.yml b/playbooks/byo/openshift-preflight/check.yml
index 94f53eb84..32673d01d 100644
--- a/playbooks/byo/openshift-preflight/check.yml
+++ b/playbooks/byo/openshift-preflight/check.yml
@@ -1,7 +1,7 @@
 ---
 - hosts: OSEv3
   roles:
-    - openshift_preflight/facts
+    - openshift_preflight/init
 
 - hosts: OSEv3
   name: checks that apply to all hosts
diff --git a/roles/openshift_preflight/README.md b/roles/openshift_preflight/README.md
index a3d734e32..b6d3542d3 100644
--- a/roles/openshift_preflight/README.md
+++ b/roles/openshift_preflight/README.md
@@ -24,9 +24,8 @@ Example Playbook
 ```yaml
 ---
 - hosts: OSEv3
-  gather_facts: no
   roles:
-    - openshift_preflight/facts
+    - openshift_preflight/init
 
 - hosts: OSEv3
   name: checks that apply to all hosts
diff --git a/roles/openshift_preflight/facts/meta/main.yml b/roles/openshift_preflight/facts/meta/main.yml
deleted file mode 100644
index 0bbeadd34..000000000
--- a/roles/openshift_preflight/facts/meta/main.yml
+++ /dev/null
@@ -1,3 +0,0 @@
----
-dependencies:
-  - role: openshift_facts
diff --git a/roles/openshift_preflight/facts/tasks/main.yml b/roles/openshift_preflight/facts/tasks/main.yml
deleted file mode 100644
index bf2d82196..000000000
--- a/roles/openshift_preflight/facts/tasks/main.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- name: set common variables
-  set_fact:
-    oo_preflight_check_results: "{{ oo_preflight_check_results | default([]) }}"
diff --git a/roles/openshift_preflight/init/meta/main.yml b/roles/openshift_preflight/init/meta/main.yml
new file mode 100644
index 000000000..0bbeadd34
--- /dev/null
+++ b/roles/openshift_preflight/init/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+  - role: openshift_facts
diff --git a/roles/openshift_preflight/init/tasks/main.yml b/roles/openshift_preflight/init/tasks/main.yml
new file mode 100644
index 000000000..bf2d82196
--- /dev/null
+++ b/roles/openshift_preflight/init/tasks/main.yml
@@ -0,0 +1,4 @@
+---
+- name: set common variables
+  set_fact:
+    oo_preflight_check_results: "{{ oo_preflight_check_results | default([]) }}"
-- 
cgit v1.2.3