From adb4a6b6f46f22c3020cb9bf8d464af4cf35e508 Mon Sep 17 00:00:00 2001
From: Andrei Stepanov <astepano@redhat.com>
Date: Apr 03 2018 12:32:26 +0000
Subject: Rewrite merge-standard-inventory with ansible-inventory


Signed-off-by: Andrei Stepanov <astepano@redhat.com>

---

diff --git a/scripts/merge-standard-inventory b/scripts/merge-standard-inventory
index 6e3d5be..6993683 100755
--- a/scripts/merge-standard-inventory
+++ b/scripts/merge-standard-inventory
@@ -1,208 +1,14 @@
-#!/usr/bin/env python
+#!/bin/bash
 
-import json
-import os
-import sys
-import subprocess
-import time
+# CI pipeline specifies:
+#
+# export ANSIBLE_INVENTORY=$(test -e inventory && echo inventory || echo /usr/share/ansible/inventory)
+#
+# CI pipeline directly uses: /usr/share/ansible/inventory
+#
+# or you can provide `inventory` file with invoking `merge_standard_inventories`
+#
 
+TEST_DYNAMIC_INVENTORY_DIRECTORY="${TEST_DYNAMIC_INVENTORY_DIRECTORY:-/usr/share/ansible/inventory}"
 
-def main(argv):
-    """
-    Run all standard inventory scripts and return their merged output.
-
-    Note the standard inventory scripts clean up their spawned hosts
-    when they detect their parent processes go away. To accomodate
-    that behavior, this script forks a child process to run the
-    inventory scripts, send back the merged inventory, and then wait
-    for the parent of this script (ansible) to die before silently
-    exiting. In the mean time, this script outputs the merged
-    inventory gathered by the child and then exits.
-    """
-
-    # Keep track of our parent
-    waitpid = os.getppid()
-
-    tty = err_to_tty()
-    pipein, pipeout = os.pipe()
-
-    childpid = os.fork()
-    if childpid == 0:
-        # this is the child process
-
-        # close the inherited input side of the pipe
-        os.close(pipein)
-
-        # run and merge output from standard inventory scripts
-        merged_data = merge_standard_inventories(argv[1:])
-
-        # send merged data to parent via output pipe
-        os.write(pipeout, merged_data.encode('utf-8'))
-
-        # close the pipe so the parent knows we are done
-        os.close(pipeout)
-
-        # wait for the grandparent process to exit
-        linger(waitpid, tty)
-
-        # exit cleanly
-        sys.exit(0)
-
-    # this is the parent process
-
-    # close the inherited output side of the pipe
-    os.close(pipeout)
-
-    # send eveything from the child to stdout
-    while True:
-        data = os.read(pipein, 999)
-        if not data:
-            os.close(pipein)
-            break
-        sys.stdout.write(data.decode('utf-8'))
-
-    return 0
-
-
-def merge_standard_inventories(args):
-
-    inventory_dir = os.environ.get(
-        "TEST_DYNAMIC_INVENTORY_DIRECTORY", "/usr/share/ansible/inventory")
-
-    ignore_ext_string = os.environ.get(
-        "ANSIBLE_INVENTORY_IGNORE", "~, .orig, .bak, .ini, .cfg, .retry, .pyc, .pyo")
-    ignore_ext_list = []
-    for s in ignore_ext_string.split(','):
-        if s.strip():
-            ignore_ext_list.append(s.strip())
-    inventory_ignore_extensions = tuple(ignore_ext_list)
-
-    merged = Inventory()
-
-    for i in os.listdir(inventory_dir):
-        ipath = os.path.join(inventory_dir, i)
-        if not i.startswith("standard-inventory-"):
-            continue
-        if i.endswith(inventory_ignore_extensions):
-            continue
-        if not os.access(ipath, os.X_OK):
-            continue
-
-        cmd = [ipath] + args
-
-        try:
-            inv_out = subprocess.check_output(cmd, stdin=None, close_fds=True)
-        except subprocess.CalledProcessError:
-            raise RuntimeError("Could not run: {0}".format(str(cmd)))
-
-        merged.merge(inv_out.decode('utf-8'))
-
-    return merged.dumps()
-
-
-def err_to_tty():
-    try:
-        tty = os.open("/dev/tty", os.O_WRONLY)
-        os.dup2(tty, 2)
-    except OSError:
-        tty = None
-
-    return tty
-
-
-def linger(waitpid, tty=None):
-    # Go into daemon mode and watch the process
-
-    null = open(os.devnull, 'w')
-
-    os.chdir("/")
-    os.setsid()
-    os.umask(0)
-
-    if tty is None:
-        tty = null.fileno()
-
-    # Duplicate standard input to standard output and standard error.
-    os.dup2(null.fileno(), 0)
-    os.dup2(tty, 1)
-    os.dup2(tty, 2)
-
-    # Now wait for the watched process to go away, then return
-    while True:
-        time.sleep(3)
-
-        try:
-            os.kill(waitpid, 0)
-        except OSError:
-            break  # The process no longer exists
-
-    return
-
-
-class Inventory:
-    """
-    Merge JSON data from standard test dynamic inventory scripts.
-
-    Note: This class is very specific to the JSON data written by the
-    ansible dynamic inventory scripts that are provided by the
-    standard-test-roles package. In particular, it insists on finding
-    and generating "subjects" and "localhost" members.
-    """
-
-    def __init__(self):
-        self.hosts = []
-        self.variables = {}
-
-    def merge_files(self, files):
-        for f in files:
-            with open(f) as ifile:
-                s = ifile.read()
-                self.merge(s)
-
-    def merge(self, s):
-        # parse provided string as JSON
-        inventory = json.loads(s)
-
-        if not isinstance(inventory, dict):
-            raise ValueError(
-                "inventory JSON does not contain the expected top level dictionary")
-
-        if "subjects" not in inventory or not isinstance(inventory["subjects"], dict):
-            raise ValueError(
-                "inventory JSON does not contain the expected [subjects] dictionary")
-        if "hosts" not in inventory["subjects"] or not isinstance(inventory["subjects"]["hosts"], list):
-            raise ValueError(
-                "inventory JSON does not contain the expected [subjects][hosts] list")
-
-        for h in inventory["subjects"]["hosts"]:
-            self.hosts.append(h)
-
-        if "_meta" not in inventory or not isinstance(inventory["_meta"], dict):
-            raise ValueError(
-                "inventory JSON does not contain the expected [_meta] dictionary")
-        if "hostvars" not in inventory["_meta"] or not isinstance(inventory["_meta"]["hostvars"], dict):
-            raise ValueError(
-                "inventory JSON does not contain the expected [_meta][hostvars] dict")
-
-        for h in inventory["_meta"]["hostvars"]:
-            if not isinstance(inventory["_meta"]["hostvars"][h], dict):
-                raise ValueError(
-                    "inventory JSON does not contain the expected [_meta][hostvars][{0}] dict".format(h))
-
-            self.variables[h] = inventory["_meta"]["hostvars"][h]
-
-        if "localhost" not in inventory or not isinstance(inventory["localhost"], dict):
-            raise ValueError(
-                "inventory JSON does not contain the expected [localhost] dictionary")
-        if "hosts" not in inventory["localhost"] or not isinstance(inventory["localhost"]["hosts"], list):
-            raise ValueError(
-                "inventory JSON does not contain the expected [localhost][hosts] list")
-
-    def dumps(self):
-        data = {"subjects": {"hosts": self.hosts, "vars": {}}, "localhost": {
-            "hosts": self.hosts, "vars": {}}, "_meta": {"hostvars": self.variables}}
-        return json.dumps(data, indent=4, separators=(',', ': '))
-
-
-if __name__ == '__main__':
-    sys.exit(main(sys.argv))
+exec ansible-inventory --inventory="$TEST_DYNAMIC_INVENTORY_DIRECTORY" "$@"

