We use an actual YAML parser this time around, and bring
the behavior more in line with what Ansible is doing, so
interoperability should be more solid overall.
New in this implementation is more flexibility in defining
host lists, including support for explicit lists as well
as glob patterns.
Signed-off-by: Andrea Bolognani <abologna(a)redhat.com>
---
guests/lcitool | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 87 insertions(+)
diff --git a/guests/lcitool b/guests/lcitool
index 6d4010b..3564eb8 100755
--- a/guests/lcitool
+++ b/guests/lcitool
@@ -19,11 +19,13 @@
import argparse
import crypt
+import fnmatch
import os
import random
import string
import sys
import textwrap
+import yaml
# This is necessary to maintain Python 2.7 compatibility
try:
@@ -139,10 +141,95 @@ class Config:
return root_hash_file
+class Inventory:
+
+ def __init__(self):
+ try:
+ parser = configparser.SafeConfigParser()
+ parser.read("./ansible.cfg")
+ inventory_path = parser.get("defaults", "inventory")
+ except:
+ raise Error("Can't find inventory location in ansible.cfg")
+
+ self._facts = {}
+ try:
+ # We can only deal with trivial inventories, but that's
+ # all we need right now and we can expand support further
+ # later on if necessary
+ with open(inventory_path, "r") as f:
+ for line in f:
+ host = line.strip()
+ self._facts[host] = {}
+ except:
+ raise Error(
+ "Missing or invalid inventory ({})".format(
+ inventory_path,
+ )
+ )
+
+ for host in self._facts:
+ try:
+ self._facts[host] = self._read_all_facts(host)
+ self._facts[host]["inventory_hostname"] = host
+ except:
+ raise Error("Can't load facts for
'{}'".format(host))
+
+ def _add_facts_from_file(self, facts, yaml_path):
+ with open(yaml_path, "r") as f:
+ some_facts = yaml.load(f)
+ for fact in some_facts:
+ facts[fact] = some_facts[fact]
+
+ def _read_all_facts(self, host):
+ facts = {}
+
+ # We load from group_vars/ first and host_vars/ second, sorting
+ # files alphabetically; doing so should result in our view of
+ # the facts matching Ansible's
+ for source in ["./group_vars/all/",
"./host_vars/{}/".format(host)]:
+ for item in sorted(os.listdir(source)):
+ yaml_path = os.path.join(source, item)
+ if not os.path.isfile(yaml_path):
+ continue
+ if not yaml_path.endswith(".yml"):
+ continue
+ self._add_facts_from_file(facts, yaml_path)
+
+ return facts
+
+ def expand_pattern(self, pattern):
+ if pattern == None:
+ raise Error("Missing host list")
+
+ if pattern == "all":
+ pattern = "*"
+
+ # This works correctly for single hosts as well as more complex
+ # cases such as explicit lists, glob patterns and any combination
+ # of the above
+ matches = []
+ for partial_pattern in pattern.split(","):
+
+ partial_matches = []
+ for host in self._facts:
+ if fnmatch.fnmatch(host, partial_pattern):
+ partial_matches += [host]
+
+ if len(partial_matches) == 0:
+ raise Error("Invalid host list '{}'".format(pattern))
+
+ matches += partial_matches
+
+ return list(set(matches))
+
+ def get_facts(self, host):
+ return self._facts[host]
+
class Application:
def __init__(self):
self._config = Config()
+ self._inventory = Inventory()
self._parser = argparse.ArgumentParser(
conflict_handler = "resolve",
--
2.17.1