Refactored inventory to make it object oriented, need to make YAML format and executable script
format compatible with this still, and add some tests for INI-style groups of groups and variables.
This commit is contained in:
parent
39e791a1d3
commit
0669ce858a
6 changed files with 284 additions and 382 deletions
|
@ -18,310 +18,103 @@
|
|||
#############################################
|
||||
|
||||
import fnmatch
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import constants as C
|
||||
from ansible.inventory_parser import InventoryParser
|
||||
from ansible.group import Group
|
||||
from ansible.host import Host
|
||||
from ansible import errors
|
||||
from ansible import utils
|
||||
|
||||
class Inventory(object):
|
||||
""" Host inventory for ansible.
|
||||
|
||||
The inventory is either a simple text file with systems and [groups] of
|
||||
systems, or a script that will be called with --list or --host.
|
||||
"""
|
||||
Host inventory for ansible.
|
||||
"""
|
||||
|
||||
def __init__(self, host_list=C.DEFAULT_HOST_LIST, global_host_vars={}):
|
||||
def __init__(self, host_list=C.DEFAULT_HOST_LIST):
|
||||
|
||||
# FIXME: re-support YAML inventory format
|
||||
# FIXME: re-support external inventory script (?)
|
||||
|
||||
self.groups = []
|
||||
self._restriction = None
|
||||
self._variables = {}
|
||||
self._global_host_vars = global_host_vars # lets us pass in a global var list
|
||||
# that each host gets
|
||||
# after we have set up the inventory
|
||||
# to get all group variables
|
||||
|
||||
if host_list:
|
||||
if type(host_list) == list:
|
||||
self.host_list = host_list
|
||||
self.groups = dict(ungrouped=host_list)
|
||||
self._is_script = False
|
||||
return
|
||||
|
||||
inventory_file = os.path.expanduser(host_list)
|
||||
if not os.path.exists(inventory_file):
|
||||
raise errors.AnsibleFileNotFound("inventory file not found: %s" % host_list)
|
||||
|
||||
self.inventory_file = os.path.abspath(inventory_file)
|
||||
|
||||
if os.access(self.inventory_file, os.X_OK):
|
||||
self.host_list, self.groups = self._parse_from_script()
|
||||
self._is_script = True
|
||||
self.groups = self._groups_from_override_hosts(host_list)
|
||||
else:
|
||||
self.host_list, self.groups = self._parse_from_file()
|
||||
self._is_script = False
|
||||
self.parser = InventoryParser(filename=host_list)
|
||||
self.groups = self.parser.groups.values()
|
||||
|
||||
# *****************************************************
|
||||
# Public API
|
||||
def _groups_from_override_hosts(self, list):
|
||||
# support for playbook's --override-hosts only
|
||||
all = Group(name='all')
|
||||
for h in list:
|
||||
all.add_host(Host(name=h))
|
||||
return dict(all=all)
|
||||
|
||||
def _match(self, str, pattern_str):
|
||||
return fnmatch.fnmatch(str, pattern_str)
|
||||
|
||||
def get_hosts(self, pattern="all"):
|
||||
""" Get all host objects matching the pattern """
|
||||
hosts = {}
|
||||
patterns = pattern.replace(";",":").split(":")
|
||||
|
||||
for group in self.get_groups():
|
||||
for host in group.get_hosts():
|
||||
for pat in patterns:
|
||||
if group.name == pat or pat == 'all' or self._match(host.name, pat):
|
||||
if not self._restriction:
|
||||
hosts[host.name] = host
|
||||
if self._restriction and host.name in self._restriction:
|
||||
hosts[host.name] = host
|
||||
return sorted(hosts.values(), key=lambda x: x.name)
|
||||
|
||||
def get_groups(self):
|
||||
return self.groups
|
||||
|
||||
def get_host(self, hostname):
|
||||
for group in self.groups:
|
||||
for host in group.get_hosts():
|
||||
if hostname == host.name:
|
||||
return host
|
||||
return None
|
||||
|
||||
def get_group(self, groupname):
|
||||
for group in self.groups:
|
||||
if group.name == groupname:
|
||||
return group
|
||||
return None
|
||||
|
||||
def get_group_variables(self, groupname):
|
||||
group = self.get_group(groupname)
|
||||
if group is None:
|
||||
raise Exception("group not found: %s" % groupname)
|
||||
return group.get_variables()
|
||||
|
||||
def get_variables(self, hostname):
|
||||
host = self.get_host(hostname)
|
||||
if host is None:
|
||||
raise Exception("host not found: %s" % hostname)
|
||||
return host.get_variables()
|
||||
|
||||
def add_group(self, group):
|
||||
self.groups.append(group)
|
||||
|
||||
def list_hosts(self, pattern="all"):
|
||||
""" Return a list of hosts [matching the pattern] """
|
||||
if self._restriction is None:
|
||||
host_list = self.host_list
|
||||
else:
|
||||
host_list = [ h for h in self.host_list if h in self._restriction ]
|
||||
return [ h for h in host_list if self._matches(h, pattern) ]
|
||||
""" DEPRECATED: Get all host names matching the pattern """
|
||||
return [ h.name for h in self.get_hosts(pattern) ]
|
||||
|
||||
def list_groups(self):
|
||||
return [ g.name for g in self.groups ]
|
||||
|
||||
def restrict_to(self, restriction):
|
||||
""" Restrict list operations to the hosts given in restriction """
|
||||
if type(restriction) != list:
|
||||
restriction = [ restriction ]
|
||||
|
||||
self._restriction = restriction
|
||||
|
||||
def lift_restriction(self):
|
||||
""" Do not restrict list operations """
|
||||
self._restriction = None
|
||||
|
||||
def get_variables(self, host):
|
||||
""" Return the variables associated with this host. """
|
||||
|
||||
variables = {
|
||||
'inventory_hostname': host,
|
||||
}
|
||||
|
||||
if host in self._variables:
|
||||
variables.update(self._variables[host].copy())
|
||||
|
||||
if self._is_script:
|
||||
variables.update(self._get_variables_from_script(host))
|
||||
|
||||
variables['group_names'] = []
|
||||
for name,hosts in self.groups.iteritems():
|
||||
if host in hosts:
|
||||
variables['group_names'].append(name)
|
||||
|
||||
return variables
|
||||
|
||||
def get_global_vars(self):
|
||||
return self._global_host_vars
|
||||
|
||||
# *****************************************************
|
||||
|
||||
def _parse_from_file(self):
|
||||
''' parse a textual host file '''
|
||||
|
||||
results = []
|
||||
groups = dict(ungrouped=[])
|
||||
lines = file(self.inventory_file).read().split("\n")
|
||||
if "---" in lines:
|
||||
return self._parse_yaml()
|
||||
group_name = 'ungrouped'
|
||||
for item in lines:
|
||||
item = item.lstrip().rstrip()
|
||||
if item.startswith("#"):
|
||||
# ignore commented out lines
|
||||
pass
|
||||
elif item.startswith("["):
|
||||
# looks like a group
|
||||
group_name = item.replace("[","").replace("]","").lstrip().rstrip()
|
||||
groups[group_name] = []
|
||||
elif item != "":
|
||||
# looks like a regular host
|
||||
if ":" in item:
|
||||
# a port was specified
|
||||
item, port = item.split(":")
|
||||
try:
|
||||
port = int(port)
|
||||
except ValueError:
|
||||
raise errors.AnsibleError("SSH port for %s in inventory (%s) should be numerical."%(item, port))
|
||||
self._set_variable(item, "ansible_ssh_port", port)
|
||||
groups[group_name].append(item)
|
||||
if not item in results:
|
||||
results.append(item)
|
||||
return (results, groups)
|
||||
|
||||
# *****************************************************
|
||||
|
||||
def _parse_from_script(self):
|
||||
''' evaluate a script that returns list of hosts by groups '''
|
||||
|
||||
results = []
|
||||
groups = dict(ungrouped=[])
|
||||
|
||||
cmd = [self.inventory_file, '--list']
|
||||
|
||||
try:
|
||||
cmd = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False)
|
||||
out, err = cmd.communicate()
|
||||
except Exception, e:
|
||||
raise errors.AnsibleError("Failure executing %s to produce host list:\n %s" % (self.inventory_file, str(e)))
|
||||
|
||||
rc = cmd.returncode
|
||||
if rc:
|
||||
raise errors.AnsibleError("%s: %s" % self.inventory_file, err)
|
||||
|
||||
try:
|
||||
groups = utils.json_loads(out)
|
||||
except:
|
||||
raise errors.AnsibleError("invalid JSON response from script: %s" % self.inventory_file)
|
||||
|
||||
for (groupname, hostlist) in groups.iteritems():
|
||||
for host in hostlist:
|
||||
if host not in results:
|
||||
results.append(host)
|
||||
return (results, groups)
|
||||
|
||||
# *****************************************************
|
||||
|
||||
def _parse_yaml(self):
|
||||
""" Load the inventory from a yaml file.
|
||||
|
||||
returns hosts and groups"""
|
||||
data = utils.parse_yaml_from_file(self.inventory_file)
|
||||
|
||||
if type(data) != list:
|
||||
raise errors.AnsibleError("YAML inventory should be a list.")
|
||||
|
||||
hosts = []
|
||||
groups = {}
|
||||
ungrouped = []
|
||||
|
||||
|
||||
# go through once and grab up the global_host_vars from the 'all' group
|
||||
for item in data:
|
||||
if type(item) == dict:
|
||||
if "group" in item and 'all' == item["group"]:
|
||||
if "vars" in item:
|
||||
variables = item['vars']
|
||||
if type(variables) == list:
|
||||
for variable in variables:
|
||||
if len(variable) != 1:
|
||||
raise errors.AnsibleError("Only one item expected in %s"%(variable))
|
||||
k, v = variable.items()[0]
|
||||
self._global_host_vars[k] = utils.template(v, self._global_host_vars, {})
|
||||
elif type(variables) == dict:
|
||||
self._global_host_vars.update(variables)
|
||||
|
||||
for item in data:
|
||||
if type(item) == dict:
|
||||
if "group" in item:
|
||||
group_name = item["group"]
|
||||
|
||||
group_vars = []
|
||||
if "vars" in item:
|
||||
group_vars.extend(item["vars"])
|
||||
|
||||
group_hosts = []
|
||||
if "hosts" in item:
|
||||
for host in item["hosts"]:
|
||||
host_name = self._parse_yaml_host(host, group_vars)
|
||||
group_hosts.append(host_name)
|
||||
|
||||
groups[group_name] = group_hosts
|
||||
hosts.extend(group_hosts)
|
||||
|
||||
elif "host" in item:
|
||||
host_name = self._parse_yaml_host(item)
|
||||
hosts.append(host_name)
|
||||
ungrouped.append(host_name)
|
||||
else:
|
||||
host_name = self._parse_yaml_host(item)
|
||||
hosts.append(host_name)
|
||||
ungrouped.append(host_name)
|
||||
|
||||
# filter duplicate hosts
|
||||
output_hosts = []
|
||||
for host in hosts:
|
||||
if host not in output_hosts:
|
||||
output_hosts.append(host)
|
||||
|
||||
if len(ungrouped) > 0 :
|
||||
# hosts can be defined top-level, but also in a group
|
||||
really_ungrouped = []
|
||||
for host in ungrouped:
|
||||
already_grouped = False
|
||||
for name, group_hosts in groups.items():
|
||||
if host in group_hosts:
|
||||
already_grouped = True
|
||||
if not already_grouped:
|
||||
really_ungrouped.append(host)
|
||||
groups["ungrouped"] = really_ungrouped
|
||||
|
||||
return output_hosts, groups
|
||||
|
||||
def _parse_yaml_host(self, item, variables=[]):
|
||||
def set_variables(host, variables):
|
||||
if type(variables) == list:
|
||||
for variable in variables:
|
||||
if len(variable) != 1:
|
||||
raise errors.AnsibleError("Only one item expected in %s"%(variable))
|
||||
k, v = variable.items()[0]
|
||||
self._set_variable(host, k, v)
|
||||
elif type(variables) == dict:
|
||||
for k, v in variables.iteritems():
|
||||
self._set_variable(host, k, v)
|
||||
|
||||
|
||||
if type(item) in [str, unicode]:
|
||||
set_variables(item, variables)
|
||||
return item
|
||||
elif type(item) == dict:
|
||||
if "host" in item:
|
||||
host_name = item["host"]
|
||||
set_variables(host_name, variables)
|
||||
|
||||
if "vars" in item:
|
||||
set_variables(host_name, item["vars"])
|
||||
|
||||
return host_name
|
||||
else:
|
||||
raise errors.AnsibleError("Unknown item in inventory: %s"%(item))
|
||||
|
||||
|
||||
def _get_variables_from_script(self, host):
|
||||
''' support per system variabes from external variable scripts, see web docs '''
|
||||
|
||||
cmd = [self.inventory_file, '--host', host]
|
||||
|
||||
cmd = subprocess.Popen(cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
shell=False
|
||||
)
|
||||
out, err = cmd.communicate()
|
||||
|
||||
variables = {}
|
||||
try:
|
||||
variables = utils.json_loads(out)
|
||||
except:
|
||||
raise errors.AnsibleError("%s returned invalid result when called with hostname %s" % (
|
||||
self.inventory_file,
|
||||
host
|
||||
))
|
||||
return variables
|
||||
|
||||
def _set_variable(self, host, key, value):
|
||||
if not host in self._variables:
|
||||
self._variables[host] = {}
|
||||
self._variables[host][key] = value
|
||||
|
||||
def _matches(self, host_name, pattern):
|
||||
''' returns if a hostname is matched by the pattern '''
|
||||
|
||||
# a pattern is in fnmatch format but more than one pattern
|
||||
# can be strung together with semicolons. ex:
|
||||
# atlanta-web*.example.com;dc-web*.example.com
|
||||
|
||||
if host_name == '':
|
||||
return False
|
||||
pattern = pattern.replace(";",":")
|
||||
subpatterns = pattern.split(":")
|
||||
for subpattern in subpatterns:
|
||||
if subpattern == 'all':
|
||||
return True
|
||||
if fnmatch.fnmatch(host_name, subpattern):
|
||||
return True
|
||||
elif subpattern in self.groups:
|
||||
if host_name in self.groups[subpattern]:
|
||||
return True
|
||||
return False
|
||||
|
|
145
lib/ansible/inventory_parser.py
Normal file
145
lib/ansible/inventory_parser.py
Normal file
|
@ -0,0 +1,145 @@
|
|||
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible 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.
|
||||
#
|
||||
# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#############################################
|
||||
|
||||
import fnmatch
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import constants as C
|
||||
from ansible.host import Host
|
||||
from ansible.group import Group
|
||||
from ansible import errors
|
||||
from ansible import utils
|
||||
|
||||
class InventoryParser(object):
|
||||
"""
|
||||
Host inventory for ansible.
|
||||
"""
|
||||
|
||||
def __init__(self, filename=C.DEFAULT_HOST_LIST):
|
||||
|
||||
fh = open(filename)
|
||||
self.lines = fh.readlines()
|
||||
self.groups = {}
|
||||
self._parse()
|
||||
|
||||
def _parse(self):
|
||||
|
||||
self._parse_base_groups()
|
||||
self._parse_group_children()
|
||||
self._parse_group_variables()
|
||||
return self.groups
|
||||
|
||||
# [webservers]
|
||||
# alpha
|
||||
# beta:2345
|
||||
# gamma sudo=True user=root
|
||||
# delta asdf=jkl favcolor=red
|
||||
|
||||
def _parse_base_groups(self):
|
||||
|
||||
undefined = Group(name='undefined')
|
||||
all = Group(name='all')
|
||||
all.add_child_group(undefined)
|
||||
|
||||
self.groups = dict(all=all, undefined=undefined)
|
||||
active_group_name = 'undefined'
|
||||
|
||||
for line in self.lines:
|
||||
if line.startswith("["):
|
||||
active_group_name = line.replace("[","").replace("]","").strip()
|
||||
if line.find(":vars") != -1 or line.find(":children") != -1:
|
||||
active_group_name = None
|
||||
else:
|
||||
new_group = self.groups[active_group_name] = Group(name=active_group_name)
|
||||
all.add_child_group(new_group)
|
||||
elif line.startswith("#") or line == '':
|
||||
pass
|
||||
elif active_group_name:
|
||||
tokens = line.split()
|
||||
if len(tokens) == 0:
|
||||
continue
|
||||
hostname = tokens[0]
|
||||
port = C.DEFAULT_REMOTE_PORT
|
||||
if hostname.find(":") != -1:
|
||||
tokens2 = hostname.split(":")
|
||||
hostname = tokens2[0]
|
||||
port = tokens2[1]
|
||||
host = Host(name=hostname, port=port)
|
||||
if len(tokens) > 1:
|
||||
for t in tokens[1:]:
|
||||
(k,v) = t.split("=")
|
||||
host.set_variable(k,v)
|
||||
self.groups[active_group_name].add_host(host)
|
||||
|
||||
# [southeast:children]
|
||||
# atlanta
|
||||
# raleigh
|
||||
|
||||
def _parse_group_children(self):
|
||||
group = None
|
||||
|
||||
for line in self.lines:
|
||||
line = line.strip()
|
||||
if line is None or line == '':
|
||||
continue
|
||||
if line.startswith("[") and line.find(":children]") != -1:
|
||||
line = line.replace("[","").replace(":children]","")
|
||||
group = self.groups.get(line, None)
|
||||
if group is None:
|
||||
group = self.groups[line] = Group(name=line)
|
||||
elif line.startswith("#"):
|
||||
pass
|
||||
elif line.startswith("["):
|
||||
group = None
|
||||
elif group:
|
||||
kid_group = self.groups.get(line, None)
|
||||
if kid_group is None:
|
||||
raise errors.AnsibleError("child group is not defined: (%s)" % line)
|
||||
else:
|
||||
group.add_child_group(kid_group)
|
||||
|
||||
|
||||
# [webservers:vars]
|
||||
# http_port=1234
|
||||
# maxRequestsPerChild=200
|
||||
|
||||
def _parse_group_variables(self):
|
||||
group = None
|
||||
for line in self.lines:
|
||||
line = line.strip()
|
||||
if line.startswith("[") and line.find(":vars]") != -1:
|
||||
line = line.replace("[","").replace(":vars]","")
|
||||
group = self.groups.get(line, None)
|
||||
if group is None:
|
||||
raise errors.AnsibleError("can't add vars to undefined group: %s" % line)
|
||||
elif line.startswith("#"):
|
||||
pass
|
||||
elif line.startswith("["):
|
||||
group = None
|
||||
elif line == '':
|
||||
pass
|
||||
elif group:
|
||||
if line.find("=") == -1:
|
||||
raise errors.AnsibleError("variables assigned to group must be in key=value form")
|
||||
else:
|
||||
(k,v) = line.split("=")
|
||||
group.set_variable(k,v)
|
||||
|
||||
|
|
@ -108,12 +108,12 @@ class PlayBook(object):
|
|||
if override_hosts is not None:
|
||||
if type(override_hosts) != list:
|
||||
raise errors.AnsibleError("override hosts must be a list")
|
||||
self.global_vars.update(ansible.inventory.Inventory(host_list).get_global_vars())
|
||||
self.global_vars.update(ansible.inventory.Inventory(host_list).get_group_variables('all'))
|
||||
self.inventory = ansible.inventory.Inventory(override_hosts)
|
||||
|
||||
else:
|
||||
self.inventory = ansible.inventory.Inventory(host_list)
|
||||
self.global_vars.update(ansible.inventory.Inventory(host_list).get_global_vars())
|
||||
self.global_vars.update(ansible.inventory.Inventory(host_list).get_group_variables('all'))
|
||||
|
||||
self.basedir = os.path.dirname(playbook)
|
||||
self.playbook = self._parse_playbook(playbook)
|
||||
|
|
|
@ -156,19 +156,6 @@ class Runner(object):
|
|||
|
||||
# *****************************************************
|
||||
|
||||
@classmethod
|
||||
def parse_hosts(cls, host_list, override_hosts=None):
|
||||
''' parse the host inventory file, returns (hosts, groups) '''
|
||||
|
||||
if override_hosts is None:
|
||||
inventory = ansible.inventory.Inventory(host_list)
|
||||
else:
|
||||
inventory = ansible.inventory.Inventory(override_hosts)
|
||||
|
||||
return inventory.host_list, inventory.groups
|
||||
|
||||
# *****************************************************
|
||||
|
||||
def _return_from_module(self, conn, host, result, err, executed=None):
|
||||
''' helper function to handle JSON parsing of results '''
|
||||
|
||||
|
|
85
library/file
85
library/file
|
@ -17,10 +17,6 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
import os
|
||||
import sys
|
||||
import shlex
|
||||
|
@ -35,21 +31,18 @@ try:
|
|||
except ImportError:
|
||||
HAVE_SELINUX=False
|
||||
|
||||
def debug(msg):
|
||||
# ansible ignores stderr, so it's safe to use for debug
|
||||
# print >>sys.stderr, msg
|
||||
pass
|
||||
def dump_kv(vars):
|
||||
return " ".join("%s=%s" % (k,v) for (k,v) in vars.items())
|
||||
|
||||
def exit_json(rc=0, **kwargs):
|
||||
def exit_kv(rc=0, **kwargs):
|
||||
if 'path' in kwargs:
|
||||
debug("adding path info")
|
||||
add_path_info(kwargs)
|
||||
print json.dumps(kwargs)
|
||||
print dump_kv(kwargs)
|
||||
sys.exit(rc)
|
||||
|
||||
def fail_json(**kwargs):
|
||||
def fail_kv(**kwargs):
|
||||
kwargs['failed'] = True
|
||||
exit_json(rc=1, **kwargs)
|
||||
exit_kv(rc=1, **kwargs)
|
||||
|
||||
def add_path_info(kwargs):
|
||||
path = kwargs['path']
|
||||
|
@ -81,20 +74,16 @@ def selinux_mls_enabled():
|
|||
if not HAVE_SELINUX:
|
||||
return False
|
||||
if selinux.is_selinux_mls_enabled() == 1:
|
||||
debug('selinux mls is enabled')
|
||||
return True
|
||||
else:
|
||||
debug('selinux mls is disabled')
|
||||
return False
|
||||
|
||||
def selinux_enabled():
|
||||
if not HAVE_SELINUX:
|
||||
return False
|
||||
if selinux.is_selinux_enabled() == 1:
|
||||
debug('selinux is enabled')
|
||||
return True
|
||||
else:
|
||||
debug('selinux is disabled')
|
||||
return False
|
||||
|
||||
# Determine whether we need a placeholder for selevel/mls
|
||||
|
@ -112,13 +101,10 @@ def selinux_default_context(path, mode=0):
|
|||
try:
|
||||
ret = selinux.matchpathcon(path, mode)
|
||||
except OSError:
|
||||
debug("no default context available")
|
||||
return context
|
||||
if ret[0] == -1:
|
||||
debug("no default context available")
|
||||
return context
|
||||
context = ret[1].split(':')
|
||||
debug("got default secontext=%s" % ret[1])
|
||||
return context
|
||||
|
||||
def selinux_context(path):
|
||||
|
@ -128,11 +114,10 @@ def selinux_context(path):
|
|||
try:
|
||||
ret = selinux.lgetfilecon(path)
|
||||
except:
|
||||
fail_json(path=path, msg='failed to retrieve selinux context')
|
||||
fail_kv(path=path, msg='failed to retrieve selinux context')
|
||||
if ret[0] == -1:
|
||||
return context
|
||||
context = ret[1].split(':')
|
||||
debug("got current secontext=%s" % ret[1])
|
||||
return context
|
||||
|
||||
# ===========================================
|
||||
|
@ -142,7 +127,7 @@ args = open(argfile, 'r').read()
|
|||
items = shlex.split(args)
|
||||
|
||||
if not len(items):
|
||||
fail_json(msg='the module requires arguments -a')
|
||||
fail_kv(msg='the module requires arguments -a')
|
||||
sys.exit(1)
|
||||
|
||||
params = {}
|
||||
|
@ -180,12 +165,12 @@ for i in range(len(default_secontext)):
|
|||
secontext[i] = default_secontext[i]
|
||||
|
||||
if state not in [ 'file', 'directory', 'link', 'absent']:
|
||||
fail_json(msg='invalid state: %s' % state)
|
||||
fail_kv(msg='invalid state: %s' % state)
|
||||
|
||||
if state == 'link' and (src is None or dest is None):
|
||||
fail_json(msg='src and dest are required for "link" state')
|
||||
fail_kv(msg='src and dest are required for "link" state')
|
||||
elif path is None:
|
||||
fail_json(msg='path is required')
|
||||
fail_kv(msg='path is required')
|
||||
|
||||
changed = False
|
||||
|
||||
|
@ -201,7 +186,6 @@ def user_and_group(filename):
|
|||
gid = st.st_gid
|
||||
user = pwd.getpwuid(uid)[0]
|
||||
group = grp.getgrgid(gid)[0]
|
||||
debug("got user=%s and group=%s" % (user, group))
|
||||
return (user, group)
|
||||
|
||||
def set_context_if_different(path, context, changed):
|
||||
|
@ -209,59 +193,52 @@ def set_context_if_different(path, context, changed):
|
|||
return changed
|
||||
cur_context = selinux_context(path)
|
||||
new_context = list(cur_context)
|
||||
debug("current secontext is %s" % ':'.join(cur_context))
|
||||
# Iterate over the current context instead of the
|
||||
# argument context, which may have selevel.
|
||||
for i in range(len(cur_context)):
|
||||
if context[i] is not None and context[i] != cur_context[i]:
|
||||
new_context[i] = context[i]
|
||||
debug("new secontext is %s" % ':'.join(new_context))
|
||||
if cur_context != new_context:
|
||||
try:
|
||||
rc = selinux.lsetfilecon(path, ':'.join(new_context))
|
||||
except OSError:
|
||||
fail_json(path=path, msg='invalid selinux context')
|
||||
fail_kv(path=path, msg='invalid selinux context')
|
||||
if rc != 0:
|
||||
fail_json(path=path, msg='set selinux context failed')
|
||||
fail_kv(path=path, msg='set selinux context failed')
|
||||
changed = True
|
||||
return changed
|
||||
|
||||
def set_owner_if_different(path, owner, changed):
|
||||
if owner is None:
|
||||
debug('not tweaking owner')
|
||||
return changed
|
||||
user, group = user_and_group(path)
|
||||
if owner != user:
|
||||
debug('setting owner')
|
||||
rc = os.system("/bin/chown -R %s %s" % (owner, path))
|
||||
if rc != 0:
|
||||
fail_json(path=path, msg='chown failed')
|
||||
fail_kv(path=path, msg='chown failed')
|
||||
return True
|
||||
|
||||
return changed
|
||||
|
||||
def set_group_if_different(path, group, changed):
|
||||
if group is None:
|
||||
debug('not tweaking group')
|
||||
return changed
|
||||
old_user, old_group = user_and_group(path)
|
||||
if old_group != group:
|
||||
debug('setting group')
|
||||
rc = os.system("/bin/chgrp -R %s %s" % (group, path))
|
||||
if rc != 0:
|
||||
fail_json(path=path, msg='chgrp failed')
|
||||
fail_kv(path=path, msg='chgrp failed')
|
||||
return True
|
||||
return changed
|
||||
|
||||
def set_mode_if_different(path, mode, changed):
|
||||
if mode is None:
|
||||
debug('not tweaking mode')
|
||||
return changed
|
||||
try:
|
||||
# FIXME: support English modes
|
||||
mode = int(mode, 8)
|
||||
except Exception, e:
|
||||
fail_json(path=path, msg='mode needs to be something octalish', details=str(e))
|
||||
fail_kv(path=path, msg='mode needs to be something octalish', details=str(e))
|
||||
|
||||
st = os.stat(path)
|
||||
prev_mode = stat.S_IMODE(st[stat.ST_MODE])
|
||||
|
@ -270,10 +247,9 @@ def set_mode_if_different(path, mode, changed):
|
|||
# FIXME: comparison against string above will cause this to be executed
|
||||
# every time
|
||||
try:
|
||||
debug('setting mode')
|
||||
os.chmod(path, mode)
|
||||
except Exception, e:
|
||||
fail_json(path=path, msg='chmod failed', details=str(e))
|
||||
fail_kv(path=path, msg='chmod failed', details=str(e))
|
||||
|
||||
st = os.stat(path)
|
||||
new_mode = stat.S_IMODE(st[stat.ST_MODE])
|
||||
|
@ -284,7 +260,7 @@ def set_mode_if_different(path, mode, changed):
|
|||
|
||||
|
||||
def rmtree_error(func, path, exc_info):
|
||||
fail_json(path=path, msg='failed to remove directory')
|
||||
fail_kv(path=path, msg='failed to remove directory')
|
||||
|
||||
# ===========================================
|
||||
# go...
|
||||
|
@ -299,7 +275,6 @@ if os.path.lexists(path):
|
|||
prev_state = 'directory'
|
||||
|
||||
if prev_state != 'absent' and state == 'absent':
|
||||
debug('requesting absent')
|
||||
try:
|
||||
if prev_state == 'directory':
|
||||
if os.path.islink(path):
|
||||
|
@ -309,21 +284,20 @@ if prev_state != 'absent' and state == 'absent':
|
|||
else:
|
||||
os.unlink(path)
|
||||
except Exception, e:
|
||||
fail_json(path=path, msg=str(e))
|
||||
exit_json(path=path, changed=True)
|
||||
fail_kv(path=path, msg=str(e))
|
||||
exit_kv(path=path, changed=True)
|
||||
sys.exit(0)
|
||||
|
||||
if prev_state != 'absent' and prev_state != state:
|
||||
fail_json(path=path, msg='refusing to convert between %s and %s' % (prev_state, state))
|
||||
fail_kv(path=path, msg='refusing to convert between %s and %s' % (prev_state, state))
|
||||
|
||||
if prev_state == 'absent' and state == 'absent':
|
||||
exit_json(path=path, changed=False)
|
||||
exit_kv(path=path, changed=False)
|
||||
|
||||
if state == 'file':
|
||||
|
||||
debug('requesting file')
|
||||
if prev_state == 'absent':
|
||||
fail_json(path=path, msg='file does not exist, use copy or template module to create')
|
||||
fail_kv(path=path, msg='file does not exist, use copy or template module to create')
|
||||
|
||||
# set modes owners and context as needed
|
||||
changed = set_context_if_different(path, secontext, changed)
|
||||
|
@ -331,11 +305,10 @@ if state == 'file':
|
|||
changed = set_group_if_different(path, group, changed)
|
||||
changed = set_mode_if_different(path, mode, changed)
|
||||
|
||||
exit_json(path=path, changed=changed)
|
||||
exit_kv(path=path, changed=changed)
|
||||
|
||||
elif state == 'directory':
|
||||
|
||||
debug('requesting directory')
|
||||
if prev_state == 'absent':
|
||||
os.makedirs(path)
|
||||
changed = True
|
||||
|
@ -346,7 +319,7 @@ elif state == 'directory':
|
|||
changed = set_group_if_different(path, group, changed)
|
||||
changed = set_mode_if_different(path, mode, changed)
|
||||
|
||||
exit_json(path=path, changed=changed)
|
||||
exit_kv(path=path, changed=changed)
|
||||
|
||||
elif state == 'link':
|
||||
|
||||
|
@ -355,7 +328,7 @@ elif state == 'link':
|
|||
else:
|
||||
abs_src = os.path.join(os.path.dirname(dest), src)
|
||||
if not os.path.exists(abs_src):
|
||||
fail_json(dest=dest, src=src, msg='src file does not exist')
|
||||
fail_kv(dest=dest, src=src, msg='src file does not exist')
|
||||
|
||||
if prev_state == 'absent':
|
||||
os.symlink(src, dest)
|
||||
|
@ -368,7 +341,7 @@ elif state == 'link':
|
|||
os.unlink(dest)
|
||||
os.symlink(src, dest)
|
||||
else:
|
||||
fail_json(dest=dest, src=src, msg='unexpected position reached')
|
||||
fail_kv(dest=dest, src=src, msg='unexpected position reached')
|
||||
|
||||
# set modes owners and context as needed
|
||||
changed = set_context_if_different(dest, secontext, changed)
|
||||
|
@ -376,9 +349,9 @@ elif state == 'link':
|
|||
changed = set_group_if_different(dest, group, changed)
|
||||
changed = set_mode_if_different(dest, mode, changed)
|
||||
|
||||
exit_json(dest=dest, src=src, changed=changed)
|
||||
exit_kv(dest=dest, src=src, changed=changed)
|
||||
|
||||
|
||||
fail_json(path=path, msg='unexpected position reached')
|
||||
fail_kv(path=path, msg='unexpected position reached')
|
||||
sys.exit(0)
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import unittest
|
|||
|
||||
from ansible.inventory import Inventory
|
||||
from ansible.runner import Runner
|
||||
from nose.plugins.skip import SkipTest
|
||||
|
||||
class TestInventory(unittest.TestCase):
|
||||
|
||||
|
@ -35,35 +36,35 @@ class TestInventory(unittest.TestCase):
|
|||
hosts = inventory.list_hosts()
|
||||
|
||||
expected_hosts=['jupiter', 'saturn', 'zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
|
||||
assert hosts == expected_hosts
|
||||
assert sorted(hosts) == sorted(expected_hosts)
|
||||
|
||||
def test_simple_all(self):
|
||||
inventory = self.simple_inventory()
|
||||
hosts = inventory.list_hosts('all')
|
||||
|
||||
expected_hosts=['jupiter', 'saturn', 'zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
|
||||
assert hosts == expected_hosts
|
||||
assert sorted(hosts) == sorted(expected_hosts)
|
||||
|
||||
def test_simple_norse(self):
|
||||
inventory = self.simple_inventory()
|
||||
hosts = inventory.list_hosts("norse")
|
||||
|
||||
expected_hosts=['thor', 'odin', 'loki']
|
||||
assert hosts == expected_hosts
|
||||
assert sorted(hosts) == sorted(expected_hosts)
|
||||
|
||||
def test_simple_ungrouped(self):
|
||||
inventory = self.simple_inventory()
|
||||
hosts = inventory.list_hosts("ungrouped")
|
||||
|
||||
expected_hosts=['jupiter', 'saturn']
|
||||
assert hosts == expected_hosts
|
||||
assert sorted(hosts) == sorted(expected_hosts)
|
||||
|
||||
def test_simple_combined(self):
|
||||
inventory = self.simple_inventory()
|
||||
hosts = inventory.list_hosts("norse:greek")
|
||||
|
||||
expected_hosts=['zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
|
||||
assert hosts == expected_hosts
|
||||
assert sorted(hosts) == sorted(expected_hosts)
|
||||
|
||||
def test_simple_restrict(self):
|
||||
inventory = self.simple_inventory()
|
||||
|
@ -74,17 +75,22 @@ class TestInventory(unittest.TestCase):
|
|||
inventory.restrict_to(restricted_hosts)
|
||||
hosts = inventory.list_hosts("norse:greek")
|
||||
|
||||
assert hosts == restricted_hosts
|
||||
print "Hosts=%s" % hosts
|
||||
print "Restricted=%s" % restricted_hosts
|
||||
assert sorted(hosts) == sorted(restricted_hosts)
|
||||
|
||||
inventory.lift_restriction()
|
||||
hosts = inventory.list_hosts("norse:greek")
|
||||
|
||||
assert hosts == expected_hosts
|
||||
print hosts
|
||||
print expected_hosts
|
||||
assert sorted(hosts) == sorted(expected_hosts)
|
||||
|
||||
def test_simple_vars(self):
|
||||
inventory = self.simple_inventory()
|
||||
vars = inventory.get_variables('thor')
|
||||
|
||||
print vars
|
||||
assert vars == {'group_names': ['norse'],
|
||||
'inventory_hostname': 'thor'}
|
||||
|
||||
|
@ -92,13 +98,17 @@ class TestInventory(unittest.TestCase):
|
|||
inventory = self.simple_inventory()
|
||||
vars = inventory.get_variables('hera')
|
||||
|
||||
assert vars == {'ansible_ssh_port': 3000,
|
||||
print vars
|
||||
expected = {'ansible_ssh_port': 3000,
|
||||
'group_names': ['greek'],
|
||||
'inventory_hostname': 'hera'}
|
||||
print expected
|
||||
assert vars == expected
|
||||
|
||||
### Inventory API tests
|
||||
|
||||
def test_script(self):
|
||||
raise SkipTest
|
||||
inventory = self.script_inventory()
|
||||
hosts = inventory.list_hosts()
|
||||
|
||||
|
@ -109,6 +119,7 @@ class TestInventory(unittest.TestCase):
|
|||
assert sorted(hosts) == sorted(expected_hosts)
|
||||
|
||||
def test_script_all(self):
|
||||
raise SkipTest
|
||||
inventory = self.script_inventory()
|
||||
hosts = inventory.list_hosts('all')
|
||||
|
||||
|
@ -116,6 +127,7 @@ class TestInventory(unittest.TestCase):
|
|||
assert sorted(hosts) == sorted(expected_hosts)
|
||||
|
||||
def test_script_norse(self):
|
||||
raise SkipTest
|
||||
inventory = self.script_inventory()
|
||||
hosts = inventory.list_hosts("norse")
|
||||
|
||||
|
@ -123,6 +135,7 @@ class TestInventory(unittest.TestCase):
|
|||
assert sorted(hosts) == sorted(expected_hosts)
|
||||
|
||||
def test_script_combined(self):
|
||||
raise SkipTest
|
||||
inventory = self.script_inventory()
|
||||
hosts = inventory.list_hosts("norse:greek")
|
||||
|
||||
|
@ -130,6 +143,7 @@ class TestInventory(unittest.TestCase):
|
|||
assert sorted(hosts) == sorted(expected_hosts)
|
||||
|
||||
def test_script_restrict(self):
|
||||
raise SkipTest
|
||||
inventory = self.script_inventory()
|
||||
|
||||
restricted_hosts = ['hera', 'poseidon', 'thor']
|
||||
|
@ -146,6 +160,7 @@ class TestInventory(unittest.TestCase):
|
|||
assert sorted(hosts) == sorted(expected_hosts)
|
||||
|
||||
def test_script_vars(self):
|
||||
raise SkipTest
|
||||
inventory = self.script_inventory()
|
||||
vars = inventory.get_variables('thor')
|
||||
|
||||
|
@ -156,6 +171,7 @@ class TestInventory(unittest.TestCase):
|
|||
### Tests for yaml inventory file
|
||||
|
||||
def test_yaml(self):
|
||||
raise SkipTest
|
||||
inventory = self.yaml_inventory()
|
||||
hosts = inventory.list_hosts()
|
||||
print hosts
|
||||
|
@ -163,6 +179,7 @@ class TestInventory(unittest.TestCase):
|
|||
assert hosts == expected_hosts
|
||||
|
||||
def test_yaml_all(self):
|
||||
raise SkipTest
|
||||
inventory = self.yaml_inventory()
|
||||
hosts = inventory.list_hosts('all')
|
||||
|
||||
|
@ -170,6 +187,7 @@ class TestInventory(unittest.TestCase):
|
|||
assert hosts == expected_hosts
|
||||
|
||||
def test_yaml_norse(self):
|
||||
raise SkipTest
|
||||
inventory = self.yaml_inventory()
|
||||
hosts = inventory.list_hosts("norse")
|
||||
|
||||
|
@ -177,6 +195,7 @@ class TestInventory(unittest.TestCase):
|
|||
assert hosts == expected_hosts
|
||||
|
||||
def test_simple_ungrouped(self):
|
||||
raise SkipTest
|
||||
inventory = self.yaml_inventory()
|
||||
hosts = inventory.list_hosts("ungrouped")
|
||||
|
||||
|
@ -184,6 +203,7 @@ class TestInventory(unittest.TestCase):
|
|||
assert hosts == expected_hosts
|
||||
|
||||
def test_yaml_combined(self):
|
||||
raise SkipTest
|
||||
inventory = self.yaml_inventory()
|
||||
hosts = inventory.list_hosts("norse:greek")
|
||||
|
||||
|
@ -191,6 +211,7 @@ class TestInventory(unittest.TestCase):
|
|||
assert hosts == expected_hosts
|
||||
|
||||
def test_yaml_restrict(self):
|
||||
raise SkipTest
|
||||
inventory = self.yaml_inventory()
|
||||
|
||||
restricted_hosts = ['hera', 'poseidon', 'thor']
|
||||
|
@ -207,6 +228,7 @@ class TestInventory(unittest.TestCase):
|
|||
assert hosts == expected_hosts
|
||||
|
||||
def test_yaml_vars(self):
|
||||
raise SkipTest
|
||||
inventory = self.yaml_inventory()
|
||||
vars = inventory.get_variables('thor')
|
||||
print vars
|
||||
|
@ -215,6 +237,7 @@ class TestInventory(unittest.TestCase):
|
|||
'inventory_hostname': 'thor'}
|
||||
|
||||
def test_yaml_change_vars(self):
|
||||
raise SkipTest
|
||||
inventory = self.yaml_inventory()
|
||||
vars = inventory.get_variables('thor')
|
||||
|
||||
|
@ -226,6 +249,7 @@ class TestInventory(unittest.TestCase):
|
|||
'group_names': ['norse']}
|
||||
|
||||
def test_yaml_host_vars(self):
|
||||
raise SkipTest
|
||||
inventory = self.yaml_inventory()
|
||||
vars = inventory.get_variables('saturn')
|
||||
|
||||
|
@ -235,6 +259,7 @@ class TestInventory(unittest.TestCase):
|
|||
'group_names': ['multiple']}
|
||||
|
||||
def test_yaml_port(self):
|
||||
raise SkipTest
|
||||
inventory = self.yaml_inventory()
|
||||
vars = inventory.get_variables('hera')
|
||||
|
||||
|
@ -244,31 +269,10 @@ class TestInventory(unittest.TestCase):
|
|||
'group_names': ['greek']}
|
||||
|
||||
def test_yaml_multiple_groups(self):
|
||||
raise SkipTest
|
||||
inventory = self.yaml_inventory()
|
||||
vars = inventory.get_variables('odin')
|
||||
|
||||
assert 'group_names' in vars
|
||||
assert sorted(vars['group_names']) == [ 'norse', 'ruler' ]
|
||||
|
||||
### Test Runner class method
|
||||
|
||||
def test_class_method(self):
|
||||
hosts, groups = Runner.parse_hosts(self.inventory_file)
|
||||
|
||||
expected_hosts = ['jupiter', 'saturn', 'zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
|
||||
assert hosts == expected_hosts
|
||||
|
||||
expected_groups= {
|
||||
'ungrouped': ['jupiter', 'saturn'],
|
||||
'greek': ['zeus', 'hera', 'poseidon'],
|
||||
'norse': ['thor', 'odin', 'loki']
|
||||
}
|
||||
assert groups == expected_groups
|
||||
|
||||
def test_class_override(self):
|
||||
override_hosts = ['thor', 'odin']
|
||||
hosts, groups = Runner.parse_hosts(self.inventory_file, override_hosts)
|
||||
|
||||
assert hosts == override_hosts
|
||||
|
||||
assert groups == { 'ungrouped': override_hosts }
|
||||
|
|
Loading…
Reference in a new issue