Merge inventory from beta branch.
This commit is contained in:
		
					parent
					
						
							
								2b6177c9f7
							
						
					
				
			
			
				commit
				
					
						e2a1519332
					
				
			
		
					 28 changed files with 1769 additions and 36 deletions
				
			
		
							
								
								
									
										390
									
								
								cdist/inventory.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										390
									
								
								cdist/inventory.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,390 @@
 | 
			
		|||
#!/usr/bin/env python3
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# 2016 Darko Poljak (darko.poljak at gmail.com)
 | 
			
		||||
#
 | 
			
		||||
# This file is part of cdist.
 | 
			
		||||
#
 | 
			
		||||
# cdist is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# cdist is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with cdist. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
#
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
import cdist
 | 
			
		||||
import logging
 | 
			
		||||
import os
 | 
			
		||||
import os.path
 | 
			
		||||
import itertools
 | 
			
		||||
import sys
 | 
			
		||||
from cdist.hostsource import hostfile_process_line
 | 
			
		||||
 | 
			
		||||
DIST_INVENTORY_DB_NAME = "inventory"
 | 
			
		||||
 | 
			
		||||
dist_inventory_db = os.path.abspath(os.path.join(
 | 
			
		||||
    os.path.dirname(cdist.__file__), DIST_INVENTORY_DB_NAME))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def determine_default_inventory_dir(args):
 | 
			
		||||
    # The order of inventory dir setting by decreasing priority
 | 
			
		||||
    # 1. inventory_dir argument
 | 
			
		||||
    # 2. CDIST_INVENTORY_DIR env var if set
 | 
			
		||||
    # 3. ~/.cdist/inventory if HOME env var is set
 | 
			
		||||
    # 4. distribution inventory directory
 | 
			
		||||
    if not args.inventory_dir:
 | 
			
		||||
        if 'CDIST_INVENTORY_DIR' in os.environ:
 | 
			
		||||
            args.inventory_dir = os.environ['CDIST_INVENTORY_DIR']
 | 
			
		||||
        else:
 | 
			
		||||
            home = cdist.home_dir()
 | 
			
		||||
            if home:
 | 
			
		||||
                args.inventory_dir = os.path.join(home, DIST_INVENTORY_DB_NAME)
 | 
			
		||||
            else:
 | 
			
		||||
                args.inventory_dir = dist_inventory_db
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def contains_all(big, little):
 | 
			
		||||
    """Return True if big contains all elements from little,
 | 
			
		||||
       False otherwise.
 | 
			
		||||
    """
 | 
			
		||||
    return set(little).issubset(set(big))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def contains_any(big, little):
 | 
			
		||||
    """Return True if big contains any element from little,
 | 
			
		||||
       False otherwise.
 | 
			
		||||
    """
 | 
			
		||||
    for x in little:
 | 
			
		||||
        if x in big:
 | 
			
		||||
            return True
 | 
			
		||||
    return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def check_always_true(x, y):
 | 
			
		||||
    return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def rstrip_nl(s):
 | 
			
		||||
    '''str.rstrip "\n" from s'''
 | 
			
		||||
    return str.rstrip(s, "\n")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Inventory(object):
 | 
			
		||||
    """Inventory main class"""
 | 
			
		||||
 | 
			
		||||
    def __init__(self, db_basedir=dist_inventory_db):
 | 
			
		||||
        self.db_basedir = db_basedir
 | 
			
		||||
        self.log = logging.getLogger("inventory")
 | 
			
		||||
        self.init_db()
 | 
			
		||||
 | 
			
		||||
    def init_db(self):
 | 
			
		||||
        self.log.debug("Init db: {}".format(self.db_basedir))
 | 
			
		||||
        if not os.path.exists(self.db_basedir):
 | 
			
		||||
            os.makedirs(self.db_basedir, exist_ok=True)
 | 
			
		||||
        elif not os.path.isdir(self.db_basedir):
 | 
			
		||||
            raise cdist.Error(("Invalid inventory db basedir \'{}\',"
 | 
			
		||||
                               " must be a directory").format(self.db_basedir))
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def strlist_to_list(slist):
 | 
			
		||||
        if slist:
 | 
			
		||||
            result = [x for x in slist.split(',') if x]
 | 
			
		||||
        else:
 | 
			
		||||
            result = []
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
    def _input_values(self, source):
 | 
			
		||||
        """Yield input values from source.
 | 
			
		||||
           Source can be a sequence or filename (stdin if '-').
 | 
			
		||||
           In case of filename each line represents one input value.
 | 
			
		||||
        """
 | 
			
		||||
        if isinstance(source, str):
 | 
			
		||||
            import fileinput
 | 
			
		||||
            try:
 | 
			
		||||
                with fileinput.FileInput(files=(source)) as f:
 | 
			
		||||
                    for x in f:
 | 
			
		||||
                        result = hostfile_process_line(x, strip_func=rstrip_nl)
 | 
			
		||||
                        if result:
 | 
			
		||||
                            yield result
 | 
			
		||||
            except (IOError, OSError) as e:
 | 
			
		||||
                raise cdist.Error("Error reading from \'{}\'".format(
 | 
			
		||||
                    source))
 | 
			
		||||
        else:
 | 
			
		||||
            if source:
 | 
			
		||||
                for x in source:
 | 
			
		||||
                    if x:
 | 
			
		||||
                        yield x
 | 
			
		||||
 | 
			
		||||
    def _host_path(self, host):
 | 
			
		||||
        hostpath = os.path.join(self.db_basedir, host)
 | 
			
		||||
        return hostpath
 | 
			
		||||
 | 
			
		||||
    def _all_hosts(self):
 | 
			
		||||
        return os.listdir(self.db_basedir)
 | 
			
		||||
 | 
			
		||||
    def _check_host(self, hostpath):
 | 
			
		||||
        if not os.path.exists(hostpath):
 | 
			
		||||
            return False
 | 
			
		||||
        else:
 | 
			
		||||
            if not os.path.isfile(hostpath):
 | 
			
		||||
                raise cdist.Error(("Host path \'{}\' exists, but is not"
 | 
			
		||||
                                   " a valid file").format(hostpath))
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def _read_host_tags(self, hostpath):
 | 
			
		||||
        result = set()
 | 
			
		||||
        with open(hostpath, "rt") as f:
 | 
			
		||||
            for tag in f:
 | 
			
		||||
                tag = tag.rstrip("\n")
 | 
			
		||||
                if tag:
 | 
			
		||||
                    result.add(tag)
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
    def _get_host_tags(self, host):
 | 
			
		||||
        hostpath = self._host_path(host)
 | 
			
		||||
        if self._check_host(hostpath):
 | 
			
		||||
            return self._read_host_tags(hostpath)
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def _write_host_tags(self, host, tags):
 | 
			
		||||
        hostpath = self._host_path(host)
 | 
			
		||||
        if self._check_host(hostpath):
 | 
			
		||||
            with open(hostpath, "wt") as f:
 | 
			
		||||
                for tag in tags:
 | 
			
		||||
                    f.write("{}\n".format(tag))
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def commandline(cls, args):
 | 
			
		||||
        """Manipulate inventory db"""
 | 
			
		||||
        log = logging.getLogger("cdist")
 | 
			
		||||
        if 'taglist' in args:
 | 
			
		||||
            args.taglist = cls.strlist_to_list(args.taglist)
 | 
			
		||||
        determine_default_inventory_dir(args)
 | 
			
		||||
 | 
			
		||||
        log.info("Using inventory: {}".format(args.inventory_dir))
 | 
			
		||||
        log.debug("Inventory args: {}".format(vars(args)))
 | 
			
		||||
        log.debug("Inventory command: {}".format(args.subcommand))
 | 
			
		||||
 | 
			
		||||
        if args.subcommand == "list":
 | 
			
		||||
            c = InventoryList(hosts=args.host, istag=args.tag,
 | 
			
		||||
                              hostfile=args.hostfile,
 | 
			
		||||
                              db_basedir=args.inventory_dir,
 | 
			
		||||
                              list_only_host=args.list_only_host,
 | 
			
		||||
                              has_all_tags=args.has_all_tags)
 | 
			
		||||
        elif args.subcommand == "add-host":
 | 
			
		||||
            c = InventoryHost(hosts=args.host, hostfile=args.hostfile,
 | 
			
		||||
                              db_basedir=args.inventory_dir)
 | 
			
		||||
        elif args.subcommand == "del-host":
 | 
			
		||||
            c = InventoryHost(hosts=args.host, hostfile=args.hostfile,
 | 
			
		||||
                              all=args.all, db_basedir=args.inventory_dir,
 | 
			
		||||
                              action="del")
 | 
			
		||||
        elif args.subcommand == "add-tag":
 | 
			
		||||
            c = InventoryTag(hosts=args.host, tags=args.taglist,
 | 
			
		||||
                             hostfile=args.hostfile, tagfile=args.tagfile,
 | 
			
		||||
                             db_basedir=args.inventory_dir)
 | 
			
		||||
        elif args.subcommand == "del-tag":
 | 
			
		||||
            c = InventoryTag(hosts=args.host, tags=args.taglist,
 | 
			
		||||
                             hostfile=args.hostfile, tagfile=args.tagfile,
 | 
			
		||||
                             all=args.all, db_basedir=args.inventory_dir,
 | 
			
		||||
                             action="del")
 | 
			
		||||
        else:
 | 
			
		||||
            raise cdist.Error("Unknown inventory command \'{}\'".format(
 | 
			
		||||
                        args.subcommand))
 | 
			
		||||
        c.run()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InventoryList(Inventory):
 | 
			
		||||
    def __init__(self, hosts=None, istag=False, hostfile=None,
 | 
			
		||||
                 list_only_host=False, has_all_tags=False,
 | 
			
		||||
                 db_basedir=dist_inventory_db):
 | 
			
		||||
        super().__init__(db_basedir)
 | 
			
		||||
        self.hosts = hosts
 | 
			
		||||
        self.istag = istag
 | 
			
		||||
        self.hostfile = hostfile
 | 
			
		||||
        self.list_only_host = list_only_host
 | 
			
		||||
        self.has_all_tags = has_all_tags
 | 
			
		||||
 | 
			
		||||
    def _print(self, host, tags):
 | 
			
		||||
        if self.list_only_host:
 | 
			
		||||
            print("{}".format(host))
 | 
			
		||||
        else:
 | 
			
		||||
            print("{} {}".format(host, ",".join(sorted(tags))))
 | 
			
		||||
 | 
			
		||||
    def _do_list(self, it_tags, it_hosts, check_func):
 | 
			
		||||
        if (it_tags is not None):
 | 
			
		||||
            param_tags = set(it_tags)
 | 
			
		||||
            self.log.debug("param_tags: {}".format(param_tags))
 | 
			
		||||
        else:
 | 
			
		||||
            param_tags = set()
 | 
			
		||||
        for host in it_hosts:
 | 
			
		||||
            self.log.debug("host: {}".format(host))
 | 
			
		||||
            tags = self._get_host_tags(host)
 | 
			
		||||
            if tags is None:
 | 
			
		||||
                self.log.info("Host \'{}\' not found, skipped".format(host))
 | 
			
		||||
                continue
 | 
			
		||||
            self.log.debug("tags: {}".format(tags))
 | 
			
		||||
            if check_func(tags, param_tags):
 | 
			
		||||
                yield host, tags
 | 
			
		||||
 | 
			
		||||
    def entries(self):
 | 
			
		||||
        if not self.hosts and not self.hostfile:
 | 
			
		||||
            self.log.info("Listing all hosts")
 | 
			
		||||
            it_hosts = self._all_hosts()
 | 
			
		||||
            it_tags = None
 | 
			
		||||
            check_func = check_always_true
 | 
			
		||||
        else:
 | 
			
		||||
            it = itertools.chain(self._input_values(self.hosts),
 | 
			
		||||
                                 self._input_values(self.hostfile))
 | 
			
		||||
            if self.istag:
 | 
			
		||||
                self.log.info("Listing by tag(s)")
 | 
			
		||||
                it_hosts = self._all_hosts()
 | 
			
		||||
                it_tags = it
 | 
			
		||||
                if self.has_all_tags:
 | 
			
		||||
                    check_func = contains_all
 | 
			
		||||
                else:
 | 
			
		||||
                    check_func = contains_any
 | 
			
		||||
            else:
 | 
			
		||||
                self.log.info("Listing by host(s)")
 | 
			
		||||
                it_hosts = it
 | 
			
		||||
                it_tags = None
 | 
			
		||||
                check_func = check_always_true
 | 
			
		||||
        for host, tags in self._do_list(it_tags, it_hosts, check_func):
 | 
			
		||||
            yield host, tags
 | 
			
		||||
 | 
			
		||||
    def host_entries(self):
 | 
			
		||||
        for host, tags in self.entries():
 | 
			
		||||
            yield host
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        for host, tags in self.entries():
 | 
			
		||||
            self._print(host, tags)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InventoryHost(Inventory):
 | 
			
		||||
    def __init__(self, hosts=None, hostfile=None,
 | 
			
		||||
                 db_basedir=dist_inventory_db, all=False, action="add"):
 | 
			
		||||
        super().__init__(db_basedir)
 | 
			
		||||
        self.actions = ("add", "del")
 | 
			
		||||
        if action not in self.actions:
 | 
			
		||||
            raise cdist.Error("Invalid action \'{}\', valid actions are:"
 | 
			
		||||
                              " {}\n".format(action, self.actions.keys()))
 | 
			
		||||
        self.action = action
 | 
			
		||||
        self.hosts = hosts
 | 
			
		||||
        self.hostfile = hostfile
 | 
			
		||||
        self.all = all
 | 
			
		||||
 | 
			
		||||
        if not self.hosts and not self.hostfile:
 | 
			
		||||
            self.hostfile = "-"
 | 
			
		||||
 | 
			
		||||
    def _new_hostpath(self, hostpath):
 | 
			
		||||
        # create empty file
 | 
			
		||||
        with open(hostpath, "w"):
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    def _action(self, host):
 | 
			
		||||
        if self.action == "add":
 | 
			
		||||
            self.log.info("Adding host \'{}\'".format(host))
 | 
			
		||||
        elif self.action == "del":
 | 
			
		||||
            self.log.info("Deleting host \'{}\'".format(host))
 | 
			
		||||
        hostpath = self._host_path(host)
 | 
			
		||||
        self.log.debug("hostpath: {}".format(hostpath))
 | 
			
		||||
        if self.action == "add" and not os.path.exists(hostpath):
 | 
			
		||||
                self._new_hostpath(hostpath)
 | 
			
		||||
        else:
 | 
			
		||||
            if not os.path.isfile(hostpath):
 | 
			
		||||
                raise cdist.Error(("Host path \'{}\' is"
 | 
			
		||||
                                   " not a valid file").format(hostpath))
 | 
			
		||||
            if self.action == "del":
 | 
			
		||||
                os.remove(hostpath)
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        if self.action == "del" and self.all:
 | 
			
		||||
            self.log.debug("Doing for all hosts")
 | 
			
		||||
            it = self._all_hosts()
 | 
			
		||||
        else:
 | 
			
		||||
            self.log.debug("Doing for specified hosts")
 | 
			
		||||
            it = itertools.chain(self._input_values(self.hosts),
 | 
			
		||||
                                 self._input_values(self.hostfile))
 | 
			
		||||
        for host in it:
 | 
			
		||||
            self._action(host)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InventoryTag(Inventory):
 | 
			
		||||
    def __init__(self, hosts=None, tags=None, hostfile=None, tagfile=None,
 | 
			
		||||
                 db_basedir=dist_inventory_db, all=False, action="add"):
 | 
			
		||||
        super().__init__(db_basedir)
 | 
			
		||||
        self.actions = ("add", "del")
 | 
			
		||||
        if action not in self.actions:
 | 
			
		||||
            raise cdist.Error("Invalid action \'{}\', valid actions are:"
 | 
			
		||||
                              " {}\n".format(action, self.actions.keys()))
 | 
			
		||||
        self.action = action
 | 
			
		||||
        self.hosts = hosts
 | 
			
		||||
        self.tags = tags
 | 
			
		||||
        self.hostfile = hostfile
 | 
			
		||||
        self.tagfile = tagfile
 | 
			
		||||
        self.all = all
 | 
			
		||||
 | 
			
		||||
        if not self.hosts and not self.hostfile:
 | 
			
		||||
            self.allhosts = True
 | 
			
		||||
        else:
 | 
			
		||||
            self.allhosts = False
 | 
			
		||||
        if not self.tags and not self.tagfile:
 | 
			
		||||
            self.tagfile = "-"
 | 
			
		||||
 | 
			
		||||
        if self.hostfile == "-" and self.tagfile == "-":
 | 
			
		||||
            raise cdist.Error("Cannot read both, hosts and tags, from stdin")
 | 
			
		||||
 | 
			
		||||
    def _read_input_tags(self):
 | 
			
		||||
        self.input_tags = set()
 | 
			
		||||
        for tag in itertools.chain(self._input_values(self.tags),
 | 
			
		||||
                                   self._input_values(self.tagfile)):
 | 
			
		||||
            self.input_tags.add(tag)
 | 
			
		||||
 | 
			
		||||
    def _action(self, host):
 | 
			
		||||
        host_tags = self._get_host_tags(host)
 | 
			
		||||
        if host_tags is None:
 | 
			
		||||
            print("Host \'{}\' does not exist, skipping".format(host),
 | 
			
		||||
                  file=sys.stderr)
 | 
			
		||||
            return
 | 
			
		||||
        self.log.debug("existing host_tags: {}".format(host_tags))
 | 
			
		||||
        if self.action == "del" and self.all:
 | 
			
		||||
            host_tags = set()
 | 
			
		||||
        else:
 | 
			
		||||
            for tag in self.input_tags:
 | 
			
		||||
                if self.action == "add":
 | 
			
		||||
                    self.log.info("Adding tag \'{}\' for host \'{}\'".format(
 | 
			
		||||
                        tag, host))
 | 
			
		||||
                    host_tags.add(tag)
 | 
			
		||||
                elif self.action == "del":
 | 
			
		||||
                    self.log.info("Deleting tag \'{}\' for host \'{}\'".format(
 | 
			
		||||
                        tag, host))
 | 
			
		||||
                    if tag in host_tags:
 | 
			
		||||
                        host_tags.remove(tag)
 | 
			
		||||
        self.log.debug("new host tags: {}".format(host_tags))
 | 
			
		||||
        if not self._write_host_tags(host, host_tags):
 | 
			
		||||
            self.log.info("{} does not exist, skipped".format(host))
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        if self.allhosts:
 | 
			
		||||
            self.log.debug("Doing for all hosts")
 | 
			
		||||
            it = self._all_hosts()
 | 
			
		||||
        else:
 | 
			
		||||
            self.log.debug("Doing for specified hosts")
 | 
			
		||||
            it = itertools.chain(self._input_values(self.hosts),
 | 
			
		||||
                                 self._input_values(self.hostfile))
 | 
			
		||||
        if not(self.action == "del" and self.all):
 | 
			
		||||
            self._read_input_tags()
 | 
			
		||||
        for host in it:
 | 
			
		||||
            self._action(host)
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue