On Thu, Jul 12, 2018 at 05:19:21PM +0200, Andrea Bolognani wrote:
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 | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 91 insertions(+)
diff --git a/guests/lcitool b/guests/lcitool
index e03b388..e90a33b 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
Since you have to install yaml to actually use it, I suggest at this
point to create a requirements.txt file for this script, so that users
don't have to go over the code to check the dependencies.
# This is necessary to maintain Python 2.7 compatibility
try:
@@ -44,6 +46,32 @@ class Util:
salt = "".join(random.choice(alphabeth) for x in range(0, 16))
return "$6${}$".format(salt)
+ @staticmethod
+ def expand_pattern(pattern, source, name):
+ if pattern == None:
+ raise Error("Missing {} list".format(name))
+
+ if pattern == "all":
+ pattern = "*"
+
+ # This works correctly for single items 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 item in source:
+ if fnmatch.fnmatch(item, partial_pattern):
+ partial_matches += [item]
+
+ if len(partial_matches) == 0:
+ raise Error("Invalid {} list '{}'".format(name,
pattern))
+
+ matches += partial_matches
+
+ return sorted(set(matches))
+
class Config:
def _get_config_file(self, name):
@@ -142,10 +170,73 @@ 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):
+ return Util.expand_pattern(pattern, self._facts, "host")
+
+ 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
--
libvir-list mailing list
libvir-list(a)redhat.com
https://www.redhat.com/mailman/listinfo/libvir-list