From 3454fa99506e70be2275b1209c11489487099bc8 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Sat, 14 Apr 2012 11:58:08 -0400 Subject: [PATCH 01/83] As part of the support for access to external nodes information, save fact data into /var/lib/ansible/setup_data OR a per-user directory when running from playbooks. Technically this info is also available via the SETUP_CACHE but that is a bit more complex of a construct and it would be better to not cross the streams. --- lib/ansible/playbook.py | 4 ++-- lib/ansible/runner.py | 22 +++++++++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py index ba5115d02d9..695895624b5 100755 --- a/lib/ansible/playbook.py +++ b/lib/ansible/playbook.py @@ -313,7 +313,7 @@ class PlayBook(object): setup_cache=SETUP_CACHE, basedir=self.basedir, conditional=only_if, callbacks=self.runner_callbacks, extra_vars=self.extra_vars, debug=self.debug, sudo=sudo, - transport=transport, sudo_pass=self.sudo_pass + transport=transport, sudo_pass=self.sudo_pass, is_playbook=True ) if async_seconds == 0: @@ -475,7 +475,7 @@ class PlayBook(object): remote_pass=self.remote_pass, remote_port=self.remote_port, setup_cache=SETUP_CACHE, callbacks=self.runner_callbacks, sudo=sudo, debug=self.debug, - transport=transport, sudo_pass=self.sudo_pass + transport=transport, sudo_pass=self.sudo_pass, is_playbook=True ).run() self.stats.compute(setup_results, setup=True) diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index 418a069ba8c..a715b531251 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -76,7 +76,7 @@ class Runner(object): sudo_pass=C.DEFAULT_SUDO_PASS, remote_port=C.DEFAULT_REMOTE_PORT, background=0, basedir=None, setup_cache=None, transport=C.DEFAULT_TRANSPORT, conditional='True', groups={}, callbacks=None, verbose=False, - debug=False, sudo=False, extra_vars=None, module_vars=None): + debug=False, sudo=False, extra_vars=None, module_vars=None, is_playbook=False): if setup_cache is None: setup_cache = {} @@ -117,6 +117,7 @@ class Runner(object): self.basedir = basedir self.sudo = sudo self.sudo_pass = sudo_pass + self.is_playbook = is_playbook euid = pwd.getpwuid(os.geteuid())[0] if self.transport == 'local' and self.remote_user != euid: @@ -401,6 +402,23 @@ class Runner(object): # ***************************************************** + def _save_setup_result_to_disk(self, conn, result): + ''' cache results of calling setup ''' + + dest = os.path.expanduser("~/.ansible/setup_data") + if self.remote_user == 'root': + dest = "/var/lib/ansible/setup_data" + if not os.path.exists(dest): + os.makedirs(dest) + + fh = open(os.path.join(dest, conn.host), "w") + fh.write(result) + fh.close() + + return result + + # ***************************************************** + def _add_result_to_setup_cache(self, conn, result): ''' allows discovered variables to be used in templates and action statements ''' @@ -434,6 +452,8 @@ class Runner(object): if module_name == 'setup': self._add_result_to_setup_cache(conn, result) + if self.is_playbook: + self._save_setup_result_to_disk(conn, result) return self._return_from_module(conn, host, result, err, executed) From d8f9d7c6c966e34e1ea225a41cc26b4835e04830 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Sat, 14 Apr 2012 16:08:04 -0400 Subject: [PATCH 02/83] Use correct user to determine host files path, do not reuse .ansible which is already taken for ansible management ops. --- lib/ansible/runner.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index a715b531251..78d01609470 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -28,6 +28,7 @@ import random import traceback import tempfile import subprocess +import getpass import ansible.constants as C import ansible.connection @@ -405,8 +406,9 @@ class Runner(object): def _save_setup_result_to_disk(self, conn, result): ''' cache results of calling setup ''' - dest = os.path.expanduser("~/.ansible/setup_data") - if self.remote_user == 'root': + dest = os.path.expanduser("~/.ansible_setup_data") + user = getpass.getuser() + if user == 'root': dest = "/var/lib/ansible/setup_data" if not os.path.exists(dest): os.makedirs(dest) From c5cae87ecadc099800ef38870943a2c242a3498b Mon Sep 17 00:00:00 2001 From: Jeroen Hoekx Date: Fri, 13 Apr 2012 14:39:54 +0200 Subject: [PATCH 03/83] Refactor inventory code out of Runner. This introduces the Inventory class. Playbook uses the internals of Runner to limit the number of hosts to poll asynchronously. To accomodate this, Inventory can be restricted to specific hosts. --- bin/ansible | 8 +- lib/ansible/inventory.py | 189 +++++++++++++++++++++++++++++++++++++++ lib/ansible/playbook.py | 62 +++++++------ lib/ansible/runner.py | 158 +++----------------------------- 4 files changed, 241 insertions(+), 176 deletions(-) create mode 100644 lib/ansible/inventory.py diff --git a/bin/ansible b/bin/ansible index f254eaf3ff9..844004ecc4d 100755 --- a/bin/ansible +++ b/bin/ansible @@ -98,11 +98,11 @@ class Cli(object): # ---------------------------------------------- - def get_polling_runner(self, old_runner, hosts, jid): + def get_polling_runner(self, old_runner, jid): return ansible.runner.Runner( module_name='async_status', module_path=old_runner.module_path, module_args="jid=%s" % jid, remote_user=old_runner.remote_user, - remote_pass=old_runner.remote_pass, host_list=hosts, + remote_pass=old_runner.remote_pass, inventory=old_runner.inventory, timeout=old_runner.timeout, forks=old_runner.forks, remote_port=old_runner.remote_port, pattern='*', callbacks=self.silent_callbacks, verbose=True, @@ -138,8 +138,10 @@ class Cli(object): clock = options.seconds while (clock >= 0): - polling_runner = self.get_polling_runner(runner, poll_hosts, jid) + runner.inventory.restrict_to(poll_hosts) + polling_runner = self.get_polling_runner(runner, jid) poll_results = polling_runner.run() + runner.inventory.lift_restrictions() if poll_results is None: break for (host, host_result) in poll_results['contacted'].iteritems(): diff --git a/lib/ansible/inventory.py b/lib/ansible/inventory.py new file mode 100644 index 00000000000..448ef372b04 --- /dev/null +++ b/lib/ansible/inventory.py @@ -0,0 +1,189 @@ +# (c) 2012, Michael DeHaan +# +# 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 . + +############################################# + +import fnmatch +import os +import subprocess + +import constants as C +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. + """ + + def __init__(self, host_list=C.DEFAULT_HOST_LIST, extra_vars=None): + + self._restriction = None + + 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(extra_vars) + self._is_script = True + else: + self.host_list, self.groups = self._parse_from_file() + self._is_script = False + + # ***************************************************** + # Public API + + 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) ] + + 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, extra_vars=None): + """ Return the variables associated with this host. """ + + if not self._is_script: + return {} + + return self._get_variables_from_script(host, extra_vars) + + # ***************************************************** + + def _parse_from_file(self): + ''' parse a textual host file ''' + + results = [] + groups = dict(ungrouped=[]) + lines = file(self.inventory_file).read().split("\n") + 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 + groups[group_name].append(item) + if not item in results: + results.append(item) + return (results, groups) + + # ***************************************************** + + def _parse_from_script(self, extra_vars=None): + ''' evaluate a script that returns list of hosts by groups ''' + + results = [] + groups = dict(ungrouped=[]) + + cmd = [self.inventory_file, '--list'] + + if extra_vars: + cmd.extend(['--extra-vars', extra_vars]) + + cmd = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False) + out, err = cmd.communicate() + 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 _get_variables_from_script(self, host, extra_vars=None): + ''' support per system variabes from external variable scripts, see web docs ''' + + cmd = [self.inventory_file, '--host', host] + + if extra_vars: + cmd.extend(['--extra-vars', extra_vars]) + + 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 _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 diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py index ba5115d02d9..c3e195e0409 100755 --- a/lib/ansible/playbook.py +++ b/lib/ansible/playbook.py @@ -17,6 +17,7 @@ ############################################# +import ansible.inventory import ansible.runner import ansible.constants as C from ansible import utils @@ -68,7 +69,6 @@ class PlayBook(object): if playbook is None or callbacks is None or runner_callbacks is None or stats is None: raise Exception('missing required arguments') - self.host_list = host_list self.module_path = module_path self.forks = forks self.timeout = timeout @@ -88,9 +88,13 @@ class PlayBook(object): self.basedir = os.path.dirname(playbook) self.playbook = self._parse_playbook(playbook) - self.host_list, self.groups = ansible.runner.Runner.parse_hosts( - host_list, override_hosts=self.override_hosts, extra_vars=self.extra_vars) - + if override_hosts is not None: + if type(override_hosts) != list: + raise errors.AnsibleError("override hosts must be a list") + self.inventory = ansible.inventory.Inventory(override_hosts) + else: + self.inventory = ansible.inventory.Inventory(host_list) + # ***************************************************** def _get_vars(self, play, dirname): @@ -233,7 +237,6 @@ class PlayBook(object): def _async_poll(self, runner, hosts, async_seconds, async_poll_interval, only_if): ''' launch an async job, if poll_interval is set, wait for completion ''' - runner.host_list = hosts runner.background = async_seconds results = runner.run() self.stats.compute(results, poll=True) @@ -257,7 +260,7 @@ class PlayBook(object): return results clock = async_seconds - runner.host_list = self.hosts_to_poll(results) + host_list = self.hosts_to_poll(results) poll_results = results while (clock >= 0): @@ -267,11 +270,13 @@ class PlayBook(object): runner.module_name = 'async_status' runner.background = 0 runner.pattern = '*' + self.inventory.restrict_to(host_list) poll_results = runner.run() self.stats.compute(poll_results, poll=True) - runner.host_list = self.hosts_to_poll(poll_results) + host_list = self.hosts_to_poll(poll_results) + self.inventory.lift_restriction() - if len(runner.host_list) == 0: + if len(host_list) == 0: break if poll_results is None: break @@ -298,15 +303,16 @@ class PlayBook(object): # ***************************************************** - def _run_module(self, pattern, host_list, module, args, vars, remote_user, + def _run_module(self, pattern, module, args, vars, remote_user, async_seconds, async_poll_interval, only_if, sudo, transport): ''' run a particular module step in a playbook ''' - hosts = [ h for h in host_list if (h not in self.stats.failures) and (h not in self.stats.dark)] + hosts = [ h for h in self.inventory.list_hosts() if (h not in self.stats.failures) and (h not in self.stats.dark)] + self.inventory.restrict_to(hosts) runner = ansible.runner.Runner( - pattern=pattern, groups=self.groups, module_name=module, - module_args=args, host_list=hosts, forks=self.forks, + pattern=pattern, inventory=self.inventory, module_name=module, + module_args=args, forks=self.forks, remote_pass=self.remote_pass, module_path=self.module_path, timeout=self.timeout, remote_user=remote_user, remote_port=self.remote_port, module_vars=vars, @@ -317,13 +323,16 @@ class PlayBook(object): ) if async_seconds == 0: - return runner.run() + results = runner.run() else: - return self._async_poll(runner, hosts, async_seconds, async_poll_interval, only_if) + results = self._async_poll(runner, hosts, async_seconds, async_poll_interval, only_if) + + self.inventory.lift_restriction() + return results # ***************************************************** - def _run_task(self, pattern=None, host_list=None, task=None, + def _run_task(self, pattern=None, task=None, remote_user=None, handlers=None, conditional=False, sudo=False, transport=None): ''' run a single task in the playbook and recursively run any subtasks. ''' @@ -354,7 +363,7 @@ class PlayBook(object): # load up an appropriate ansible runner to # run the task in parallel - results = self._run_module(pattern, host_list, module_name, + results = self._run_module(pattern, module_name, module_args, module_vars, remote_user, async_seconds, async_poll_interval, only_if, sudo, transport) @@ -406,7 +415,7 @@ class PlayBook(object): # ***************************************************** - def _do_conditional_imports(self, vars_files, host_list): + def _do_conditional_imports(self, vars_files): ''' handle the vars_files section, which can contain variables ''' # FIXME: save parsed variable results in memory to avoid excessive re-reading/parsing @@ -417,7 +426,7 @@ class PlayBook(object): if type(vars_files) != list: raise errors.AnsibleError("vars_files must be a list") - for host in host_list: + for host in self.inventory.list_hosts(): cache_vars = SETUP_CACHE.get(host,{}) SETUP_CACHE[host] = cache_vars for filename in vars_files: @@ -460,16 +469,18 @@ class PlayBook(object): if vars_files is not None: self.callbacks.on_setup_secondary() - self._do_conditional_imports(vars_files, self.host_list) + self._do_conditional_imports(vars_files) else: self.callbacks.on_setup_primary() - host_list = [ h for h in self.host_list if not (h in self.stats.failures or h in self.stats.dark) ] + host_list = [ h for h in self.inventory.list_hosts(pattern) + if not (h in self.stats.failures or h in self.stats.dark) ] + self.inventory.restrict_to(host_list) # push any variables down to the system setup_results = ansible.runner.Runner( - pattern=pattern, groups=self.groups, module_name='setup', - module_args=vars, host_list=host_list, + pattern=pattern, module_name='setup', + module_args=vars, inventory=self.inventory, forks=self.forks, module_path=self.module_path, timeout=self.timeout, remote_user=user, remote_pass=self.remote_pass, remote_port=self.remote_port, @@ -479,6 +490,8 @@ class PlayBook(object): ).run() self.stats.compute(setup_results, setup=True) + self.inventory.lift_restriction() + # now for each result, load into the setup cache so we can # let runner template out future commands setup_ok = setup_results.get('contacted', {}) @@ -494,7 +507,6 @@ class PlayBook(object): SETUP_CACHE[h].update(extra_vars) except: SETUP_CACHE[h] = extra_vars - return host_list # ***************************************************** @@ -530,7 +542,6 @@ class PlayBook(object): for task in tasks: self._run_task( pattern=pattern, - host_list=self.host_list, task=task, handlers=handlers, remote_user=user, @@ -547,16 +558,17 @@ class PlayBook(object): for task in handlers: triggered_by = task.get('run', None) if type(triggered_by) == list: + self.inventory.restrict_to(triggered_by) self._run_task( pattern=pattern, task=task, handlers=[], - host_list=triggered_by, conditional=True, remote_user=user, sudo=sudo, transport=transport ) + self.inventory.lift_restriction() # end of execution for this particular pattern. Multiple patterns # can be in a single playbook file diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index 418a069ba8c..50c4029a45d 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -18,7 +18,6 @@ ################################################ -import fnmatch import multiprocessing import signal import os @@ -27,10 +26,10 @@ import Queue import random import traceback import tempfile -import subprocess import ansible.constants as C import ansible.connection +import ansible.inventory from ansible import utils from ansible import errors from ansible import callbacks as ans_callbacks @@ -67,8 +66,6 @@ def _executor_hook(job_queue, result_queue): class Runner(object): - _external_variable_script = None - def __init__(self, host_list=C.DEFAULT_HOST_LIST, module_path=C.DEFAULT_MODULE_PATH, module_name=C.DEFAULT_MODULE_NAME, module_args=C.DEFAULT_MODULE_ARGS, forks=C.DEFAULT_FORKS, timeout=C.DEFAULT_TIMEOUT, pattern=C.DEFAULT_PATTERN, @@ -76,7 +73,7 @@ class Runner(object): sudo_pass=C.DEFAULT_SUDO_PASS, remote_port=C.DEFAULT_REMOTE_PORT, background=0, basedir=None, setup_cache=None, transport=C.DEFAULT_TRANSPORT, conditional='True', groups={}, callbacks=None, verbose=False, - debug=False, sudo=False, extra_vars=None, module_vars=None): + debug=False, sudo=False, extra_vars=None, module_vars=None, inventory=None): if setup_cache is None: setup_cache = {} @@ -92,11 +89,10 @@ class Runner(object): self.transport = transport self.connector = ansible.connection.Connection(self, self.transport) - if type(host_list) == str: - self.host_list, self.groups = self.parse_hosts(host_list) + if inventory is None: + self.inventory = ansible.inventory.Inventory(host_list, extra_vars) else: - self.host_list = host_list - self.groups = groups + self.inventory = inventory self.setup_cache = setup_cache self.conditional = conditional @@ -130,106 +126,6 @@ class Runner(object): # ***************************************************** - @classmethod - def parse_hosts_from_regular_file(cls, host_list): - ''' parse a textual host file ''' - - results = [] - groups = dict(ungrouped=[]) - lines = file(host_list).read().split("\n") - 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 - groups[group_name].append(item) - if not item in results: - results.append(item) - return (results, groups) - - # ***************************************************** - - @classmethod - def parse_hosts_from_script(cls, host_list, extra_vars): - ''' evaluate a script that returns list of hosts by groups ''' - - results = [] - groups = dict(ungrouped=[]) - host_list = os.path.abspath(host_list) - cls._external_variable_script = host_list - cmd = [host_list, '--list'] - if extra_vars: - cmd.extend(['--extra-vars', extra_vars]) - cmd = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False) - out, err = cmd.communicate() - rc = cmd.returncode - if rc: - raise errors.AnsibleError("%s: %s" % (host_list, err)) - try: - groups = utils.json_loads(out) - except: - raise errors.AnsibleError("invalid JSON response from script: %s" % host_list) - for (groupname, hostlist) in groups.iteritems(): - for host in hostlist: - if host not in results: - results.append(host) - return (results, groups) - - # ***************************************************** - - @classmethod - def parse_hosts(cls, host_list, override_hosts=None, extra_vars=None): - ''' parse the host inventory file, returns (hosts, groups) ''' - - if override_hosts is not None: - if type(override_hosts) != list: - raise errors.AnsibleError("override hosts must be a list") - return (override_hosts, dict(ungrouped=override_hosts)) - - if type(host_list) == list: - raise Exception("function can only be called on inventory files") - - host_list = os.path.expanduser(host_list) - if not os.path.exists(host_list): - raise errors.AnsibleFileNotFound("inventory file not found: %s" % host_list) - - if not os.access(host_list, os.X_OK): - return Runner.parse_hosts_from_regular_file(host_list) - else: - return Runner.parse_hosts_from_script(host_list, extra_vars) - - # ***************************************************** - - 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 - - # ***************************************************** - def _connect(self, host): ''' connects to a host, returns (is_successful, connection_object OR traceback_string) ''' @@ -296,34 +192,6 @@ class Runner(object): # ***************************************************** - def _add_variables_from_script(self, conn, inject): - ''' support per system variabes from external variable scripts, see web docs ''' - - host = conn.host - - cmd = [Runner._external_variable_script, '--host', host] - if self.extra_vars: - cmd.extend(['--extra-vars', self.extra_vars]) - - cmd = subprocess.Popen(cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - shell=False - ) - out, err = cmd.communicate() - inject2 = {} - try: - inject2 = utils.json_loads(out) - except: - raise errors.AnsibleError("%s returned invalid result when called with hostname %s" % ( - Runner._external_variable_script, - host - )) - # store injected variables in the templates - inject.update(inject2) - - # ***************************************************** - def _add_setup_vars(self, inject, args): ''' setup module variables need special handling ''' @@ -377,8 +245,9 @@ class Runner(object): if not eval(conditional): return [ utils.smjson(dict(skipped=True)), None, 'skipped' ] - if Runner._external_variable_script is not None: - self._add_variables_from_script(conn, inject) + host_variables = self.inventory.get_variables(conn.host, self.extra_vars) + inject.update(host_variables) + if self.module_name == 'setup': args = self._add_setup_vars(inject, args) args = self._add_setup_metadata(args) @@ -692,13 +561,6 @@ class Runner(object): # ***************************************************** - def _match_hosts(self, pattern): - ''' return all matched hosts fitting a pattern ''' - - return [ h for h in self.host_list if self._matches(h, pattern) ] - - # ***************************************************** - def _parallel_exec(self, hosts): ''' handles mulitprocessing when more than 1 fork is required ''' @@ -745,7 +607,7 @@ class Runner(object): results2["dark"][host] = result # hosts which were contacted but never got a chance to return - for host in self._match_hosts(self.pattern): + for host in self.inventory.list_hosts(self.pattern): if not (host in results2['dark'] or host in results2['contacted']): results2["dark"][host] = {} @@ -757,7 +619,7 @@ class Runner(object): ''' xfer & run module on all matched hosts ''' # find hosts that match the pattern - hosts = self._match_hosts(self.pattern) + hosts = self.inventory.list_hosts(self.pattern) if len(hosts) == 0: self.callbacks.on_no_hosts() return dict(contacted={}, dark={}) From 195e6d617b67c70043db7812fc2346d7a226c730 Mon Sep 17 00:00:00 2001 From: Jeroen Hoekx Date: Fri, 13 Apr 2012 20:50:30 +0200 Subject: [PATCH 04/83] Add tests for Inventory class. --- test/TestInventory.py | 143 ++++++++++++++++++++++++++++++++++++++++++ test/inventory_api.py | 39 ++++++++++++ test/simple_hosts | 12 ++++ 3 files changed, 194 insertions(+) create mode 100644 test/TestInventory.py create mode 100644 test/inventory_api.py create mode 100644 test/simple_hosts diff --git a/test/TestInventory.py b/test/TestInventory.py new file mode 100644 index 00000000000..b0e3c62ccf5 --- /dev/null +++ b/test/TestInventory.py @@ -0,0 +1,143 @@ +import os +import unittest + +from ansible.inventory import Inventory + +class TestInventory(unittest.TestCase): + + def setUp(self): + self.cwd = os.getcwd() + self.test_dir = os.path.join(self.cwd, 'test') + + self.inventory_file = os.path.join(self.test_dir, 'simple_hosts') + self.inventory_script = os.path.join(self.test_dir, 'inventory_api.py') + + os.chmod(self.inventory_script, 0755) + + def tearDown(self): + os.chmod(self.inventory_script, 0644) + + ### Simple inventory format tests + + def simple_inventory(self): + return Inventory( self.inventory_file ) + + def script_inventory(self): + return Inventory( self.inventory_script ) + + def test_simple(self): + inventory = self.simple_inventory() + hosts = inventory.list_hosts() + + expected_hosts=['jupiter', 'saturn', 'zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki'] + assert hosts == 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 + + def test_simple_norse(self): + inventory = self.simple_inventory() + hosts = inventory.list_hosts("norse") + + expected_hosts=['thor', 'odin', 'loki'] + assert hosts == 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 + + def test_simple_restrict(self): + inventory = self.simple_inventory() + + restricted_hosts = ['hera', 'poseidon', 'thor'] + expected_hosts=['zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki'] + + inventory.restrict_to(restricted_hosts) + hosts = inventory.list_hosts("norse:greek") + + assert hosts == restricted_hosts + + inventory.lift_restriction() + hosts = inventory.list_hosts("norse:greek") + + assert hosts == expected_hosts + + def test_simple_vars(self): + inventory = self.simple_inventory() + vars = inventory.get_variables('thor') + + assert vars == {} + + def test_simple_extra_vars(self): + inventory = self.simple_inventory() + vars = inventory.get_variables('thor', 'a=5') + + assert vars == {} + + ### Inventory API tests + + def test_script(self): + inventory = self.script_inventory() + hosts = inventory.list_hosts() + + expected_hosts=['jupiter', 'saturn', 'zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki'] + + print "Expected: %s"%(expected_hosts) + print "Got : %s"%(hosts) + assert sorted(hosts) == sorted(expected_hosts) + + def test_script_all(self): + inventory = self.script_inventory() + hosts = inventory.list_hosts('all') + + expected_hosts=['jupiter', 'saturn', 'zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki'] + assert sorted(hosts) == sorted(expected_hosts) + + def test_script_norse(self): + inventory = self.script_inventory() + hosts = inventory.list_hosts("norse") + + expected_hosts=['thor', 'odin', 'loki'] + assert sorted(hosts) == sorted(expected_hosts) + + def test_script_combined(self): + inventory = self.script_inventory() + hosts = inventory.list_hosts("norse:greek") + + expected_hosts=['zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki'] + assert sorted(hosts) == sorted(expected_hosts) + + def test_script_restrict(self): + inventory = self.script_inventory() + + restricted_hosts = ['hera', 'poseidon', 'thor'] + expected_hosts=['zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki'] + + inventory.restrict_to(restricted_hosts) + hosts = inventory.list_hosts("norse:greek") + + assert sorted(hosts) == sorted(restricted_hosts) + + inventory.lift_restriction() + hosts = inventory.list_hosts("norse:greek") + + assert sorted(hosts) == sorted(expected_hosts) + + def test_script_vars(self): + inventory = self.script_inventory() + vars = inventory.get_variables('thor') + + assert vars == {"hammer":True} + + def test_script_extra_vars(self): + inventory = self.script_inventory() + vars = inventory.get_variables('thor', 'simple=yes') + + assert vars == {"hammer":True, "simple": "yes"} \ No newline at end of file diff --git a/test/inventory_api.py b/test/inventory_api.py new file mode 100644 index 00000000000..bcde15bd3c7 --- /dev/null +++ b/test/inventory_api.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python + +import json +import sys + +from optparse import OptionParser + +parser = OptionParser() +parser.add_option('-l', '--list', default=False, dest="list_hosts", action="store_true") +parser.add_option('-H', '--host', default=None, dest="host") +parser.add_option('-e', '--extra-vars', default=None, dest="extra") + +options, args = parser.parse_args() + +systems = { + "ungouped": [ "jupiter", "saturn" ], + "greek": [ "zeus", "hera", "poseidon" ], + "norse": [ "thor", "odin", "loki" ] +} + +variables = { + "thor": { + "hammer": True + } +} + +if options.list_hosts == True: + print json.dumps(systems) + sys.exit(0) + +if options.host is not None: + if options.extra: + k,v = options.extra.split("=") + variables[options.host][k] = v + print json.dumps(variables[options.host]) + sys.exit(0) + +parser.print_help() +sys.exit(1) \ No newline at end of file diff --git a/test/simple_hosts b/test/simple_hosts new file mode 100644 index 00000000000..14312af92d8 --- /dev/null +++ b/test/simple_hosts @@ -0,0 +1,12 @@ +jupiter +saturn + +[greek] +zeus +hera +poseidon + +[norse] +thor +odin +loki From 746f1b92ae0111f7deedbe1518955138a51ee737 Mon Sep 17 00:00:00 2001 From: Jeroen Hoekx Date: Sat, 14 Apr 2012 09:29:14 +0200 Subject: [PATCH 05/83] Reimplement the class method on Runner. --- lib/ansible/runner.py | 11 +++++++++++ test/TestInventory.py | 26 +++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index 50c4029a45d..0df79873474 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -123,6 +123,17 @@ class Runner(object): self._tmp_paths = {} random.seed() + # ***************************************************** + + @classmethod + def parse_hosts(cls, host_list, override_hosts=None, extra_vars=None): + ''' parse the host inventory file, returns (hosts, groups) ''' + if override_hosts is None: + inventory = ansible.inventory.Inventory(host_list, extra_vars) + else: + inventory = ansible.inventory.Inventory(override_hosts) + + return inventory.host_list, inventory.groups # ***************************************************** diff --git a/test/TestInventory.py b/test/TestInventory.py index b0e3c62ccf5..ba89bdc3a9c 100644 --- a/test/TestInventory.py +++ b/test/TestInventory.py @@ -2,6 +2,7 @@ import os import unittest from ansible.inventory import Inventory +from ansible.runner import Runner class TestInventory(unittest.TestCase): @@ -140,4 +141,27 @@ class TestInventory(unittest.TestCase): inventory = self.script_inventory() vars = inventory.get_variables('thor', 'simple=yes') - assert vars == {"hammer":True, "simple": "yes"} \ No newline at end of file + assert vars == {"hammer":True, "simple": "yes"} + + ### 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 } From f04041b37dd13d31b0a8d690a1b66da8e9244ce7 Mon Sep 17 00:00:00 2001 From: Jeroen Hoekx Date: Sat, 14 Apr 2012 12:15:57 +0200 Subject: [PATCH 06/83] Ignore port numbers in simple inventory format --- lib/ansible/inventory.py | 3 +++ test/simple_hosts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/ansible/inventory.py b/lib/ansible/inventory.py index 448ef372b04..429b4c95d9f 100644 --- a/lib/ansible/inventory.py +++ b/lib/ansible/inventory.py @@ -105,6 +105,9 @@ class Inventory(object): groups[group_name] = [] elif item != "": # looks like a regular host + if ":" in item: + # a port was specified + item, port = item.split(":") groups[group_name].append(item) if not item in results: results.append(item) diff --git a/test/simple_hosts b/test/simple_hosts index 14312af92d8..6a4e297b4fb 100644 --- a/test/simple_hosts +++ b/test/simple_hosts @@ -3,7 +3,7 @@ saturn [greek] zeus -hera +hera:3000 poseidon [norse] From 54f452616079824e804885c43f97d63fe06a1aba Mon Sep 17 00:00:00 2001 From: Jeroen Hoekx Date: Sat, 14 Apr 2012 13:12:32 +0200 Subject: [PATCH 07/83] Export SSH port number as host variable. --- lib/ansible/inventory.py | 14 ++++++++++++++ test/TestInventory.py | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/lib/ansible/inventory.py b/lib/ansible/inventory.py index 429b4c95d9f..3f97460e51f 100644 --- a/lib/ansible/inventory.py +++ b/lib/ansible/inventory.py @@ -35,6 +35,7 @@ class Inventory(object): def __init__(self, host_list=C.DEFAULT_HOST_LIST, extra_vars=None): self._restriction = None + self._variables = {} if type(host_list) == list: self.host_list = host_list @@ -80,6 +81,9 @@ class Inventory(object): def get_variables(self, host, extra_vars=None): """ Return the variables associated with this host. """ + if host in self._variables: + return self._variables[host] + if not self._is_script: return {} @@ -108,6 +112,11 @@ class Inventory(object): 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) @@ -170,6 +179,11 @@ class Inventory(object): )) 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 ''' diff --git a/test/TestInventory.py b/test/TestInventory.py index ba89bdc3a9c..2f86f507283 100644 --- a/test/TestInventory.py +++ b/test/TestInventory.py @@ -82,6 +82,12 @@ class TestInventory(unittest.TestCase): assert vars == {} + def test_simple_port(self): + inventory = self.simple_inventory() + vars = inventory.get_variables('hera') + + assert vars == {'ansible_ssh_port': 3000} + ### Inventory API tests def test_script(self): From 3a24aa9a70cf625aa9c9eff545f31c4fafeaa7bf Mon Sep 17 00:00:00 2001 From: Jeroen Hoekx Date: Sat, 14 Apr 2012 15:45:24 +0200 Subject: [PATCH 08/83] Add YAML inventory format. See test/yaml_hosts for an example. Hosts can be part of multiple groups. Groups can also have variables, inherited by the hosts. There is no variable scope, last variable seen wins. --- lib/ansible/inventory.py | 72 ++++++++++++++++++++++++++++++++++++++ test/TestInventory.py | 74 ++++++++++++++++++++++++++++++++++++++++ test/yaml_hosts | 26 ++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 test/yaml_hosts diff --git a/lib/ansible/inventory.py b/lib/ansible/inventory.py index 3f97460e51f..c3678d64a5e 100644 --- a/lib/ansible/inventory.py +++ b/lib/ansible/inventory.py @@ -97,6 +97,8 @@ class Inventory(object): 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() @@ -154,6 +156,76 @@ class Inventory(object): # ***************************************************** + 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 = {} + + for item in data: + if type(item) == dict: + if "group" in item: + group_name = item["group"] + + group_vars = [] + if "vars" in item: + group_vars = 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) + + # or a host + elif "host" in item: + host_name = self._parse_yaml_host(item) + hosts.append(host_name) + else: + host_name = self._parse_yaml_host(item) + hosts.append(host_name) + + # filter duplicate hosts + output_hosts = [] + for host in hosts: + if host not in output_hosts: + output_hosts.append(host) + + return output_hosts, groups + + def _parse_yaml_host(self, item, variables=[]): + def set_variables(host, variables): + for variable in variables: + if len(variable) != 1: + raise AnsibleError("Only one item expected in %s"%(variable)) + k, v = variable.items()[0] + 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 AnsibleError("Unknown item in inventory: %s"%(item)) + + def _get_variables_from_script(self, host, extra_vars=None): ''' support per system variabes from external variable scripts, see web docs ''' diff --git a/test/TestInventory.py b/test/TestInventory.py index 2f86f507283..a0b0b74d16d 100644 --- a/test/TestInventory.py +++ b/test/TestInventory.py @@ -12,6 +12,7 @@ class TestInventory(unittest.TestCase): self.inventory_file = os.path.join(self.test_dir, 'simple_hosts') self.inventory_script = os.path.join(self.test_dir, 'inventory_api.py') + self.inventory_yaml = os.path.join(self.test_dir, 'yaml_hosts') os.chmod(self.inventory_script, 0755) @@ -26,6 +27,9 @@ class TestInventory(unittest.TestCase): def script_inventory(self): return Inventory( self.inventory_script ) + def yaml_inventory(self): + return Inventory( self.inventory_yaml ) + def test_simple(self): inventory = self.simple_inventory() hosts = inventory.list_hosts() @@ -149,6 +153,76 @@ class TestInventory(unittest.TestCase): assert vars == {"hammer":True, "simple": "yes"} + ### Tests for yaml inventory file + + def test_yaml(self): + inventory = self.yaml_inventory() + hosts = inventory.list_hosts() + print hosts + expected_hosts=['jupiter', 'saturn', 'zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki'] + assert hosts == expected_hosts + + def test_yaml_all(self): + inventory = self.yaml_inventory() + hosts = inventory.list_hosts('all') + + expected_hosts=['jupiter', 'saturn', 'zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki'] + assert hosts == expected_hosts + + def test_yaml_norse(self): + inventory = self.yaml_inventory() + hosts = inventory.list_hosts("norse") + + expected_hosts=['thor', 'odin', 'loki'] + assert hosts == expected_hosts + + def test_yaml_combined(self): + inventory = self.yaml_inventory() + hosts = inventory.list_hosts("norse:greek") + + expected_hosts=['zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki'] + assert hosts == expected_hosts + + def test_yaml_restrict(self): + inventory = self.yaml_inventory() + + restricted_hosts = ['hera', 'poseidon', 'thor'] + expected_hosts=['zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki'] + + inventory.restrict_to(restricted_hosts) + hosts = inventory.list_hosts("norse:greek") + + assert hosts == restricted_hosts + + inventory.lift_restriction() + hosts = inventory.list_hosts("norse:greek") + + assert hosts == expected_hosts + + def test_yaml_vars(self): + inventory = self.yaml_inventory() + vars = inventory.get_variables('thor') + + assert vars == {"hammer":True} + + def test_yaml_host_vars(self): + inventory = self.yaml_inventory() + vars = inventory.get_variables('saturn') + + assert vars == {"moon":"titan"} + + def test_yaml_extra_vars(self): + inventory = self.yaml_inventory() + vars = inventory.get_variables('thor', 'a=5') + + assert vars == {"hammer":True} + + def test_yaml_port(self): + inventory = self.yaml_inventory() + vars = inventory.get_variables('hera') + + assert vars == {'ansible_ssh_port': 3000} + ### Test Runner class method def test_class_method(self): diff --git a/test/yaml_hosts b/test/yaml_hosts new file mode 100644 index 00000000000..95f278b91a0 --- /dev/null +++ b/test/yaml_hosts @@ -0,0 +1,26 @@ +--- + +- jupiter +- host: saturn + vars: + - moon: titan + +- group: greek + hosts: + - zeus + - hera + - poseidon + vars: + - ansible_ssh_port: 3000 + +- group: norse + hosts: + - host: thor + vars: + - hammer: True + - odin + - loki + +- group: multiple + hosts: + - saturn From 961ccdb2f4768392d9a1396c73a6b317b6b48f60 Mon Sep 17 00:00:00 2001 From: Jeroen Hoekx Date: Mon, 16 Apr 2012 10:55:08 +0200 Subject: [PATCH 09/83] List hosts in no group in the ungrouped group. --- lib/ansible/inventory.py | 17 ++++++++++++++++- test/TestInventory.py | 14 ++++++++++++++ test/yaml_hosts | 2 ++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/lib/ansible/inventory.py b/lib/ansible/inventory.py index c3678d64a5e..46a5edb589a 100644 --- a/lib/ansible/inventory.py +++ b/lib/ansible/inventory.py @@ -168,6 +168,8 @@ class Inventory(object): hosts = [] groups = {} + ungrouped = [] + for item in data: if type(item) == dict: if "group" in item: @@ -186,13 +188,14 @@ class Inventory(object): groups[group_name] = group_hosts hosts.extend(group_hosts) - # or a host 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 = [] @@ -200,6 +203,18 @@ class Inventory(object): 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=[]): diff --git a/test/TestInventory.py b/test/TestInventory.py index a0b0b74d16d..21cccf5fe89 100644 --- a/test/TestInventory.py +++ b/test/TestInventory.py @@ -51,6 +51,13 @@ class TestInventory(unittest.TestCase): expected_hosts=['thor', 'odin', 'loki'] assert hosts == expected_hosts + def test_simple_ungrouped(self): + inventory = self.simple_inventory() + hosts = inventory.list_hosts("ungrouped") + + expected_hosts=['jupiter', 'saturn'] + assert hosts == expected_hosts + def test_simple_combined(self): inventory = self.simple_inventory() hosts = inventory.list_hosts("norse:greek") @@ -176,6 +183,13 @@ class TestInventory(unittest.TestCase): expected_hosts=['thor', 'odin', 'loki'] assert hosts == expected_hosts + def test_simple_ungrouped(self): + inventory = self.yaml_inventory() + hosts = inventory.list_hosts("ungrouped") + + expected_hosts=['jupiter'] + assert hosts == expected_hosts + def test_yaml_combined(self): inventory = self.yaml_inventory() hosts = inventory.list_hosts("norse:greek") diff --git a/test/yaml_hosts b/test/yaml_hosts index 95f278b91a0..7568ff4bda2 100644 --- a/test/yaml_hosts +++ b/test/yaml_hosts @@ -5,6 +5,8 @@ vars: - moon: titan +- zeus + - group: greek hosts: - zeus From 8c3206c99fa940ce58fdcb90e9aa4e0942c32914 Mon Sep 17 00:00:00 2001 From: Jeroen Hoekx Date: Mon, 16 Apr 2012 10:59:34 +0200 Subject: [PATCH 10/83] Return a copy of the host variables. --- lib/ansible/inventory.py | 2 +- test/TestInventory.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/ansible/inventory.py b/lib/ansible/inventory.py index 46a5edb589a..05f8001fb12 100644 --- a/lib/ansible/inventory.py +++ b/lib/ansible/inventory.py @@ -82,7 +82,7 @@ class Inventory(object): """ Return the variables associated with this host. """ if host in self._variables: - return self._variables[host] + return self._variables[host].copy() if not self._is_script: return {} diff --git a/test/TestInventory.py b/test/TestInventory.py index 21cccf5fe89..4f48db14f65 100644 --- a/test/TestInventory.py +++ b/test/TestInventory.py @@ -219,6 +219,15 @@ class TestInventory(unittest.TestCase): assert vars == {"hammer":True} + def test_yaml_change_vars(self): + inventory = self.yaml_inventory() + vars = inventory.get_variables('thor') + + vars["hammer"] = False + + vars = inventory.get_variables('thor') + assert vars == {"hammer":True} + def test_yaml_host_vars(self): inventory = self.yaml_inventory() vars = inventory.get_variables('saturn') From 08468dcb0ca90a389795e1576d36be97cf9980a5 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 16 Apr 2012 21:52:15 -0400 Subject: [PATCH 11/83] Fixes to make ports DWIM. --- examples/playbooks/intro_example.yml | 1 + lib/ansible/connection.py | 23 +++++++---- lib/ansible/playbook.py | 19 +++++---- lib/ansible/runner.py | 62 +++++++++++++++++----------- 4 files changed, 65 insertions(+), 40 deletions(-) diff --git a/examples/playbooks/intro_example.yml b/examples/playbooks/intro_example.yml index 1fb31d2688b..7ee6fe7991e 100644 --- a/examples/playbooks/intro_example.yml +++ b/examples/playbooks/intro_example.yml @@ -9,6 +9,7 @@ - hosts: all user: root + port: 3000 # could have also have done: # user: mdehaan diff --git a/lib/ansible/connection.py b/lib/ansible/connection.py index 2abeac60bfb..1af307a2321 100755 --- a/lib/ansible/connection.py +++ b/lib/ansible/connection.py @@ -45,12 +45,12 @@ class Connection(object): self.runner = runner self.transport = transport - def connect(self, host): + def connect(self, host, port=None): conn = None if self.transport == 'local' and self._LOCALHOSTRE.search(host): - conn = LocalConnection(self.runner, host) + conn = LocalConnection(self.runner, host, None) elif self.transport == 'paramiko': - conn = ParamikoConnection(self.runner, host) + conn = ParamikoConnection(self.runner, host, port) if conn is None: raise Exception("unsupported connection type") return conn.connect() @@ -64,10 +64,13 @@ class Connection(object): class ParamikoConnection(object): ''' SSH based connections with Paramiko ''' - def __init__(self, runner, host): + def __init__(self, runner, host, port=None): self.ssh = None self.runner = runner self.host = host + self.port = port + if port is None: + self.port = self.runner.remote_port def _get_conn(self): ssh = paramiko.SSHClient() @@ -75,9 +78,13 @@ class ParamikoConnection(object): try: ssh.connect( - self.host, username=self.runner.remote_user, - allow_agent=True, look_for_keys=True, password=self.runner.remote_pass, - timeout=self.runner.timeout, port=self.runner.remote_port + self.host, + username=self.runner.remote_user, + allow_agent=True, + look_for_keys=True, + password=self.runner.remote_pass, + timeout=self.runner.timeout, + port=self.port ) except Exception, e: if str(e).find("PID check failed") != -1: @@ -183,7 +190,7 @@ class LocalConnection(object): self.runner = runner self.host = host - def connect(self): + def connect(self, port=None): ''' connect to the local host; nothing to do here ''' return self diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py index c18e60c7418..c5c843c9fdb 100755 --- a/lib/ansible/playbook.py +++ b/lib/ansible/playbook.py @@ -304,18 +304,21 @@ class PlayBook(object): # ***************************************************** def _run_module(self, pattern, module, args, vars, remote_user, - async_seconds, async_poll_interval, only_if, sudo, transport): + async_seconds, async_poll_interval, only_if, sudo, transport, port): ''' run a particular module step in a playbook ''' hosts = [ h for h in self.inventory.list_hosts() if (h not in self.stats.failures) and (h not in self.stats.dark)] self.inventory.restrict_to(hosts) + if port is None: + port=self.remote_port + runner = ansible.runner.Runner( pattern=pattern, inventory=self.inventory, module_name=module, module_args=args, forks=self.forks, remote_pass=self.remote_pass, module_path=self.module_path, timeout=self.timeout, remote_user=remote_user, - remote_port=self.remote_port, module_vars=vars, + remote_port=port, module_vars=vars, setup_cache=SETUP_CACHE, basedir=self.basedir, conditional=only_if, callbacks=self.runner_callbacks, extra_vars=self.extra_vars, debug=self.debug, sudo=sudo, @@ -333,7 +336,7 @@ class PlayBook(object): # ***************************************************** def _run_task(self, pattern=None, task=None, - remote_user=None, handlers=None, conditional=False, sudo=False, transport=None): + remote_user=None, handlers=None, conditional=False, sudo=False, transport=None, port=None): ''' run a single task in the playbook and recursively run any subtasks. ''' # load the module name and parameters from the task entry @@ -365,7 +368,7 @@ class PlayBook(object): # run the task in parallel results = self._run_module(pattern, module_name, module_args, module_vars, remote_user, async_seconds, - async_poll_interval, only_if, sudo, transport) + async_poll_interval, only_if, sudo, transport, port) self.stats.compute(results) @@ -483,7 +486,7 @@ class PlayBook(object): module_args=vars, inventory=self.inventory, forks=self.forks, module_path=self.module_path, timeout=self.timeout, remote_user=user, - remote_pass=self.remote_pass, remote_port=self.remote_port, + remote_pass=self.remote_pass, remote_port=port, setup_cache=SETUP_CACHE, callbacks=self.runner_callbacks, sudo=sudo, debug=self.debug, transport=transport, sudo_pass=self.sudo_pass, is_playbook=True @@ -546,7 +549,8 @@ class PlayBook(object): handlers=handlers, remote_user=user, sudo=sudo, - transport=transport + transport=transport, + port=port ) # handlers only run on certain nodes, they are flagged by _flag_handlers @@ -566,7 +570,8 @@ class PlayBook(object): conditional=True, remote_user=user, sudo=sudo, - transport=transport + transport=transport, + port=port ) self.inventory.lift_restriction() diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index 186feb34781..d5d844bc120 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -68,16 +68,32 @@ def _executor_hook(job_queue, result_queue): class Runner(object): - def __init__(self, host_list=C.DEFAULT_HOST_LIST, module_path=C.DEFAULT_MODULE_PATH, - module_name=C.DEFAULT_MODULE_NAME, module_args=C.DEFAULT_MODULE_ARGS, - forks=C.DEFAULT_FORKS, timeout=C.DEFAULT_TIMEOUT, pattern=C.DEFAULT_PATTERN, - remote_user=C.DEFAULT_REMOTE_USER, remote_pass=C.DEFAULT_REMOTE_PASS, - sudo_pass=C.DEFAULT_SUDO_PASS, remote_port=C.DEFAULT_REMOTE_PORT, background=0, - basedir=None, setup_cache=None, transport=C.DEFAULT_TRANSPORT, - conditional='True', groups={}, callbacks=None, verbose=False, - debug=False, sudo=False, extra_vars=None, - module_vars=None, is_playbook=False, inventory=None): - + def __init__(self, + host_list=C.DEFAULT_HOST_LIST, + module_path=C.DEFAULT_MODULE_PATH, + module_name=C.DEFAULT_MODULE_NAME, + module_args=C.DEFAULT_MODULE_ARGS, + forks=C.DEFAULT_FORKS, + timeout=C.DEFAULT_TIMEOUT, + pattern=C.DEFAULT_PATTERN, + remote_user=C.DEFAULT_REMOTE_USER, + remote_pass=C.DEFAULT_REMOTE_PASS, + remote_port=C.DEFAULT_REMOTE_PORT, + sudo_pass=C.DEFAULT_SUDO_PASS, + background=0, + basedir=None, + setup_cache=None, + transport=C.DEFAULT_TRANSPORT, + conditional='True', + groups={}, + callbacks=None, + verbose=False, + debug=False, + sudo=False, + extra_vars=None, + module_vars=None, + is_playbook=False, + inventory=None): if setup_cache is None: setup_cache = {} if basedir is None: @@ -132,25 +148,16 @@ class Runner(object): @classmethod def parse_hosts(cls, host_list, override_hosts=None, extra_vars=None): ''' parse the host inventory file, returns (hosts, groups) ''' + if override_hosts is None: inventory = ansible.inventory.Inventory(host_list, extra_vars) else: - inventory = ansible.inventory.Inventory(override_hosts) + inventory = ansible.inventory.Inventory(override_hosts, extra_vars) return inventory.host_list, inventory.groups # ***************************************************** - def _connect(self, host): - ''' connects to a host, returns (is_successful, connection_object OR traceback_string) ''' - - try: - return [ True, self.connector.connect(host) ] - except errors.AnsibleConnectionFailed, e: - return [ False, "FAILED: %s" % str(e) ] - - # ***************************************************** - def _return_from_module(self, conn, host, result, err, executed=None): ''' helper function to handle JSON parsing of results ''' @@ -510,10 +517,15 @@ class Runner(object): def _executor_internal(self, host): ''' callback executed in parallel for each host. returns (hostname, connected_ok, extra) ''' - ok, conn = self._connect(host) - if not ok: - return [ host, False, conn , None] - + host_variables = self.inventory.get_variables(host, self.extra_vars) + port = host_variables.get('ansible_ssh_port', self.remote_port) + + conn = None + try: + conn = self.connector.connect(host, port) + except errors.AnsibleConnectionFailed, e: + return [ host, False, "FAILED: %s" % str(e), None ] + cache = self.setup_cache.get(host, {}) module_name = utils.template(self.module_name, cache) From 8a027415d0f1cff803b81df4d821f56445b14c66 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 16 Apr 2012 22:04:23 -0400 Subject: [PATCH 12/83] Port shouldn't be in this example --- examples/playbooks/intro_example.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/playbooks/intro_example.yml b/examples/playbooks/intro_example.yml index 7ee6fe7991e..1fb31d2688b 100644 --- a/examples/playbooks/intro_example.yml +++ b/examples/playbooks/intro_example.yml @@ -9,7 +9,6 @@ - hosts: all user: root - port: 3000 # could have also have done: # user: mdehaan From 35fdf6636b7ab462a6fa11221137b4c4d9b241c4 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 16 Apr 2012 22:15:55 -0400 Subject: [PATCH 13/83] Allow --user for playbooks, no need for port setting as can specify in inventory file now. --- bin/ansible | 7 +++---- bin/ansible-playbook | 21 +++++++++++++++------ lib/ansible/playbook.py | 6 ++++-- lib/ansible/utils.py | 7 +------ 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/bin/ansible b/bin/ansible index 844004ecc4d..b9a393f21bd 100755 --- a/bin/ansible +++ b/bin/ansible @@ -47,7 +47,7 @@ class Cli(object): def parse(self): ''' create an options parser for bin/ansible ''' - parser = utils.base_parser(constants=C, port_opts=True, runas_opts=True, async_opts=True, + parser = utils.base_parser(constants=C, runas_opts=True, async_opts=True, output_opts=True, connect_opts=True, usage='%prog [options]') parser.add_option('-a', '--args', dest='module_args', help="module arguments", default=C.DEFAULT_MODULE_ARGS) @@ -87,7 +87,7 @@ class Cli(object): module_args=options.module_args, remote_user=options.remote_user, remote_pass=sshpass, host_list=options.inventory, timeout=options.timeout, - remote_port=options.remote_port, forks=options.forks, + forks=options.forks, background=options.seconds, pattern=pattern, callbacks=self.callbacks, sudo=options.sudo, sudo_pass=sudopass, verbose=True, @@ -104,8 +104,7 @@ class Cli(object): module_args="jid=%s" % jid, remote_user=old_runner.remote_user, remote_pass=old_runner.remote_pass, inventory=old_runner.inventory, timeout=old_runner.timeout, forks=old_runner.forks, - remote_port=old_runner.remote_port, pattern='*', - callbacks=self.silent_callbacks, verbose=True, + pattern='*', callbacks=self.silent_callbacks, verbose=True, ) # ---------------------------------------------- diff --git a/bin/ansible-playbook b/bin/ansible-playbook index 9542e34b9ff..a84b30705cc 100755 --- a/bin/ansible-playbook +++ b/bin/ansible-playbook @@ -33,7 +33,7 @@ def main(args): # create parser for CLI options usage = "%prog playbook.yml" - parser = utils.base_parser(constants=C, usage=usage, connect_opts=True) + parser = utils.base_parser(constants=C, usage=usage, connect_opts=True, runas_opts=True) parser.add_option('-e', '--extra-vars', dest='extra_vars', help='arguments to pass to the inventory script') parser.add_option('-O', '--override-hosts', dest="override_hosts", default=None, @@ -63,13 +63,22 @@ def main(args): runner_cb = callbacks.PlaybookRunnerCallbacks(stats) pb = ansible.playbook.PlayBook( - playbook=playbook,module_path=options.module_path, - host_list=options.inventory, override_hosts=override_hosts, + playbook=playbook, + module_path=options.module_path, + host_list=options.inventory, + override_hosts=override_hosts, extra_vars=options.extra_vars, - forks=options.forks, debug=options.debug, verbose=True, + forks=options.forks, + debug=options.debug, + verbose=True, + remote_user=options.remote_user, remote_pass=sshpass, - callbacks=playbook_cb, runner_callbacks=runner_cb, stats=stats, - timeout=options.timeout, transport=options.connection, + callbacks=playbook_cb, + runner_callbacks=runner_cb, + stats=stats, + timeout=options.timeout, + transport=options.connection, + sudo=options.sudo, sudo_pass=sudopass ) try: diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py index c5c843c9fdb..4df28389f03 100755 --- a/lib/ansible/playbook.py +++ b/lib/ansible/playbook.py @@ -64,7 +64,8 @@ class PlayBook(object): verbose = False, callbacks = None, runner_callbacks = None, - stats = None): + stats = None, + sudo = False): if playbook is None or callbacks is None or runner_callbacks is None or stats is None: raise Exception('missing required arguments') @@ -83,6 +84,7 @@ class PlayBook(object): self.override_hosts = override_hosts self.extra_vars = extra_vars self.stats = stats + self.sudo = sudo self.sudo_pass = sudo_pass self.basedir = os.path.dirname(playbook) @@ -529,7 +531,7 @@ class PlayBook(object): handlers = pg.get('handlers', []) user = pg.get('user', self.remote_user) port = pg.get('port', self.remote_port) - sudo = pg.get('sudo', False) + sudo = pg.get('sudo', self.sudo) transport = pg.get('connection', self.transport) self.callbacks.on_play_start(pattern) diff --git a/lib/ansible/utils.py b/lib/ansible/utils.py index bcbb5904fd7..d1bb9490e68 100755 --- a/lib/ansible/utils.py +++ b/lib/ansible/utils.py @@ -279,7 +279,7 @@ class SortedOptParser(optparse.OptionParser): self.option_list.sort(key=methodcaller('get_opt_string')) return optparse.OptionParser.format_help(self, formatter=None) -def base_parser(constants=C, usage="", output_opts=False, port_opts=False, runas_opts=False, async_opts=False, connect_opts=False): +def base_parser(constants=C, usage="", output_opts=False, runas_opts=False, async_opts=False, connect_opts=False): ''' create an options parser for any ansible script ''' parser = SortedOptParser(usage) @@ -301,11 +301,6 @@ def base_parser(constants=C, usage="", output_opts=False, port_opts=False, runas dest='timeout', help="override the SSH timeout in seconds (default=%s)" % constants.DEFAULT_TIMEOUT) - if port_opts: - parser.add_option('-p', '--port', default=constants.DEFAULT_REMOTE_PORT, type='int', - dest='remote_port', - help="override the remote ssh port (default=%s)" % constants.DEFAULT_REMOTE_PORT) - if output_opts: parser.add_option('-o', '--one-line', dest='one_line', action='store_true', help='condense output') From b24ec71ca375192cb8f41e4949ddf1dd0db1487d Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 16 Apr 2012 22:22:37 -0400 Subject: [PATCH 14/83] Update docs to reflect --port going away and --sudo and --remote-user being added to ansible-playbook --- docs/man/man1/ansible-playbook.1 | 12 ++++++++++-- docs/man/man1/ansible-playbook.1.asciidoc | 10 ++++++++++ docs/man/man1/ansible.1 | 4 ++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/docs/man/man1/ansible-playbook.1 b/docs/man/man1/ansible-playbook.1 index b4c4b80e245..df0a28f7436 100644 --- a/docs/man/man1/ansible-playbook.1 +++ b/docs/man/man1/ansible-playbook.1 @@ -2,12 +2,12 @@ .\" Title: ansible-playbook .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.75.2 -.\" Date: 04/13/2012 +.\" Date: 04/16/2012 .\" Manual: System administration commands .\" Source: Ansible 0.0.2 .\" Language: English .\" -.TH "ANSIBLE\-PLAYBOOK" "1" "04/13/2012" "Ansible 0\&.0\&.2" "System administration commands" +.TH "ANSIBLE\-PLAYBOOK" "1" "04/16/2012" "Ansible 0\&.0\&.2" "System administration commands" .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- @@ -88,6 +88,14 @@ Ignore the inventory file and run the playbook against only these hosts\&. "host \fIall\fR when using this option\&. .RE +.sp +\fB\-s\fR, \fB\-\-sudo\fR +.sp +Force all plays to use sudo, even if not marked as such\&. +.sp +\fB\-u\fR \fIUSERNAME\fR, \fB\-\-remote\-user=\fR\fIUSERNAME\fR +.sp +Use this remote user name on playbook steps that do not indicate a user name to run as\&. .SH "ENVIRONMENT" .sp The following environment variables may specified\&. diff --git a/docs/man/man1/ansible-playbook.1.asciidoc b/docs/man/man1/ansible-playbook.1.asciidoc index 6874461d76e..ce43e87f552 100644 --- a/docs/man/man1/ansible-playbook.1.asciidoc +++ b/docs/man/man1/ansible-playbook.1.asciidoc @@ -81,6 +81,16 @@ Ignore the inventory file and run the playbook against only these hosts. "hosts in playbook should be set to 'all' when using this option. +*-s*, *--sudo* + +Force all plays to use sudo, even if not marked as such. + + +*-u* 'USERNAME', *--remote-user=*'USERNAME' + +Use this remote user name on playbook steps that do not indicate a user name to run as. + + ENVIRONMENT ----------- diff --git a/docs/man/man1/ansible.1 b/docs/man/man1/ansible.1 index 07c9c21a21b..550415ada06 100644 --- a/docs/man/man1/ansible.1 +++ b/docs/man/man1/ansible.1 @@ -2,12 +2,12 @@ .\" Title: ansible .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.75.2 -.\" Date: 04/13/2012 +.\" Date: 04/16/2012 .\" Manual: System administration commands .\" Source: Ansible 0.0.2 .\" Language: English .\" -.TH "ANSIBLE" "1" "04/13/2012" "Ansible 0\&.0\&.2" "System administration commands" +.TH "ANSIBLE" "1" "04/16/2012" "Ansible 0\&.0\&.2" "System administration commands" .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- From 9ce27be8786e58c7f56bbb320bf7dc4ebcbecfab Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 16 Apr 2012 23:03:04 -0400 Subject: [PATCH 15/83] Remove extra_vars tests --- docs/man/man1/ansible-playbook.1 | 5 ----- docs/man/man1/ansible-playbook.1.asciidoc | 6 ------ lib/ansible/inventory.py | 18 ++++++------------ lib/ansible/playbook.py | 12 +----------- lib/ansible/runner.py | 14 ++++++-------- test/TestInventory.py | 18 ------------------ 6 files changed, 13 insertions(+), 60 deletions(-) diff --git a/docs/man/man1/ansible-playbook.1 b/docs/man/man1/ansible-playbook.1 index df0a28f7436..5fb260bb0cd 100644 --- a/docs/man/man1/ansible-playbook.1 +++ b/docs/man/man1/ansible-playbook.1 @@ -77,11 +77,6 @@ Connection timeout to use when trying to talk to hosts, in \fISECONDS\fR\&. .RE .PP -\fB\-e\fR \fIEXTRA_VARS\fR, \fB\-\-extra_vars=\fR\fIEXTRA_VARS\fR -.RS 4 -An additional list of space delimited key=value pairs to pass into the playbook that are not declared in the vars section of the playbook\&. -.RE -.PP \fB\-O\fR \fIOVERRIDE_HOSTS\fR, \fB\-\-override\-hosts=\fR\fIOVERRIDE_HOSTS\fR .RS 4 Ignore the inventory file and run the playbook against only these hosts\&. "hosts:" line in playbook should be set to diff --git a/docs/man/man1/ansible-playbook.1.asciidoc b/docs/man/man1/ansible-playbook.1.asciidoc index ce43e87f552..14d34710e71 100644 --- a/docs/man/man1/ansible-playbook.1.asciidoc +++ b/docs/man/man1/ansible-playbook.1.asciidoc @@ -69,12 +69,6 @@ Prompt for the password to use for playbook plays that request sudo access, if a Connection timeout to use when trying to talk to hosts, in 'SECONDS'. -*-e* 'EXTRA_VARS', *--extra_vars=*'EXTRA_VARS':: - -An additional list of space delimited key=value pairs to pass into the playbook that are not -declared in the vars section of the playbook. - - *-O* 'OVERRIDE_HOSTS', *--override-hosts=*'OVERRIDE_HOSTS':: Ignore the inventory file and run the playbook against only these hosts. "hosts:" line diff --git a/lib/ansible/inventory.py b/lib/ansible/inventory.py index 05f8001fb12..e98cd3309fa 100644 --- a/lib/ansible/inventory.py +++ b/lib/ansible/inventory.py @@ -32,7 +32,7 @@ class Inventory(object): systems, or a script that will be called with --list or --host. """ - def __init__(self, host_list=C.DEFAULT_HOST_LIST, extra_vars=None): + def __init__(self, host_list=C.DEFAULT_HOST_LIST): self._restriction = None self._variables = {} @@ -50,7 +50,7 @@ class Inventory(object): 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(extra_vars) + self.host_list, self.groups = self._parse_from_script() self._is_script = True else: self.host_list, self.groups = self._parse_from_file() @@ -78,7 +78,7 @@ class Inventory(object): """ Do not restrict list operations """ self._restriction = None - def get_variables(self, host, extra_vars=None): + def get_variables(self, host): """ Return the variables associated with this host. """ if host in self._variables: @@ -87,7 +87,7 @@ class Inventory(object): if not self._is_script: return {} - return self._get_variables_from_script(host, extra_vars) + return self._get_variables_from_script(host) # ***************************************************** @@ -126,7 +126,7 @@ class Inventory(object): # ***************************************************** - def _parse_from_script(self, extra_vars=None): + def _parse_from_script(self): ''' evaluate a script that returns list of hosts by groups ''' results = [] @@ -134,9 +134,6 @@ class Inventory(object): cmd = [self.inventory_file, '--list'] - if extra_vars: - cmd.extend(['--extra-vars', extra_vars]) - cmd = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False) out, err = cmd.communicate() rc = cmd.returncode @@ -241,14 +238,11 @@ class Inventory(object): raise AnsibleError("Unknown item in inventory: %s"%(item)) - def _get_variables_from_script(self, host, extra_vars=None): + def _get_variables_from_script(self, host): ''' support per system variabes from external variable scripts, see web docs ''' cmd = [self.inventory_file, '--host', host] - if extra_vars: - cmd.extend(['--extra-vars', extra_vars]) - cmd = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py index 4df28389f03..781e598b08e 100755 --- a/lib/ansible/playbook.py +++ b/lib/ansible/playbook.py @@ -59,7 +59,6 @@ class PlayBook(object): remote_port = C.DEFAULT_REMOTE_PORT, transport = C.DEFAULT_TRANSPORT, override_hosts = None, - extra_vars = None, debug = False, verbose = False, callbacks = None, @@ -82,7 +81,6 @@ class PlayBook(object): self.callbacks = callbacks self.runner_callbacks = runner_callbacks self.override_hosts = override_hosts - self.extra_vars = extra_vars self.stats = stats self.sudo = sudo self.sudo_pass = sudo_pass @@ -323,7 +321,7 @@ class PlayBook(object): remote_port=port, module_vars=vars, setup_cache=SETUP_CACHE, basedir=self.basedir, conditional=only_if, callbacks=self.runner_callbacks, - extra_vars=self.extra_vars, debug=self.debug, sudo=sudo, + debug=self.debug, sudo=sudo, transport=transport, sudo_pass=self.sudo_pass, is_playbook=True ) @@ -505,14 +503,6 @@ class PlayBook(object): for (host, result) in setup_ok.iteritems(): SETUP_CACHE[host] = result - if self.extra_vars: - extra_vars = utils.parse_kv(self.extra_vars) - for h in self.host_list: - try: - SETUP_CACHE[h].update(extra_vars) - except: - SETUP_CACHE[h] = extra_vars - # ***************************************************** def _run_play(self, pg): diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index d5d844bc120..66105f0ee72 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -90,7 +90,6 @@ class Runner(object): verbose=False, debug=False, sudo=False, - extra_vars=None, module_vars=None, is_playbook=False, inventory=None): @@ -109,7 +108,7 @@ class Runner(object): self.connector = ansible.connection.Connection(self, self.transport) if inventory is None: - self.inventory = ansible.inventory.Inventory(host_list, extra_vars) + self.inventory = ansible.inventory.Inventory(host_list) else: self.inventory = inventory @@ -121,7 +120,6 @@ class Runner(object): self.pattern = pattern self.module_args = module_args self.module_vars = module_vars - self.extra_vars = extra_vars self.timeout = timeout self.debug = debug self.verbose = verbose @@ -146,13 +144,13 @@ class Runner(object): # ***************************************************** @classmethod - def parse_hosts(cls, host_list, override_hosts=None, extra_vars=None): + 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, extra_vars) + inventory = ansible.inventory.Inventory(host_list) else: - inventory = ansible.inventory.Inventory(override_hosts, extra_vars) + inventory = ansible.inventory.Inventory(override_hosts) return inventory.host_list, inventory.groups @@ -267,7 +265,7 @@ class Runner(object): if not eval(conditional): return [ utils.smjson(dict(skipped=True)), None, 'skipped' ] - host_variables = self.inventory.get_variables(conn.host, self.extra_vars) + host_variables = self.inventory.get_variables(conn.host) inject.update(host_variables) if self.module_name == 'setup': @@ -517,7 +515,7 @@ class Runner(object): def _executor_internal(self, host): ''' callback executed in parallel for each host. returns (hostname, connected_ok, extra) ''' - host_variables = self.inventory.get_variables(host, self.extra_vars) + host_variables = self.inventory.get_variables(host) port = host_variables.get('ansible_ssh_port', self.remote_port) conn = None diff --git a/test/TestInventory.py b/test/TestInventory.py index 4f48db14f65..750cbf189ac 100644 --- a/test/TestInventory.py +++ b/test/TestInventory.py @@ -87,12 +87,6 @@ class TestInventory(unittest.TestCase): assert vars == {} - def test_simple_extra_vars(self): - inventory = self.simple_inventory() - vars = inventory.get_variables('thor', 'a=5') - - assert vars == {} - def test_simple_port(self): inventory = self.simple_inventory() vars = inventory.get_variables('hera') @@ -154,12 +148,6 @@ class TestInventory(unittest.TestCase): assert vars == {"hammer":True} - def test_script_extra_vars(self): - inventory = self.script_inventory() - vars = inventory.get_variables('thor', 'simple=yes') - - assert vars == {"hammer":True, "simple": "yes"} - ### Tests for yaml inventory file def test_yaml(self): @@ -234,12 +222,6 @@ class TestInventory(unittest.TestCase): assert vars == {"moon":"titan"} - def test_yaml_extra_vars(self): - inventory = self.yaml_inventory() - vars = inventory.get_variables('thor', 'a=5') - - assert vars == {"hammer":True} - def test_yaml_port(self): inventory = self.yaml_inventory() vars = inventory.get_variables('hera') From 377488aa2a4f5f9f561ae37a70d446cc81869fd1 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 16 Apr 2012 23:06:27 -0400 Subject: [PATCH 16/83] remove --extra-vars since the new YAML inventory stuff wasn't quite using it as designed and it is getting hard to follow with that and SETUP_CACHE etc --- bin/ansible-playbook | 3 --- 1 file changed, 3 deletions(-) diff --git a/bin/ansible-playbook b/bin/ansible-playbook index a84b30705cc..a3818e5585e 100755 --- a/bin/ansible-playbook +++ b/bin/ansible-playbook @@ -34,8 +34,6 @@ def main(args): # create parser for CLI options usage = "%prog playbook.yml" parser = utils.base_parser(constants=C, usage=usage, connect_opts=True, runas_opts=True) - parser.add_option('-e', '--extra-vars', dest='extra_vars', - help='arguments to pass to the inventory script') parser.add_option('-O', '--override-hosts', dest="override_hosts", default=None, help="run playbook against these hosts regardless of inventory settings") @@ -67,7 +65,6 @@ def main(args): module_path=options.module_path, host_list=options.inventory, override_hosts=override_hosts, - extra_vars=options.extra_vars, forks=options.forks, debug=options.debug, verbose=True, From 78b5cd64d0a5d396063212e905cbd135b6a329ad Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 16 Apr 2012 23:45:15 -0400 Subject: [PATCH 17/83] Add pydoc for constructors. Some arguments can be trimmed as we plan to remove the need for them (like setup_cache and maybe module_vars) with various pending refactoring. --- lib/ansible/playbook.py | 18 ++++++++++++++++++ lib/ansible/runner.py | 26 +++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py index 781e598b08e..542e86a6300 100755 --- a/lib/ansible/playbook.py +++ b/lib/ansible/playbook.py @@ -66,6 +66,24 @@ class PlayBook(object): stats = None, sudo = False): + """ + playbook: path to a playbook file + host_list: path to a file like /etc/ansible/hosts + module_path: path to ansible modules, like /usr/share/ansible/ + forks: desired level of paralellism + timeout: connection timeout + remote_user: run as this user if not specified in a particular play + remote_pass: use this remote password (for all plays) vs using SSH keys + sudo_pass: if sudo==True, and a password is required, this is the sudo password + remote_port: default remote port to use if not specified with the host or play + transport: how to connect to hosts that don't specify a transport (local, paramiko, etc) + override_hosts: skip the inventory file, just talk to these hosts + callbacks output callbacks for the playbook + runner_callbacks: more callbacks, this time for the runner API + stats: holds aggregrate data about events occuring to each host + sudo: if not specified per play, requests all plays use sudo mode + """ + if playbook is None or callbacks is None or runner_callbacks is None or stats is None: raise Exception('missing required arguments') diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index 66105f0ee72..894800fc3cd 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -85,7 +85,6 @@ class Runner(object): setup_cache=None, transport=C.DEFAULT_TRANSPORT, conditional='True', - groups={}, callbacks=None, verbose=False, debug=False, @@ -93,6 +92,31 @@ class Runner(object): module_vars=None, is_playbook=False, inventory=None): + + """ + host_list : path to a host list file, like /etc/ansible/hosts + module_path : path to modules, like /usr/share/ansible + module_name : which module to run (string) + module_args : args to pass to the module (string) + forks : desired level of paralellism (hosts to run on at a time) + timeout : connection timeout, such as a SSH timeout, in seconds + pattern : pattern or groups to select from in inventory + remote_user : connect as this remote username + remote_pass : supply this password (if not using keys) + remote_port : use this default remote port (if not set by the inventory system) + sudo_pass : sudo password if using sudo and sudo requires a password + background : run asynchronously with a cap of this many # of seconds (if not 0) + basedir : paths used by modules if not absolute are relative to here + setup_cache : this is a internalism that is going away + transport : transport mode (paramiko, local) + conditional : only execute if this string, evaluated, is True + callbacks : output callback class + sudo : log in as remote user and immediately sudo to root + module_vars : provides additional variables to a template. FIXME: just use module_args, remove + is_playbook : indicates Runner is being used by a playbook. affects behavior in various ways. + inventory : inventory object, if host_list is not provided + """ + if setup_cache is None: setup_cache = {} if basedir is None: From 3f26a1c7f601c40897e8b458daab2c670ba5b7a6 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 16 Apr 2012 23:47:41 -0400 Subject: [PATCH 18/83] verbose option is not being used, so remove it. debug variable still exists. --- bin/ansible | 2 +- bin/ansible-playbook | 1 - lib/ansible/playbook.py | 2 -- lib/ansible/runner.py | 2 -- test/TestPlayBook.py | 1 - test/TestRunner.py | 1 - 6 files changed, 1 insertion(+), 8 deletions(-) diff --git a/bin/ansible b/bin/ansible index b9a393f21bd..9eb5d205d2c 100755 --- a/bin/ansible +++ b/bin/ansible @@ -90,7 +90,7 @@ class Cli(object): forks=options.forks, background=options.seconds, pattern=pattern, callbacks=self.callbacks, sudo=options.sudo, - sudo_pass=sudopass, verbose=True, + sudo_pass=sudopass, transport=options.connection, debug=options.debug ) return (runner, runner.run()) diff --git a/bin/ansible-playbook b/bin/ansible-playbook index a3818e5585e..403ac19ee47 100755 --- a/bin/ansible-playbook +++ b/bin/ansible-playbook @@ -67,7 +67,6 @@ def main(args): override_hosts=override_hosts, forks=options.forks, debug=options.debug, - verbose=True, remote_user=options.remote_user, remote_pass=sshpass, callbacks=playbook_cb, diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py index 542e86a6300..3e8a79d364e 100755 --- a/lib/ansible/playbook.py +++ b/lib/ansible/playbook.py @@ -60,7 +60,6 @@ class PlayBook(object): transport = C.DEFAULT_TRANSPORT, override_hosts = None, debug = False, - verbose = False, callbacks = None, runner_callbacks = None, stats = None, @@ -95,7 +94,6 @@ class PlayBook(object): self.remote_port = remote_port self.transport = transport self.debug = debug - self.verbose = verbose self.callbacks = callbacks self.runner_callbacks = runner_callbacks self.override_hosts = override_hosts diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index 894800fc3cd..958ae899955 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -86,7 +86,6 @@ class Runner(object): transport=C.DEFAULT_TRANSPORT, conditional='True', callbacks=None, - verbose=False, debug=False, sudo=False, module_vars=None, @@ -146,7 +145,6 @@ class Runner(object): self.module_vars = module_vars self.timeout = timeout self.debug = debug - self.verbose = verbose self.remote_user = remote_user self.remote_pass = remote_pass self.remote_port = remote_port diff --git a/test/TestPlayBook.py b/test/TestPlayBook.py index 9e04e254320..97e3f102c3b 100644 --- a/test/TestPlayBook.py +++ b/test/TestPlayBook.py @@ -136,7 +136,6 @@ class TestPlaybook(unittest.TestCase): timeout = 5, remote_user = self.user, remote_pass = None, - verbose = False, stats = ans_callbacks.AggregateStats(), callbacks = self.test_callbacks, runner_callbacks = self.test_callbacks diff --git a/test/TestRunner.py b/test/TestRunner.py index 20c2c4e24cb..d61466040f3 100644 --- a/test/TestRunner.py +++ b/test/TestRunner.py @@ -29,7 +29,6 @@ class TestRunner(unittest.TestCase): forks=1, background=0, pattern='all', - verbose=True, ) self.cwd = os.getcwd() self.test_dir = os.path.join(self.cwd, 'test') From da6cb1ca6e2914c3287d687217e79033bec2f276 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 16 Apr 2012 23:51:03 -0400 Subject: [PATCH 19/83] Less scrolling over constructor params now that documentation follows --- lib/ansible/runner.py | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index 958ae899955..1fced2c589c 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -69,28 +69,15 @@ def _executor_hook(job_queue, result_queue): class Runner(object): def __init__(self, - host_list=C.DEFAULT_HOST_LIST, - module_path=C.DEFAULT_MODULE_PATH, - module_name=C.DEFAULT_MODULE_NAME, - module_args=C.DEFAULT_MODULE_ARGS, - forks=C.DEFAULT_FORKS, - timeout=C.DEFAULT_TIMEOUT, - pattern=C.DEFAULT_PATTERN, - remote_user=C.DEFAULT_REMOTE_USER, - remote_pass=C.DEFAULT_REMOTE_PASS, - remote_port=C.DEFAULT_REMOTE_PORT, - sudo_pass=C.DEFAULT_SUDO_PASS, - background=0, - basedir=None, - setup_cache=None, - transport=C.DEFAULT_TRANSPORT, - conditional='True', - callbacks=None, - debug=False, - sudo=False, - module_vars=None, - is_playbook=False, - inventory=None): + host_list=C.DEFAULT_HOST_LIST, module_path=C.DEFAULT_MODULE_PATH, + module_name=C.DEFAULT_MODULE_NAME, module_args=C.DEFAULT_MODULE_ARGS, + forks=C.DEFAULT_FORKS, timeout=C.DEFAULT_TIMEOUT, + pattern=C.DEFAULT_PATTERN, remote_user=C.DEFAULT_REMOTE_USER, + remote_pass=C.DEFAULT_REMOTE_PASS, remote_port=C.DEFAULT_REMOTE_PORT, + sudo_pass=C.DEFAULT_SUDO_PASS, background=0, basedir=None, + setup_cache=None, transport=C.DEFAULT_TRANSPORT, conditional='True', + callbacks=None, debug=False, sudo=False, module_vars=None, + is_playbook=False, inventory=None): """ host_list : path to a host list file, like /etc/ansible/hosts From 9e0b7ee6292afaf0568a7795bed46b99e9a7e7e4 Mon Sep 17 00:00:00 2001 From: Jeroen Hoekx Date: Tue, 17 Apr 2012 11:14:58 +0200 Subject: [PATCH 20/83] template: expand path if metadata is in user home. --- library/template | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/template b/library/template index 0b13422d5ab..31a2095e502 100755 --- a/library/template +++ b/library/template @@ -47,7 +47,8 @@ for x in items: source = params['src'] dest = params['dest'] -metadata = params.get('metadata', '/etc/ansible/setup') +metadata = params.get('metadata', '/etc/ansible/setup') +metadata = os.path.expanduser(metadata) module_vars = params.get('vars') # raise an error if there is no template metadata From 6f09b41eb5851cab2c0b8d8e91998977405e98b4 Mon Sep 17 00:00:00 2001 From: Jeroen Hoekx Date: Tue, 17 Apr 2012 13:30:30 +0200 Subject: [PATCH 21/83] Don't test modules that aren't present. Also account for path difference in sleep in different distributions. --- test/TestRunner.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/test/TestRunner.py b/test/TestRunner.py index d61466040f3..fc890125a56 100644 --- a/test/TestRunner.py +++ b/test/TestRunner.py @@ -14,6 +14,15 @@ try: except: import simplejson as json +from nose.plugins.skip import SkipTest + +def get_binary(name): + for directory in os.environ["PATH"].split(os.pathsep): + path = os.path.join(directory, name) + if os.path.isfile(path) and os.access(path, os.X_OK): + return path + return None + class TestRunner(unittest.TestCase): def setUp(self): @@ -73,6 +82,8 @@ class TestRunner(unittest.TestCase): assert "ping" in result def test_facter(self): + if not get_binary("facter"): + raise SkipTest result = self._run('facter',[]) assert "hostname" in result @@ -172,7 +183,7 @@ class TestRunner(unittest.TestCase): def test_async(self): # test async launch and job status # of any particular module - result = self._run('command', [ "/bin/sleep", "3" ], background=20) + result = self._run('command', [ get_binary("sleep"), "3" ], background=20) assert 'ansible_job_id' in result assert 'started' in result jid = result['ansible_job_id'] @@ -197,6 +208,8 @@ class TestRunner(unittest.TestCase): assert open(input).read() == open(output).read() def test_yum(self): + if not get_binary("yum"): + raise SkipTest result = self._run('yum', [ "list=repos" ]) assert 'failed' not in result From aa555b8b16e74f011db4e0e14a59680c3cd23a7f Mon Sep 17 00:00:00 2001 From: Jeroen Hoekx Date: Tue, 17 Apr 2012 15:29:59 +0200 Subject: [PATCH 22/83] Inventory: AnsibleError is not global... --- lib/ansible/inventory.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ansible/inventory.py b/lib/ansible/inventory.py index e98cd3309fa..3295f3f2dd8 100644 --- a/lib/ansible/inventory.py +++ b/lib/ansible/inventory.py @@ -218,7 +218,7 @@ class Inventory(object): def set_variables(host, variables): for variable in variables: if len(variable) != 1: - raise AnsibleError("Only one item expected in %s"%(variable)) + raise errors.AnsibleError("Only one item expected in %s"%(variable)) k, v = variable.items()[0] self._set_variable(host, k, v) @@ -235,7 +235,7 @@ class Inventory(object): return host_name else: - raise AnsibleError("Unknown item in inventory: %s"%(item)) + raise errors.AnsibleError("Unknown item in inventory: %s"%(item)) def _get_variables_from_script(self, host): From 38128c4720f664347604ef92bfa6927c21fbbf0d Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Fri, 13 Apr 2012 15:10:29 -0400 Subject: [PATCH 23/83] Remove asciidoc builddep --- ansible.spec | 1 - 1 file changed, 1 deletion(-) diff --git a/ansible.spec b/ansible.spec index 2a1d366d0db..3ad6138a9f6 100644 --- a/ansible.spec +++ b/ansible.spec @@ -13,7 +13,6 @@ Source0: https://github.com/downloads/ansible/ansible/%{name}-%{version}.tar.gz Url: http://ansible.github.com BuildArch: noarch -BuildRequires: asciidoc BuildRequires: python-devel Requires: python-paramiko From c9ab5ebc6bb9c7f3293b12cd9fd85e69e1844f91 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Fri, 13 Apr 2012 15:10:45 -0400 Subject: [PATCH 24/83] Fix FSF address in virt header. --- library/virt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/virt b/library/virt index 85e88228d1b..e2dee4c182d 100755 --- a/library/virt +++ b/library/virt @@ -10,8 +10,7 @@ This software may be freely redistributed under the terms of the GNU general public license. You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +along with this program. If not, see . """ VIRT_FAILED = 1 From 9316a504c5d09519508ef7296c7c521534b00f87 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Fri, 13 Apr 2012 16:27:56 -0400 Subject: [PATCH 25/83] Fixup specfile --- ansible.spec | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/ansible.spec b/ansible.spec index 3ad6138a9f6..d2a85a0c776 100644 --- a/ansible.spec +++ b/ansible.spec @@ -1,19 +1,20 @@ +%if 0%{?rhel} <= 5 %{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot +%endif Name: ansible Release: 1%{?dist} Summary: Minimal SSH command and control Version: 0.0.2 -BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot Group: Development/Libraries License: GPLv3 -Prefix: %{_prefix} Source0: https://github.com/downloads/ansible/ansible/%{name}-%{version}.tar.gz Url: http://ansible.github.com BuildArch: noarch -BuildRequires: python-devel +BuildRequires: python2-devel Requires: python-paramiko Requires: python-jinja2 @@ -24,16 +25,16 @@ executing commands, running "modules", or executing larger 'playbooks' that can serve as a configuration management or deployment system. %prep -%setup -q -n %{name}-%{version} +%setup -q %build -python setup.py build +%{__python} setup.py build %install -python setup.py install -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES +%{__python} setup.py install -O1 --root=$RPM_BUILD_ROOT mkdir -p $RPM_BUILD_ROOT/etc/ansible/ cp examples/hosts $RPM_BUILD_ROOT/etc/ansible/ -mkdir -p $RPM_BUILD_ROOT/%{_mandir}/man1/ +mkdir -p $RPM_BUILD_ROOT/%{_mandir}/man1/ cp -v docs/man/man1/*.1 $RPM_BUILD_ROOT/%{_mandir}/man1/ mkdir -p $RPM_BUILD_ROOT/%{_datadir}/ansible cp -v library/* $RPM_BUILD_ROOT/%{_datadir}/ansible/ @@ -42,14 +43,13 @@ cp -v library/* $RPM_BUILD_ROOT/%{_datadir}/ansible/ rm -rf $RPM_BUILD_ROOT %files -%doc README.md PKG-INFO %defattr(-,root,root) -%{_mandir}/man1/*.gz -%{python_sitelib}/* +%{python_sitelib}/ansible* %{_bindir}/ansible* -%{_datadir}/ansible/* -%config(noreplace) /etc/ansible/hosts -%config(noreplace) %{_sysconfdir}/ansible/ +%{_datadir}/ansible +%config(noreplace) %{_sysconfdir}/ansible/* +%doc README.md PKG-INFO +%doc %{_mandir}/man1/ansible* %changelog From 2d26d1fd71b0b20f21da3a449ee9f9ba79c890fd Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Tue, 17 Apr 2012 10:31:29 -0400 Subject: [PATCH 26/83] Update %description and auto-add version to manpages Conflicts: docs/man/man1/ansible-playbook.1 docs/man/man1/ansible.1 Update %description --- Makefile | 7 ++++--- ansible.spec | 10 +++++++--- docs/man/.gitignore | 1 + docs/man/man1/ansible-playbook.1 | 15 ++++++++++++--- docs/man/man1/ansible-playbook.1.asciidoc | 2 +- docs/man/man1/ansible.1 | 19 ++++++++++++++----- docs/man/man1/ansible.1.asciidoc | 2 +- 7 files changed, 40 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index 2095cf9ed2d..6de5dd195f5 100644 --- a/Makefile +++ b/Makefile @@ -16,10 +16,10 @@ tests: docs: $(MANPAGES) -%.1: %.1.asciidoc - $(ASCII2MAN) +%.1.asciidoc.gen: %.1.asciidoc ansible.spec + sed "s/%VERSION%/$(RPMVERSION)/" $< > $@ -%.5: %.5.asciidoc +%.1: %.1.asciidoc.gen $(ASCII2MAN) loc: @@ -45,6 +45,7 @@ clean: find . -type f \( -name "*.swp" \) -delete @echo "Cleaning up asciidoc to man transformations and results" find ./docs/man -type f -name "*.xml" -delete + find ./docs/man -type f -name "*.gen" -delete @echo "Cleaning up output from test runs" -rm -rf test/test_data @echo "Cleaning up RPM building stuff" diff --git a/ansible.spec b/ansible.spec index d2a85a0c776..5280eed8c97 100644 --- a/ansible.spec +++ b/ansible.spec @@ -20,9 +20,13 @@ Requires: python-paramiko Requires: python-jinja2 %description -Ansible is a extra-simple tool/API for doing 'parallel remote things' over SSH -executing commands, running "modules", or executing larger 'playbooks' that -can serve as a configuration management or deployment system. + +Ansible is a radically simple model-driven configuration management, +multi-node deployment, and remote task execution system. Ansible works +over SSH and does not require any software or daemons to be installed +on remote nodes. Extension modules can be written in any language and +are transferred to managed machines automatically. + %prep %setup -q diff --git a/docs/man/.gitignore b/docs/man/.gitignore index 6722cd96e78..ceb714c17d9 100644 --- a/docs/man/.gitignore +++ b/docs/man/.gitignore @@ -1 +1,2 @@ *.xml +*.gen diff --git a/docs/man/man1/ansible-playbook.1 b/docs/man/man1/ansible-playbook.1 index 5fb260bb0cd..1642966af37 100644 --- a/docs/man/man1/ansible-playbook.1 +++ b/docs/man/man1/ansible-playbook.1 @@ -1,13 +1,22 @@ '\" t .\" Title: ansible-playbook .\" Author: [see the "AUTHOR" section] -.\" Generator: DocBook XSL Stylesheets v1.75.2 -.\" Date: 04/16/2012 +.\" Generator: DocBook XSL Stylesheets v1.76.1 +.\" Date: 04/17/2012 .\" Manual: System administration commands .\" Source: Ansible 0.0.2 .\" Language: English .\" -.TH "ANSIBLE\-PLAYBOOK" "1" "04/16/2012" "Ansible 0\&.0\&.2" "System administration commands" +.TH "ANSIBLE\-PLAYBOOK" "1" "04/17/2012" "Ansible 0\&.0\&.2" "System administration commands" +.\" ----------------------------------------------------------------- +.\" * Define some portability stuff +.\" ----------------------------------------------------------------- +.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.\" http://bugs.debian.org/507673 +.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html +.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- diff --git a/docs/man/man1/ansible-playbook.1.asciidoc b/docs/man/man1/ansible-playbook.1.asciidoc index 14d34710e71..1ebb0204f4f 100644 --- a/docs/man/man1/ansible-playbook.1.asciidoc +++ b/docs/man/man1/ansible-playbook.1.asciidoc @@ -2,7 +2,7 @@ ansible-playbook(1) =================== :doctype:manpage :man source: Ansible -:man version: 0.0.2 +:man version: %VERSION% :man manual: System administration commands NAME diff --git a/docs/man/man1/ansible.1 b/docs/man/man1/ansible.1 index 550415ada06..057bad2c7d0 100644 --- a/docs/man/man1/ansible.1 +++ b/docs/man/man1/ansible.1 @@ -1,13 +1,22 @@ '\" t .\" Title: ansible .\" Author: [see the "AUTHOR" section] -.\" Generator: DocBook XSL Stylesheets v1.75.2 -.\" Date: 04/16/2012 +.\" Generator: DocBook XSL Stylesheets v1.76.1 +.\" Date: 04/17/2012 .\" Manual: System administration commands .\" Source: Ansible 0.0.2 .\" Language: English .\" -.TH "ANSIBLE" "1" "04/16/2012" "Ansible 0\&.0\&.2" "System administration commands" +.TH "ANSIBLE" "1" "04/17/2012" "Ansible 0\&.0\&.2" "System administration commands" +.\" ----------------------------------------------------------------- +.\" * Define some portability stuff +.\" ----------------------------------------------------------------- +.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.\" http://bugs.debian.org/507673 +.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html +.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- @@ -25,7 +34,7 @@ ansible \- run a command somewhere else ansible [\-f forks] [\-m module_name] [\-a args] .SH "DESCRIPTION" .sp -\fBAnsible\fR is an extra\-simple tool/framework/API for doing \'remote things\' over SSH\&. +\fBAnsible\fR is an extra\-simple tool/framework/API for doing \*(Aqremote things\*(Aq over SSH\&. .SH "ARGUMENTS" .PP \fBhost\-pattern\fR @@ -63,7 +72,7 @@ to load modules from\&. The default is \fI/usr/share/ansible\fR\&. .RE .PP -\fB\-a\fR \'\fIARGUMENTS\fR\', \fB\-\-args=\fR\'\fIARGUMENTS\fR\' +\fB\-a\fR \*(Aq\fIARGUMENTS\fR\*(Aq, \fB\-\-args=\fR\*(Aq\fIARGUMENTS\fR\*(Aq .RS 4 The \fIARGUMENTS\fR diff --git a/docs/man/man1/ansible.1.asciidoc b/docs/man/man1/ansible.1.asciidoc index a32cbde4534..2e43f64f198 100644 --- a/docs/man/man1/ansible.1.asciidoc +++ b/docs/man/man1/ansible.1.asciidoc @@ -2,7 +2,7 @@ ansible(1) ========= :doctype:manpage :man source: Ansible -:man version: 0.0.2 +:man version: %VERSION% :man manual: System administration commands NAME From b4ca288a7b416966f78bc5a8d55cd4e4f967bf44 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Mon, 16 Apr 2012 14:11:57 -0400 Subject: [PATCH 27/83] Set %config(noreplace) on /etc/ansible instead of /etc/ansible/* so the directory is removed on uninstall and its contents are considered config files. --- ansible.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible.spec b/ansible.spec index 5280eed8c97..4229d12f880 100644 --- a/ansible.spec +++ b/ansible.spec @@ -51,7 +51,7 @@ rm -rf $RPM_BUILD_ROOT %{python_sitelib}/ansible* %{_bindir}/ansible* %{_datadir}/ansible -%config(noreplace) %{_sysconfdir}/ansible/* +%config(noreplace) %{_sysconfdir}/ansible %doc README.md PKG-INFO %doc %{_mandir}/man1/ansible* From d43cf592eb46b4ba1b52844dab4a117d7e8c9ba7 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Mon, 16 Apr 2012 15:43:21 -0400 Subject: [PATCH 28/83] Renaming asciidoc sources to .in. Change manpages to build from *.in and add comments about how the make targets work. --- Makefile | 11 ++++++++--- docs/man/.gitignore | 2 +- ...book.1.asciidoc => ansible-playbook.1.asciidoc.in} | 0 .../{ansible.1.asciidoc => ansible.1.asciidoc.in} | 0 4 files changed, 9 insertions(+), 4 deletions(-) rename docs/man/man1/{ansible-playbook.1.asciidoc => ansible-playbook.1.asciidoc.in} (100%) rename docs/man/man1/{ansible.1.asciidoc => ansible.1.asciidoc.in} (100%) diff --git a/Makefile b/Makefile index 6de5dd195f5..79e7481aa1a 100644 --- a/Makefile +++ b/Makefile @@ -14,12 +14,17 @@ all: clean python tests: PYTHONPATH=./lib nosetests -v +# To force a rebuild of the docs run 'touch ansible.spec && make docs' docs: $(MANPAGES) -%.1.asciidoc.gen: %.1.asciidoc ansible.spec +# Regenerate %.1.asciidoc if %.1.asciidoc.in has been modified more +# recently than %.1.asciidoc. +%.1.asciidoc: %.1.asciidoc.in sed "s/%VERSION%/$(RPMVERSION)/" $< > $@ -%.1: %.1.asciidoc.gen +# Regenerate %.1 if %.1.asciidoc or ansible.spec has been modified +# more recently than %.1. (Implicitly runs the %.1.asciidoc recipe) +%.1: %.1.asciidoc ansible.spec $(ASCII2MAN) loc: @@ -45,7 +50,7 @@ clean: find . -type f \( -name "*.swp" \) -delete @echo "Cleaning up asciidoc to man transformations and results" find ./docs/man -type f -name "*.xml" -delete - find ./docs/man -type f -name "*.gen" -delete + find ./docs/man -type f -name "*.asciidoc" -delete @echo "Cleaning up output from test runs" -rm -rf test/test_data @echo "Cleaning up RPM building stuff" diff --git a/docs/man/.gitignore b/docs/man/.gitignore index ceb714c17d9..81a33679397 100644 --- a/docs/man/.gitignore +++ b/docs/man/.gitignore @@ -1,2 +1,2 @@ *.xml -*.gen +*.asciidoc diff --git a/docs/man/man1/ansible-playbook.1.asciidoc b/docs/man/man1/ansible-playbook.1.asciidoc.in similarity index 100% rename from docs/man/man1/ansible-playbook.1.asciidoc rename to docs/man/man1/ansible-playbook.1.asciidoc.in diff --git a/docs/man/man1/ansible.1.asciidoc b/docs/man/man1/ansible.1.asciidoc.in similarity index 100% rename from docs/man/man1/ansible.1.asciidoc rename to docs/man/man1/ansible.1.asciidoc.in From 3f9a41b22deec67208372b098e30305c2c3e27e4 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Tue, 17 Apr 2012 10:39:36 -0400 Subject: [PATCH 29/83] Docs build using version in the VERSION file. Makefile upgraded with comments and some simplifications. Remove useless 'PHONEY' and 'VPATH' directives from Makefile --- Makefile | 31 +++++++++++++++++-------------- VERSION | 1 + 2 files changed, 18 insertions(+), 14 deletions(-) create mode 100644 VERSION diff --git a/Makefile b/Makefile index 79e7481aa1a..26e350dfb31 100644 --- a/Makefile +++ b/Makefile @@ -1,30 +1,36 @@ #!/usr/bin/make NAME = "ansible" +# This doesn't evaluate until it's called. The -D argument is the +# directory of the target file ($@), kinda like `dirname`. ASCII2MAN = a2x -D $(dir $@) -d manpage -f manpage $< ASCII2HTMLMAN = a2x -D docs/html/man/ -d manpage -f xhtml +# Space separated list of all the manpages we want to end up with. MANPAGES := docs/man/man1/ansible.1 docs/man/man1/ansible-playbook.1 SITELIB = $(shell python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()") +VERSION := $(shell cat VERSION) +# These are for building the RPM. RPMVERSION := $(shell awk '/Version/{print $$2; exit}' < ansible.spec | cut -d "%" -f1) RPMRELEASE := $(shell awk '/Release/{print $$2; exit}' < ansible.spec | cut -d "%" -f1) -RPMNVR = "$(NAME)-$(RPMVERSION)-$(RPMRELEASE)" +RPMDIST = $(shell rpm --eval '%dist') +RPMNVR = "$(NAME)-$(RPMVERSION)-$(RPMRELEASE)$(RPMDIST)" all: clean python tests: PYTHONPATH=./lib nosetests -v -# To force a rebuild of the docs run 'touch ansible.spec && make docs' +# To force a rebuild of the docs run 'touch VERSION && make docs' docs: $(MANPAGES) # Regenerate %.1.asciidoc if %.1.asciidoc.in has been modified more # recently than %.1.asciidoc. %.1.asciidoc: %.1.asciidoc.in - sed "s/%VERSION%/$(RPMVERSION)/" $< > $@ + sed "s/%VERSION%/$(VERSION)/" $< > $@ -# Regenerate %.1 if %.1.asciidoc or ansible.spec has been modified -# more recently than %.1. (Implicitly runs the %.1.asciidoc recipe) -%.1: %.1.asciidoc ansible.spec +# Regenerate %.1 if %.1.asciidoc or VERSION has been modified more +# recently than %.1. (Implicitly runs the %.1.asciidoc recipe) +%.1: %.1.asciidoc VERSION $(ASCII2MAN) loc: @@ -41,10 +47,10 @@ pyflakes: clean: @echo "Cleaning up distutils stuff" - -rm -rf build - -rm -rf dist + rm -rf build + rm -rf dist @echo "Cleaning up byte compiled python stuff" - find . -regex ".*\.py[co]$$" -delete + find . -type f -regex ".*\.py[co]$$" -delete @echo "Cleaning up editor backup files" find . -type f \( -name "*~" -or -name "#*" \) -delete find . -type f \( -name "*.swp" \) -delete @@ -52,9 +58,9 @@ clean: find ./docs/man -type f -name "*.xml" -delete find ./docs/man -type f -name "*.asciidoc" -delete @echo "Cleaning up output from test runs" - -rm -rf test/test_data + rm -rf test/test_data @echo "Cleaning up RPM building stuff" - -rm -rf MANIFEST rpm-build + rm -rf MANIFEST rpm-build python: python setup.py build @@ -96,6 +102,3 @@ rpm: rpmcommon @echo "Ansible RPM is built:" @echo " rpm-build/noarch/$(RPMNVR).noarch.rpm" @echo "#############################################" - -.PHONEY: docs manual clean pep8 -vpath %.asciidoc docs/man/man1 diff --git a/VERSION b/VERSION new file mode 100644 index 00000000000..7bcd0e3612d --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.0.2 \ No newline at end of file From e9e8417735fa973a02278bed544f71dd36724333 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Tue, 17 Apr 2012 10:53:32 -0400 Subject: [PATCH 30/83] Update hacking with MANPATH. Correct hacking README details. --- hacking/README | 3 --- hacking/env-setup | 7 +++++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hacking/README b/hacking/README index dd7abbd4fed..c3772269cdb 100644 --- a/hacking/README +++ b/hacking/README @@ -6,6 +6,3 @@ To use it from the root of a checkout: $ . ./hacking/env-setup Note the space between the '.' and the './' - -Man pages will not load until you run 'make docs' from the root of the -checkout. diff --git a/hacking/env-setup b/hacking/env-setup index 2802730b312..b153862a940 100755 --- a/hacking/env-setup +++ b/hacking/env-setup @@ -4,14 +4,17 @@ PREFIX_PYTHONPATH="$PWD/lib" PREFIX_PATH="$PWD/bin" +PREFIX_MANPATH="$PWD/docs/man" export PYTHONPATH=$PREFIX_PYTHONPATH:$PYTHONPATH export PATH=$PREFIX_PATH:$PATH export ANSIBLE_LIBRARY="$PWD/library" +export MANPATH=$PREFIX_MANPATH:$MANPATH echo "PATH=$PATH" echo "PYTHONPATH=$PYTHONPATH" echo "ANSIBLE_LIBRARY=$ANSIBLE_LIBRARY" +echo "MANPATH=$MANPATH" -echo "reminder: specify your host file with -i" -echo "done." +echo "Reminder: specify your host file with -i" +echo "Done." From 621dc83ac214807927f965b163221825ee63b8a1 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Tue, 17 Apr 2012 11:39:51 -0400 Subject: [PATCH 31/83] Fix man page option description indentation. --- docs/man/man1/ansible-playbook.1 | 10 ++- docs/man/man1/ansible-playbook.1.asciidoc.in | 4 +- docs/man/man1/ansible.1 | 79 +++++++++++++------- docs/man/man1/ansible.1.asciidoc.in | 22 +++--- 4 files changed, 70 insertions(+), 45 deletions(-) diff --git a/docs/man/man1/ansible-playbook.1 b/docs/man/man1/ansible-playbook.1 index 1642966af37..2128164371b 100644 --- a/docs/man/man1/ansible-playbook.1 +++ b/docs/man/man1/ansible-playbook.1 @@ -92,14 +92,16 @@ Ignore the inventory file and run the playbook against only these hosts\&. "host \fIall\fR when using this option\&. .RE -.sp +.PP \fB\-s\fR, \fB\-\-sudo\fR -.sp +.RS 4 Force all plays to use sudo, even if not marked as such\&. -.sp +.RE +.PP \fB\-u\fR \fIUSERNAME\fR, \fB\-\-remote\-user=\fR\fIUSERNAME\fR -.sp +.RS 4 Use this remote user name on playbook steps that do not indicate a user name to run as\&. +.RE .SH "ENVIRONMENT" .sp The following environment variables may specified\&. diff --git a/docs/man/man1/ansible-playbook.1.asciidoc.in b/docs/man/man1/ansible-playbook.1.asciidoc.in index 1ebb0204f4f..e478ab60c89 100644 --- a/docs/man/man1/ansible-playbook.1.asciidoc.in +++ b/docs/man/man1/ansible-playbook.1.asciidoc.in @@ -75,12 +75,12 @@ Ignore the inventory file and run the playbook against only these hosts. "hosts in playbook should be set to 'all' when using this option. -*-s*, *--sudo* +*-s*, *--sudo*:: Force all plays to use sudo, even if not marked as such. -*-u* 'USERNAME', *--remote-user=*'USERNAME' +*-u* 'USERNAME', *--remote-user=*'USERNAME':: Use this remote user name on playbook steps that do not indicate a user name to run as. diff --git a/docs/man/man1/ansible.1 b/docs/man/man1/ansible.1 index 057bad2c7d0..f5a6f37a1cb 100644 --- a/docs/man/man1/ansible.1 +++ b/docs/man/man1/ansible.1 @@ -78,50 +78,73 @@ The \fIARGUMENTS\fR to pass to the module\&. .RE -.sp +.PP \fB\-D\fR, \fB\-\-debug\fR -.sp +.RS 4 Print any messages the remote module sends to standard error to the console -.sp +.RE +.PP \fB\-k\fR, \fB\-\-ask\-pass\fR -.sp +.RS 4 Prompt for the SSH password instead of assuming key\-based authentication with ssh\-agent\&. -.sp +.RE +.PP \fB\-K\fR, \fB\-\-ask\-sudo\-pass\fR -.sp +.RS 4 Prompt for the password to use with \-\-sudo, if any -.sp +.RE +.PP \fB\-o\fR, \fB\-\-one\-line\fR -.sp +.RS 4 Try to output everything on one line\&. -.sp +.RE +.PP \fB\-s\fR, \fB\-\-sudo\fR -.sp +.RS 4 Run the command as the user given by \-u and sudo to root\&. -.sp +.RE +.PP \fB\-t\fR \fIDIRECTORY\fR, \fB\-\-tree=\fR\fIDIRECTORY\fR -.sp -Save contents in this output \fIDIRECTORY\fR, with the results saved in a file named after each host\&. -.sp +.RS 4 +Save contents in this output +\fIDIRECTORY\fR, with the results saved in a file named after each host\&. +.RE +.PP \fB\-T\fR \fISECONDS\fR, \fB\-\-timeout=\fR\fISECONDS\fR -.sp -Connection timeout to use when trying to talk to hosts, in \fISECONDS\fR\&. -.sp +.RS 4 +Connection timeout to use when trying to talk to hosts, in +\fISECONDS\fR\&. +.RE +.PP \fB\-B\fR \fINUM\fR, \fB\-\-background=\fR\fINUM\fR -.sp -Run commands in the background, killing the task after \fINUM\fR seconds\&. -.sp +.RS 4 +Run commands in the background, killing the task after +\fINUM\fR +seconds\&. +.RE +.PP \fB\-P\fR \fINUM\fR, \fB\-\-poll=\fR\fINUM\fR -.sp -Poll a background job every \fINUM\fR seconds\&. Requires \fB\-B\fR\&. -.sp +.RS 4 +Poll a background job every +\fINUM\fR +seconds\&. Requires +\fB\-B\fR\&. +.RE +.PP \fB\-u\fR \fIUSERNAME\fR, \fB\-\-remote\-user=\fR\fIUSERNAME\fR -.sp -Use this remote \fIUSERNAME\fR instead of root\&. -.sp +.RS 4 +Use this remote +\fIUSERNAME\fR +instead of root\&. +.RE +.PP \fB\-c\fR \fICONNECTION\fR, \fB\-\-connection=\fR\fICONNECTION\fR -.sp -Connection type to use\&. Possible options are \fIparamiko\fR (SSH) and \fIlocal\fR\&. Local is mostly useful for crontab or kickstarts\&. +.RS 4 +Connection type to use\&. Possible options are +\fIparamiko\fR +(SSH) and +\fIlocal\fR\&. Local is mostly useful for crontab or kickstarts\&. +.RE .SH "INVENTORY" .sp Ansible stores the hosts it can potentially operate on in an inventory file\&. The syntax is one host per line\&. Groups headers are allowed and are included on their own line, enclosed in square brackets\&. diff --git a/docs/man/man1/ansible.1.asciidoc.in b/docs/man/man1/ansible.1.asciidoc.in index 2e43f64f198..eacb6015119 100644 --- a/docs/man/man1/ansible.1.asciidoc.in +++ b/docs/man/man1/ansible.1.asciidoc.in @@ -60,48 +60,48 @@ The 'DIRECTORY' to load modules from. The default is '/usr/share/ansible'. The 'ARGUMENTS' to pass to the module. -*-D*, *--debug* +*-D*, *--debug*:: Print any messages the remote module sends to standard error to the console -*-k*, *--ask-pass* +*-k*, *--ask-pass*:: Prompt for the SSH password instead of assuming key-based authentication with ssh-agent. -*-K*, *--ask-sudo-pass* +*-K*, *--ask-sudo-pass*:: Prompt for the password to use with --sudo, if any -*-o*, *--one-line* +*-o*, *--one-line*:: Try to output everything on one line. -*-s*, *--sudo* +*-s*, *--sudo*:: Run the command as the user given by -u and sudo to root. -*-t* 'DIRECTORY', *--tree=*'DIRECTORY' +*-t* 'DIRECTORY', *--tree=*'DIRECTORY':: Save contents in this output 'DIRECTORY', with the results saved in a file named after each host. -*-T* 'SECONDS', *--timeout=*'SECONDS' +*-T* 'SECONDS', *--timeout=*'SECONDS':: Connection timeout to use when trying to talk to hosts, in 'SECONDS'. -*-B* 'NUM', *--background=*'NUM' +*-B* 'NUM', *--background=*'NUM':: Run commands in the background, killing the task after 'NUM' seconds. -*-P* 'NUM', *--poll=*'NUM' +*-P* 'NUM', *--poll=*'NUM':: Poll a background job every 'NUM' seconds. Requires *-B*. -*-u* 'USERNAME', *--remote-user=*'USERNAME' +*-u* 'USERNAME', *--remote-user=*'USERNAME':: Use this remote 'USERNAME' instead of root. -*-c* 'CONNECTION', *--connection=*'CONNECTION' +*-c* 'CONNECTION', *--connection=*'CONNECTION':: Connection type to use. Possible options are 'paramiko' (SSH) and 'local'. Local is mostly useful for crontab or kickstarts. From de70277173b049f0e7a09fdda3ca2173478b8ddd Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 17 Apr 2012 19:37:29 -0400 Subject: [PATCH 32/83] No need to save setup files to disk, this is what SETUP_CACHE effectively does. --- lib/ansible/runner.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index 1fced2c589c..7ce2275a7b9 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -299,24 +299,6 @@ class Runner(object): # ***************************************************** - def _save_setup_result_to_disk(self, conn, result): - ''' cache results of calling setup ''' - - dest = os.path.expanduser("~/.ansible_setup_data") - user = getpass.getuser() - if user == 'root': - dest = "/var/lib/ansible/setup_data" - if not os.path.exists(dest): - os.makedirs(dest) - - fh = open(os.path.join(dest, conn.host), "w") - fh.write(result) - fh.close() - - return result - - # ***************************************************** - def _add_result_to_setup_cache(self, conn, result): ''' allows discovered variables to be used in templates and action statements ''' @@ -350,8 +332,6 @@ class Runner(object): if module_name == 'setup': self._add_result_to_setup_cache(conn, result) - if self.is_playbook: - self._save_setup_result_to_disk(conn, result) return self._return_from_module(conn, host, result, err, executed) From 0c38bb2fd074e31c3b9a05630663b095070aec0f Mon Sep 17 00:00:00 2001 From: Stephen Fromm Date: Tue, 17 Apr 2012 16:59:23 -0700 Subject: [PATCH 33/83] Add native facts to library/setup This collects various facts from the host so that it isn't necessary to have facter or ohai installed. It gets various platform/distribution facts, information about the type of hardware, whether a virtual environment and what type, assorted interface facts, and ssh host public keys. Most facts are flat. The two exceptions are 'processor' and all interface facts. Interface facts are presented as: ansible_lo : { "macaddress": "00:00:00:00:00:00", "ipv4": { "address": "127.0.0.1", "netmask": "255.0.0.0" }, "ipv6": [ { "address": "::1", "prefix": "128", "scope": "host" } ] } --- library/setup | 249 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) diff --git a/library/setup b/library/setup index 6efb5e973c0..134cd232e7f 100755 --- a/library/setup +++ b/library/setup @@ -19,9 +19,16 @@ DEFAULT_ANSIBLE_SETUP = "/etc/ansible/setup" +import array +import fcntl +import glob import sys import os +import platform +import re import shlex +import socket +import struct import subprocess import traceback @@ -30,6 +37,244 @@ try: except ImportError: import simplejson as json +_I386RE = re.compile(r'i[3456]86') +SIOCGIFCONF = 0x8912 +SIOCGIFHWADDR = 0x8927 +MEMORY_FACTS = ['MemTotal', 'SwapTotal', 'MemFree', 'SwapFree'] +# DMI bits +DMI_DICT = { 'form_factor': '/sys/devices/virtual/dmi/id/chassis_type', + 'product_name': '/sys/devices/virtual/dmi/id/product_name', + 'product_serial': '/sys/devices/virtual/dmi/id/product_serial', + 'product_uuid': '/sys/devices/virtual/dmi/id/product_uuid', + 'product_version': '/sys/devices/virtual/dmi/id/product_version', + 'system_vendor': '/sys/devices/virtual/dmi/id/sys_vendor' } +# From smolt and DMI spec +FORM_FACTOR = [ "Unknown", "Other", "Unknown", "Desktop", + "Low Profile Desktop", "Pizza Box", "Mini Tower", "Tower", + "Portable", "Laptop", "Notebook", "Hand Held", "Docking Station", + "All In One", "Sub Notebook", "Space-saving", "Lunch Box", + "Main Server Chassis", "Expansion Chassis", "Sub Chassis", + "Bus Expansion Chassis", "Peripheral Chassis", "RAID Chassis", + "Rack Mount Chassis", "Sealed-case PC", "Multi-system", + "CompactPCI", "AdvancedTCA" ] +# For the most part, we assume that platform.dist() will tell the truth. +# This is the fallback to handle unknowns or exceptions +OSDIST_DICT = { '/etc/redhat-release': 'RedHat', + '/etc/vmware-release': 'VMwareESX' } + +def get_file_content(path): + if os.path.exists(path) and os.access(path, os.R_OK): + data = open(path).read().strip() + if len(data) == 0: + data = None + else: + data = None + return data + +# platform.dist() is deprecated in 2.6 +# in 2.6 and newer, you should use platform.linux_distribution() +def get_distribution_facts(facts): + dist = platform.dist() + facts['distribution'] = dist[0].capitalize() or 'NA' + facts['distribution_version'] = dist[1] or 'NA' + facts['distribution_release'] = dist[2] or 'NA' + # Try to handle the exceptions now ... + for (path, name) in OSDIST_DICT.items(): + if os.path.exists(path): + if facts['distribution'] == 'Fedora': + pass + elif name == 'RedHat': + data = get_file_content(path) + if 'Red Hat' in data: + facts['distribution'] = name + else: + facts['distribution'] = data.split()[0] + else: + facts['distribution'] = name + +# Platform +# patform.system() can be Linux, Darwin, Java, or Windows +def get_platform_facts(facts): + facts['system'] = platform.system() + facts['kernel'] = platform.release() + facts['machine'] = platform.machine() + facts['python_version'] = platform.python_version() + if facts['machine'] == 'x86_64': + facts['architecture'] = facts['machine'] + elif _I386RE.search(facts['machine']): + facts['architecture'] = 'i386' + else: + facts['archtecture'] = facts['machine'] + if facts['system'] == 'Linux': + get_distribution_facts(facts) + +def get_memory_facts(facts): + if not os.access("/proc/meminfo", os.R_OK): + return facts + for line in open("/proc/meminfo").readlines(): + data = line.split(":", 1) + key = data[0] + if key in MEMORY_FACTS: + val = data[1].strip().split(' ')[0] + facts["%s_mb" % key.lower()] = long(val) / 1024 + +def get_cpu_facts(facts): + i = 0 + physid = 0 + sockets = {} + if not os.access("/proc/cpuinfo", os.R_OK): + return facts + for line in open("/proc/cpuinfo").readlines(): + data = line.split(":", 1) + key = data[0].strip() + if key == 'model name': + if 'processor' not in facts: + facts['processor'] = [] + facts['processor'].append(data[1].strip()) + i += 1 + elif key == 'physical id': + physid = data[1].strip() + if physid not in sockets: + sockets[physid] = 1 + elif key == 'cpu cores': + sockets[physid] = int(data[1].strip()) + if len(sockets) > 0: + facts['processor_count'] = len(sockets) + facts['processor_cores'] = reduce(lambda x, y: x + y, sockets.values()) + else: + facts['processor_count'] = i + facts['processor_cores'] = 'NA' + +def get_hardware_facts(facts): + get_memory_facts(facts) + get_cpu_facts(facts) + for (key,path) in DMI_DICT.items(): + data = get_file_content(path) + if data is not None: + if key == 'form_factor': + facts['form_factor'] = FORM_FACTOR[int(data)] + else: + facts[key] = data + else: + facts[key] = 'NA' + +def get_linux_virtual_facts(facts): + if os.path.exists("/proc/xen"): + facts['virtualization_type'] = 'xen' + facts['virtualization_role'] = 'guest' + if os.path.exists("/proc/xen/capabilities"): + facts['virtualization_role'] = 'host' + if os.path.exists("/proc/modules"): + modules = [] + for line in open("/proc/modules").readlines(): + data = line.split(" ", 1) + modules.append(data[0]) + if 'kvm' in modules: + facts['virtualization_type'] = 'kvm' + facts['virtualization_role'] = 'host' + elif 'vboxdrv' in modules: + facts['virtualization_type'] = 'virtualbox' + facts['virtualization_role'] = 'host' + elif 'vboxguest' in modules: + facts['virtualization_type'] = 'virtualbox' + facts['virtualization_role'] = 'guest' + if 'QEMU' in facts['processor'][0]: + facts['virtualization_type'] = 'kvm' + facts['virtualization_role'] = 'guest' + if facts['distribution'] == 'VMwareESX': + facts['virtualization_type'] = 'VMware' + facts['virtualization_role'] = 'host' + # You can spawn a dmidecode process and parse that or infer from devices + for dev_model in glob.glob('/proc/ide/hd*/model'): + info = open(dev_model).read() + if 'VMware' in info: + facts['virtualization_type'] = 'VMware' + facts['virtualization_role'] = 'guest' + elif 'Virtual HD' in info or 'Virtual CD' in info: + facts['virtualization_type'] = 'VirtualPC' + facts['virtualization_role'] = 'guest' + +def get_virtual_facts(facts): + facts['virtualization_type'] = 'None' + facts['virtualization_role'] = 'None' + if facts['system'] == 'Linux': + facts = get_linux_virtual_facts(facts) + +# get list of interfaces that are up +def get_interfaces(): + length = 4096 + offset = 32 + step = 32 + if platform.architecture()[0] == '64bit': + offset = 16 + step = 40 + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + names = array.array('B', '\0' * length) + bytelen = struct.unpack('iL', fcntl.ioctl( + s.fileno(), SIOCGIFCONF, struct.pack( + 'iL', length, names.buffer_info()[0]) + ))[0] + return [names.tostring()[i:i+offset].split('\0', 1)[0] + for i in range(0, bytelen, step)] + +def get_iface_hwaddr(iface): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + info = fcntl.ioctl(s.fileno(), SIOCGIFHWADDR, + struct.pack('256s', iface[:15])) + return ''.join(['%02x:' % ord(char) for char in info[18:24]])[:-1] + +def get_network_facts(facts): + facts['fqdn'] = socket.gethostname() + facts['hostname'] = facts['fqdn'].split('.')[0] + facts['interfaces'] = get_interfaces() + for iface in facts['interfaces']: + facts[iface] = { 'macaddress': get_iface_hwaddr(iface) } + # This is lame, but there doesn't appear to be a good way + # to get all addresses for both IPv4 and IPv6. + cmd = subprocess.Popen("/sbin/ifconfig %s" % iface, shell=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = cmd.communicate() + for line in out.split('\n'): + data = line.split() + if 'inet addr' in line: + if 'ipv4' not in facts[iface]: + facts[iface]['ipv4'] = {} + facts[iface]['ipv4'] = { 'address': data[1].split(':')[1], + 'netmask': data[-1].split(':')[1] } + if 'inet6 addr' in line: + (ip, prefix) = data[2].split('/') + scope = data[3].split(':')[1].lower() + if 'ipv6' not in facts[iface]: + facts[iface]['ipv6'] = [] + facts[iface]['ipv6'].append( { 'address': ip, + 'prefix': prefix, + 'scope': scope } ) + return facts + +def get_public_ssh_host_keys(facts): + dsa = get_file_content('/etc/ssh/ssh_host_dsa_key.pub') + rsa = get_file_content('/etc/ssh/ssh_host_rsa_key.pub') + if dsa is None: + dsa = 'NA' + else: + facts['ssh_host_key_dsa_public'] = dsa.split()[1] + if rsa is None: + rsa = 'NA' + else: + facts['ssh_host_key_rsa_public'] = rsa.split()[1] + +def get_service_facts(facts): + get_public_ssh_host_keys(facts) + +def ansible_facts(): + facts = {} + get_platform_facts(facts) + get_hardware_facts(facts) + get_virtual_facts(facts) + get_network_facts(facts) + get_service_facts(facts) + return facts + # load config & template variables if len(sys.argv) == 1: @@ -65,6 +310,10 @@ if not os.path.exists(ansible_file): else: md5sum = os.popen("md5sum %s" % ansible_file).read().split()[0] +# Get some basic facts in case facter or ohai are not installed +for (k, v) in ansible_facts().items(): + setup_options["ansible_%s" % k] = v + # if facter is installed, and we can use --json because # ruby-json is ALSO installed, include facter data in the JSON From 28895f6615dcac6a0806472282dbe921d75dcc93 Mon Sep 17 00:00:00 2001 From: Stephen Fromm Date: Tue, 17 Apr 2012 17:12:09 -0700 Subject: [PATCH 34/83] Rename serange to selevel to be consistent with selinux docs --- library/file | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/file b/library/file index 25ea749d02b..742a7d7b227 100755 --- a/library/file +++ b/library/file @@ -102,8 +102,8 @@ recurse = params.get('recurse', 'false') seuser = params.get('seuser', None) serole = params.get('serole', None) setype = params.get('setype', None) -serange = params.get('serange', 's0') -secontext = [seuser, serole, setype, serange] +selevel = params.get('serange', 's0') +secontext = [seuser, serole, setype, selevel] if state not in [ 'file', 'directory', 'link', 'absent']: fail_json(msg='invalid state: %s' % state) From b678cf783cce38424dc821ce49179e8ef4bfb37b Mon Sep 17 00:00:00 2001 From: Jeroen Hoekx Date: Wed, 18 Apr 2012 11:40:15 +0200 Subject: [PATCH 35/83] Template the source file of the copy module. --- lib/ansible/runner.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index 7ce2275a7b9..a029d73afab 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -368,7 +368,11 @@ class Runner(object): dest = options.get('dest', None) if source is None or dest is None: return (host, True, dict(failed=True, msg="src and dest are required"), '') - + + # apply templating to source argument + inject = self.setup_cache.get(conn.host,{}) + source = utils.template(source, inject) + # transfer the file to a remote tmp location tmp_src = tmp + source.split('/')[-1] conn.put_file(utils.path_dwim(self.basedir, source), tmp_src) From 22ff8282a8277166c727b995f9e6e1d7e809d9bb Mon Sep 17 00:00:00 2001 From: Jeroen Hoekx Date: Wed, 18 Apr 2012 14:26:33 +0200 Subject: [PATCH 36/83] Template template module source. --- lib/ansible/runner.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index a029d73afab..dd7b66af275 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -463,6 +463,10 @@ class Runner(object): else: metadata = '~/.ansible/setup' + # apply templating to source argument + inject = self.setup_cache.get(conn.host,{}) + source = utils.template(source, inject) + # first copy the source template over temppath = tmp + os.path.split(source)[-1] conn.put_file(utils.path_dwim(self.basedir, source), temppath) From 0a70106b0f9f83662937033df9b8dd57a251e7f6 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Wed, 18 Apr 2012 10:48:37 -0400 Subject: [PATCH 37/83] Move ansible.spec into subdir. Closes #162 --- ansible.spec => packaging/rpm/ansible.spec | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ansible.spec => packaging/rpm/ansible.spec (100%) diff --git a/ansible.spec b/packaging/rpm/ansible.spec similarity index 100% rename from ansible.spec rename to packaging/rpm/ansible.spec From 66f294d5c1464e169274aea8b443d9c4a407220d Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Wed, 18 Apr 2012 11:00:59 -0400 Subject: [PATCH 38/83] Finish up moving spec file --- Makefile | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 26e350dfb31..b6aa3bba8a0 100644 --- a/Makefile +++ b/Makefile @@ -10,8 +10,10 @@ MANPAGES := docs/man/man1/ansible.1 docs/man/man1/ansible-playbook.1 SITELIB = $(shell python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()") VERSION := $(shell cat VERSION) # These are for building the RPM. -RPMVERSION := $(shell awk '/Version/{print $$2; exit}' < ansible.spec | cut -d "%" -f1) -RPMRELEASE := $(shell awk '/Release/{print $$2; exit}' < ansible.spec | cut -d "%" -f1) +RPMSPECDIR= packaging/rpm +RPMSPEC = $(RPMSPECDIR)/ansible.spec +RPMVERSION := $(shell awk '/Version/{print $$2; exit}' < $(RPMSPEC) | cut -d "%" -f1) +RPMRELEASE := $(shell awk '/Release/{print $$2; exit}' < $(RPMSPEC) | cut -d "%" -f1) RPMDIST = $(shell rpm --eval '%dist') RPMNVR = "$(NAME)-$(RPMVERSION)-$(RPMRELEASE)$(RPMDIST)" @@ -82,9 +84,9 @@ srpm: rpmcommon --define "_builddir %{_topdir}" \ --define "_rpmdir %{_topdir}" \ --define "_srcrpmdir %{_topdir}" \ - --define "_specdir %{_topdir}" \ + --define "_specdir $(RPMSPECDIR)" \ --define "_sourcedir %{_topdir}" \ - -bs ansible.spec + -bs $(RPMSPEC) @echo "#############################################" @echo "Ansible SRPM is built:" @echo " rpm-build/$(RPMNVR).src.rpm" @@ -95,9 +97,9 @@ rpm: rpmcommon --define "_builddir %{_topdir}" \ --define "_rpmdir %{_topdir}" \ --define "_srcrpmdir %{_topdir}" \ - --define "_specdir %{_topdir}" \ + --define "_specdir $(RPMSPECDIR)" \ --define "_sourcedir %{_topdir}" \ - -ba ansible.spec + -ba $(RPMSPEC) @echo "#############################################" @echo "Ansible RPM is built:" @echo " rpm-build/noarch/$(RPMNVR).noarch.rpm" From 2beb6592c7eb7ccbb27059adfbe24c5ad07dd1f7 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Wed, 18 Apr 2012 16:41:11 -0400 Subject: [PATCH 39/83] Update spec file location in MANIFEST.in --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index a98672ff8ae..60bc97f1872 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include README.md ansible.spec +include README.md packaging/rpm/ansible.spec include examples/hosts recursive-include docs * recursive-include library * From da0209dbc43d3f6f5a659400d0015399025f5376 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Apr 2012 21:12:48 -0400 Subject: [PATCH 40/83] The fetch module really should preserve the whole directory structure being fetched to allow subsequent calls, particularly in playbook, to recreate the host tree structure. Making it thus. --- lib/ansible/runner.py | 5 +++-- test/TestRunner.py | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index dd7b66af275..7515f617839 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -404,8 +404,9 @@ class Runner(object): return (host, True, dict(failed=True, msg="src and dest are required"), '') # files are saved in dest dir, with a subdir for each host, then the filename - filename = os.path.basename(source) - dest = "%s/%s/%s" % (utils.path_dwim(self.basedir, dest), host, filename) + dest = "%s/%s/%s" % (utils.path_dwim(self.basedir, dest), host, source) + dest = dest.replace("//","/") + print "DEST=%s" % dest # compare old and new md5 for support of change hooks local_md5 = None diff --git a/test/TestRunner.py b/test/TestRunner.py index fc890125a56..529b4a6965a 100644 --- a/test/TestRunner.py +++ b/test/TestRunner.py @@ -201,9 +201,8 @@ class TestRunner(unittest.TestCase): def test_fetch(self): input = self._get_test_file('sample.j2') - output = self._get_stage_file('127.0.0.2/sample.j2') + output = os.path.join(self.stage_dir, '127.0.0.2', input) result = self._run('fetch', [ "src=%s" % input, "dest=%s" % self.stage_dir ]) - print "output file=%s" % output assert os.path.exists(output) assert open(input).read() == open(output).read() From 4f6be6e35be353f120e5ec915cb0b90eff531675 Mon Sep 17 00:00:00 2001 From: Henry Graham Date: Wed, 18 Apr 2012 12:17:35 -0400 Subject: [PATCH 41/83] Initial debian packaging --- debian/ansible.dirs | 3 +++ debian/ansible.install | 4 ++++ debian/changelog | 5 +++++ debian/compat | 1 + debian/control | 12 ++++++++++++ debian/copyright | 26 ++++++++++++++++++++++++++ debian/docs | 1 + debian/pycompat | 1 + debian/rules | 6 ++++++ 9 files changed, 59 insertions(+) create mode 100644 debian/ansible.dirs create mode 100644 debian/ansible.install create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/docs create mode 100644 debian/pycompat create mode 100644 debian/rules diff --git a/debian/ansible.dirs b/debian/ansible.dirs new file mode 100644 index 00000000000..1ab42d1149b --- /dev/null +++ b/debian/ansible.dirs @@ -0,0 +1,3 @@ +etc/ansible +usr/lib/python2.7/site-packages +usr/share/ansible diff --git a/debian/ansible.install b/debian/ansible.install new file mode 100644 index 00000000000..d67b2cec59d --- /dev/null +++ b/debian/ansible.install @@ -0,0 +1,4 @@ +hosts etc/ansible +python/* usr/share/ansible +man/man1/* usr/share/man/man1 +bin/* usr/bin diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 00000000000..66137bf9ee3 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +ansible (0.0.2) nattyl; urgency=low + + * Initial Release test + + -- Henry Graham (hzgraham) Tue, 17 Apr 2012 17:17:01 -0400 diff --git a/debian/compat b/debian/compat new file mode 100644 index 00000000000..7ed6ff82de6 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +5 diff --git a/debian/control b/debian/control new file mode 100644 index 00000000000..4fd89eaf1f5 --- /dev/null +++ b/debian/control @@ -0,0 +1,12 @@ +Source: ansible +Section: admin +Priority: optional +Maintainer: Henry Graham (hzgraham) +Build-Depends: cdbs, debhelper (>= 5.0.0) +Standards-Version: 3.9.1 + +Package: ansible +Architecture: all +Depends: python-jinja2, python-yaml, python-paramiko +Description: The Ansible ssh management application + This package installs Ansible, which is an ssh based configuration management application diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 00000000000..d2144178357 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,26 @@ +This package was debianized by Henry Graham (hzgraham) on +Tue, 17 Apr 2012 12:19:47 -0400. + +It was downloaded from mirror.lcsee.wvu.edu + +Copyright: Henry Graham (hzgraham) + +License: + + This package 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; version 2 dated June, 1991. + + This package 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 this package; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, + USA. + +On Debian systems, the complete text of the GNU General +Public License can be found in `/usr/share/common-licenses/GPL'. + diff --git a/debian/docs b/debian/docs new file mode 100644 index 00000000000..b43bf86b50f --- /dev/null +++ b/debian/docs @@ -0,0 +1 @@ +README.md diff --git a/debian/pycompat b/debian/pycompat new file mode 100644 index 00000000000..0cfbf08886f --- /dev/null +++ b/debian/pycompat @@ -0,0 +1 @@ +2 diff --git a/debian/rules b/debian/rules new file mode 100644 index 00000000000..1b4a1575c91 --- /dev/null +++ b/debian/rules @@ -0,0 +1,6 @@ +#!/usr/bin/make -f +# -- makefile -- + +include /usr/share/cdbs/1/rules/debhelper.mk +DEB_PYTHON_SYSTEM = pysupport +include /usr/share/cdbs/1/class/python-distutils.mk From 49ca5ba9f22cc8ed3f7c537da23b69c63a4acdb6 Mon Sep 17 00:00:00 2001 From: Henry Graham Date: Wed, 18 Apr 2012 16:22:53 -0400 Subject: [PATCH 42/83] modified debian files --- debian/ansible.install | 6 +++--- debian/changelog | 4 ++-- debian/control | 9 ++++++--- debian/copyright | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/debian/ansible.install b/debian/ansible.install index d67b2cec59d..3bb3f590b51 100644 --- a/debian/ansible.install +++ b/debian/ansible.install @@ -1,4 +1,4 @@ -hosts etc/ansible -python/* usr/share/ansible -man/man1/* usr/share/man/man1 +examples/hosts etc/ansible +library/* usr/share/ansible +docs/man/man1/* usr/share/man/man1 bin/* usr/bin diff --git a/debian/changelog b/debian/changelog index 66137bf9ee3..b0fe3944986 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,5 +1,5 @@ -ansible (0.0.2) nattyl; urgency=low +ansible (0.0.2) debian; urgency=low - * Initial Release test + * Initial Release -- Henry Graham (hzgraham) Tue, 17 Apr 2012 17:17:01 -0400 diff --git a/debian/control b/debian/control index 4fd89eaf1f5..d030aaeb707 100644 --- a/debian/control +++ b/debian/control @@ -2,11 +2,14 @@ Source: ansible Section: admin Priority: optional Maintainer: Henry Graham (hzgraham) -Build-Depends: cdbs, debhelper (>= 5.0.0) +Build-Depends: cdbs, debhelper (>= 5.0.0), python-devel, asciidoc Standards-Version: 3.9.1 +Homepage: http://ansible.github.com/ Package: ansible Architecture: all Depends: python-jinja2, python-yaml, python-paramiko -Description: The Ansible ssh management application - This package installs Ansible, which is an ssh based configuration management application +Description: The Ansible Application + Ansible is a extra-simple tool/API for doing 'parallel remote things' over SSH +executing commands, running "modules", or executing larger 'playbooks' that +can serve as a configuration management or deployment system. diff --git a/debian/copyright b/debian/copyright index d2144178357..4a17425fbb8 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,7 +1,7 @@ This package was debianized by Henry Graham (hzgraham) on Tue, 17 Apr 2012 12:19:47 -0400. -It was downloaded from mirror.lcsee.wvu.edu +It was downloaded from https://github.com/ansible/ansible.git Copyright: Henry Graham (hzgraham) From b501bbfb69cd9656506dbf9837998d44f1ad0e70 Mon Sep 17 00:00:00 2001 From: Henry Graham Date: Wed, 18 Apr 2012 16:30:45 -0400 Subject: [PATCH 43/83] format change to control file --- debian/control | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/debian/control b/debian/control index d030aaeb707..0a181966e05 100644 --- a/debian/control +++ b/debian/control @@ -2,14 +2,12 @@ Source: ansible Section: admin Priority: optional Maintainer: Henry Graham (hzgraham) -Build-Depends: cdbs, debhelper (>= 5.0.0), python-devel, asciidoc +Build-Depends: cdbs, debhelper (>= 5.0.0) Standards-Version: 3.9.1 -Homepage: http://ansible.github.com/ +Homepage: http://ansible.github.com/ Package: ansible Architecture: all Depends: python-jinja2, python-yaml, python-paramiko Description: The Ansible Application - Ansible is a extra-simple tool/API for doing 'parallel remote things' over SSH -executing commands, running "modules", or executing larger 'playbooks' that -can serve as a configuration management or deployment system. + Ansible is a extra-simple tool/API for doing 'parallel remote things' over SSH executing commands, running "modules", or executing larger 'playbooks' that can serve as a configuration management or deployment system. From 9c64ceb0f803bb336dd62d4e5e00cb86579cb883 Mon Sep 17 00:00:00 2001 From: Henry Graham Date: Wed, 18 Apr 2012 17:14:30 -0400 Subject: [PATCH 44/83] manpages and control file packaging changes --- debian/ansible.install | 3 ++- debian/control | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/debian/ansible.install b/debian/ansible.install index 3bb3f590b51..9c48b824924 100644 --- a/debian/ansible.install +++ b/debian/ansible.install @@ -1,4 +1,5 @@ examples/hosts etc/ansible library/* usr/share/ansible -docs/man/man1/* usr/share/man/man1 +docs/man/man1/ansible.1 usr/share/man/man1 +docs/man/man1/ansible-playbook.1 usr/share/man/man1 bin/* usr/bin diff --git a/debian/control b/debian/control index 0a181966e05..2dc3c4634a1 100644 --- a/debian/control +++ b/debian/control @@ -8,6 +8,6 @@ Homepage: http://ansible.github.com/ Package: ansible Architecture: all -Depends: python-jinja2, python-yaml, python-paramiko -Description: The Ansible Application +Depends: python, python-support (>= 0.90), python-jinja2, python-yaml, python-paramiko +Description: Ansible Application Ansible is a extra-simple tool/API for doing 'parallel remote things' over SSH executing commands, running "modules", or executing larger 'playbooks' that can serve as a configuration management or deployment system. From 5fa3d9b1480165960372e02fd26b891cc1976381 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Apr 2012 22:19:25 -0400 Subject: [PATCH 45/83] Teach playbooks to template locally to eliminate the need for Jinja2 on remote nodes. You still need jinja2 if using /usr/bin/ansible vs /usr/bin/ansible-playbook though this could change later by fetching the ansible file with a 'slurp' module. --- lib/ansible/runner.py | 91 ++++++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 31 deletions(-) diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index 7515f617839..f5a6de3f2a3 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -201,23 +201,22 @@ class Runner(object): # ***************************************************** - def _transfer_str(self, conn, tmp, name, args_str): - ''' transfer arguments as a single file to be fed to the module. ''' + def _transfer_str(self, conn, tmp, name, data): + ''' transfer string to remote file ''' - if type(args_str) == dict: - args_str = utils.smjson(args_str) + if type(data) == dict: + data = utils.smjson(data) - args_fd, args_file = tempfile.mkstemp() - args_fo = os.fdopen(args_fd, 'w') - args_fo.write(args_str) - args_fo.flush() - args_fo.close() + afd, afile = tempfile.mkstemp() + afo = os.fdopen(afd, 'w') + afo.write(data) + afo.flush() + afo.close() - args_remote = os.path.join(tmp, name) - conn.put_file(args_file, args_remote) - os.unlink(args_file) - - return args_remote + remote = os.path.join(tmp, name) + conn.put_file(afile, remote) + os.unlink(afile) + return remote # ***************************************************** @@ -406,7 +405,6 @@ class Runner(object): # files are saved in dest dir, with a subdir for each host, then the filename dest = "%s/%s/%s" % (utils.path_dwim(self.basedir, dest), host, source) dest = dest.replace("//","/") - print "DEST=%s" % dest # compare old and new md5 for support of change hooks local_md5 = None @@ -468,25 +466,56 @@ class Runner(object): inject = self.setup_cache.get(conn.host,{}) source = utils.template(source, inject) - # first copy the source template over - temppath = tmp + os.path.split(source)[-1] - conn.put_file(utils.path_dwim(self.basedir, source), temppath) + (host, ok, data, err) = (None, None, None, None) - # install the template module - template_module = self._transfer_module(conn, tmp, 'template') + if not self.is_playbook: - # transfer module vars - if self.module_vars: - vars = utils.bigjson(self.module_vars) - vars_path = self._transfer_str(conn, tmp, 'module_vars', vars) - vars_arg=" vars=%s"%(vars_path) - else: - vars_arg="" + # templating remotely, since we don't have the benefit of SETUP_CACHE + # TODO: maybe just fetch the setup file to a tempfile + + # first copy the source template over + temppath = tmp + os.path.split(source)[-1] + conn.put_file(utils.path_dwim(self.basedir, source), temppath) + + # install the template module + template_module = self._transfer_module(conn, tmp, 'template') + + # transfer module vars + if self.module_vars: + vars = utils.bigjson(self.module_vars) + vars_path = self._transfer_str(conn, tmp, 'module_vars', vars) + vars_arg=" vars=%s"%(vars_path) + else: + vars_arg="" - # run the template module - args = "src=%s dest=%s metadata=%s%s" % (temppath, dest, metadata, vars_arg) - (result1, err, executed) = self._execute_module(conn, tmp, template_module, args) - (host, ok, data, err) = self._return_from_module(conn, host, result1, err, executed) + # run the template module + args = "src=%s dest=%s metadata=%s%s" % (temppath, dest, metadata, vars_arg) + (result1, err, executed) = self._execute_module(conn, tmp, template_module, args) + (host, ok, data, err) = self._return_from_module(conn, host, result1, err, executed) + + else: + # templating LOCALLY to avoid Jinja2 dependency on nodes inside playbook runs + # non-playbook path can be moved to use this IF it is willing to fetch + # the metadata file first + + # install the template module + copy_module = self._transfer_module(conn, tmp, 'copy') + + # playbooks can template locally to avoid the jinja2 dependency + source_data = file(utils.path_dwim(self.basedir, source)).read() + + resultant = '' + try: + resultant = utils.template(source_data, inject) + except Exception, e: + return (host, False, dict(failed=True, msg=str(e)), '') + xfered = self._transfer_str(conn, tmp, 'source', resultant) + + # run the COPY module + args = "src=%s dest=%s" % (xfered, dest) + (result1, err, executed) = self._execute_module(conn, tmp, copy_module, args) + (host, ok, data, err) = self._return_from_module(conn, host, result1, err, executed) + if ok: return self._chain_file_module(conn, tmp, data, err, options, executed) From 30d06dbceaf3a240d80bae60a60a72936f59ed4f Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Apr 2012 22:23:33 -0400 Subject: [PATCH 46/83] Don't force down ansible facts back to setup, the setup module won't like parsing them on input and that data is already there. --- lib/ansible/runner.py | 2 +- test/TestRunner.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index f5a6de3f2a3..883c18860df 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -229,7 +229,7 @@ class Runner(object): # TODO: keep this as a dict through the whole path to simplify this code for (k,v) in inject.iteritems(): - if not k.startswith('facter_') and not k.startswith('ohai_'): + if not k.startswith('facter_') and not k.startswith('ohai_') and not k.startswith('ansible_'): if not is_dict: if str(v).find(" ") != -1: v = "\"%s\"" % v diff --git a/test/TestRunner.py b/test/TestRunner.py index 529b4a6965a..aac8c41b06b 100644 --- a/test/TestRunner.py +++ b/test/TestRunner.py @@ -178,6 +178,7 @@ class TestRunner(unittest.TestCase): # almost every time so changed is always true, this just tests that # rewriting the file is ok result = self._run('setup', [ "metadata=%s" % output, "a=2", "b=3", "c=4" ]) + print "RAW RESULT=%s" % result assert 'md5sum' in result def test_async(self): From 9cd492befea21be4df1865a36076dfaf1e446c74 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Apr 2012 22:43:17 -0400 Subject: [PATCH 47/83] make all templating happen locally, so no jinja2 deps are ever required --- lib/ansible/runner.py | 91 ++++++++++++++------------------ library/template | 120 ++---------------------------------------- 2 files changed, 43 insertions(+), 168 deletions(-) diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index 883c18860df..0b6ede10044 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -28,6 +28,7 @@ import traceback import tempfile import subprocess import getpass +import base64 import ansible.constants as C import ansible.connection @@ -456,13 +457,7 @@ class Runner(object): if source is None or dest is None: return (host, True, dict(failed=True, msg="src and dest are required"), '') - if metadata is None: - if self.remote_user == 'root': - metadata = '/etc/ansible/setup' - else: - metadata = '~/.ansible/setup' - - # apply templating to source argument + # apply templating to source argument so vars can be used in the path inject = self.setup_cache.get(conn.host,{}) source = utils.template(source, inject) @@ -470,53 +465,45 @@ class Runner(object): if not self.is_playbook: - # templating remotely, since we don't have the benefit of SETUP_CACHE - # TODO: maybe just fetch the setup file to a tempfile - - # first copy the source template over - temppath = tmp + os.path.split(source)[-1] - conn.put_file(utils.path_dwim(self.basedir, source), temppath) - - # install the template module - template_module = self._transfer_module(conn, tmp, 'template') - - # transfer module vars - if self.module_vars: - vars = utils.bigjson(self.module_vars) - vars_path = self._transfer_str(conn, tmp, 'module_vars', vars) - vars_arg=" vars=%s"%(vars_path) - else: - vars_arg="" - - # run the template module - args = "src=%s dest=%s metadata=%s%s" % (temppath, dest, metadata, vars_arg) - (result1, err, executed) = self._execute_module(conn, tmp, template_module, args) - (host, ok, data, err) = self._return_from_module(conn, host, result1, err, executed) - - else: - # templating LOCALLY to avoid Jinja2 dependency on nodes inside playbook runs - # non-playbook path can be moved to use this IF it is willing to fetch - # the metadata file first - - # install the template module - copy_module = self._transfer_module(conn, tmp, 'copy') - - # playbooks can template locally to avoid the jinja2 dependency - source_data = file(utils.path_dwim(self.basedir, source)).read() - - resultant = '' - try: - resultant = utils.template(source_data, inject) - except Exception, e: - return (host, False, dict(failed=True, msg=str(e)), '') - xfered = self._transfer_str(conn, tmp, 'source', resultant) + # not running from a playbook so we have to fetch the remote + # setup file contents before proceeding... + if metadata is None: + if self.remote_user == 'root': + metadata = '/etc/ansible/setup' + else: + metadata = '~/.ansible/setup' - # run the COPY module - args = "src=%s dest=%s" % (xfered, dest) - (result1, err, executed) = self._execute_module(conn, tmp, copy_module, args) - (host, ok, data, err) = self._return_from_module(conn, host, result1, err, executed) - + # install the template module + slurp_module = self._transfer_module(conn, tmp, 'slurp') + # run the slurp module to get the metadata file + args = "src=%s" % metadata + (result1, err, executed) = self._execute_module(conn, tmp, slurp_module, args) + result1 = utils.json_loads(result1) + if not 'content' in result1 or result1.get('encoding','base64') != 'base64': + result1['failed'] = True + return self._return_from_module(conn, host, result1, err, executed) + content = base64.b64decode(result1['content']) + inject = utils.json_loads(content) + + # install the template module + copy_module = self._transfer_module(conn, tmp, 'copy') + + # template the source data locally + source_data = file(utils.path_dwim(self.basedir, source)).read() + resultant = '' + try: + resultant = utils.template(source_data, inject) + except Exception, e: + return (host, False, dict(failed=True, msg=str(e)), '') + xfered = self._transfer_str(conn, tmp, 'source', resultant) + + # run the COPY module + args = "src=%s dest=%s" % (xfered, dest) + (result1, err, executed) = self._execute_module(conn, tmp, copy_module, args) + (host, ok, data, err) = self._return_from_module(conn, host, result1, err, executed) + + # modify file attribs if needed if ok: return self._chain_file_module(conn, tmp, data, err, options, executed) else: diff --git a/library/template b/library/template index 31a2095e502..a290899c5ca 100755 --- a/library/template +++ b/library/template @@ -17,120 +17,8 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . -import sys -import os -import jinja2 -import shlex -try: - import json -except ImportError: - import simplejson as json - -environment = jinja2.Environment() - -# =========================================== -# convert arguments of form a=b c=d -# to a dictionary -# FIXME: make more idiomatic - -if len(sys.argv) == 1: - sys.exit(1) -argfile = sys.argv[1] -if not os.path.exists(argfile): - sys.exit(1) -items = shlex.split(open(argfile, 'r').read()) - -params = {} -for x in items: - (k, v) = x.split("=") - params[k] = v - -source = params['src'] -dest = params['dest'] -metadata = params.get('metadata', '/etc/ansible/setup') -metadata = os.path.expanduser(metadata) -module_vars = params.get('vars') - -# raise an error if there is no template metadata -if not os.path.exists(metadata): - print json.dumps({ - "failed" : 1, - "msg" : "Missing %s, did you run the setup module yet?" % metadata - }) - sys.exit(1) - -# raise an error if we can't parse the template metadata -#data = {} -try: - f = open(metadata) - data = json.loads(f.read()) - f.close() -except: - print json.dumps({ - "failed" : 1, - "msg" : "Failed to parse/load %s, rerun the setup module?" % metadata - }) - sys.exit(1) - -if module_vars: - try: - f = open(module_vars) - vars = json.loads(f.read()) - data.update(vars) - f.close() - except: - print json.dumps({ - "failed" : 1, - "msg" : "Failed to parse/load %s." % module_vars - }) - sys.exit(1) - -if not os.path.exists(source): - print json.dumps({ - "failed" : 1, - "msg" : "Source template could not be read: %s" % source - }) - sys.exit(1) - -source = file(source).read() - -if os.path.isdir(dest): - print json.dumps({ - "failed" : 1, - "msg" : "Destination is a directory" - }) - sys.exit(1) - -# record md5sum of original source file so we can report if it changed -changed = False -md5sum = None -if os.path.exists(dest): - md5sum = os.popen("md5sum %s" % dest).read().split()[0] - -try: - # call Jinja2 here and save the new template file - template = environment.from_string(source) - data_out = template.render(data) -except jinja2.TemplateError, e: - print json.dumps({ - "failed": True, - "msg" : e.message - }) - sys.exit(1) -f = open(dest, "w+") -f.write(data_out) -f.close() - -# record m5sum and return success and whether things have changed -md5sum2 = os.popen("md5sum %s" % dest).read().split()[0] - -if md5sum != md5sum2: - changed = True - -# mission accomplished -print json.dumps({ - "md5sum" : md5sum2, - "changed" : changed -}) - +# hey the Ansible template module isn't really a remote transferred +# module. All the magic happens in Runner.py making use of the +# copy module, and if not running from a playbook, also the 'slurp' +# module. From 8539f4926a41d2037e43c3bb30b06c468179d376 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Apr 2012 22:50:09 -0400 Subject: [PATCH 48/83] Move debian under packaging subdirectory --- {debian => packaging/debian}/ansible.dirs | 0 {debian => packaging/debian}/ansible.install | 0 {debian => packaging/debian}/changelog | 0 {debian => packaging/debian}/compat | 0 {debian => packaging/debian}/control | 0 {debian => packaging/debian}/copyright | 0 {debian => packaging/debian}/docs | 0 {debian => packaging/debian}/pycompat | 0 {debian => packaging/debian}/rules | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename {debian => packaging/debian}/ansible.dirs (100%) rename {debian => packaging/debian}/ansible.install (100%) rename {debian => packaging/debian}/changelog (100%) rename {debian => packaging/debian}/compat (100%) rename {debian => packaging/debian}/control (100%) rename {debian => packaging/debian}/copyright (100%) rename {debian => packaging/debian}/docs (100%) rename {debian => packaging/debian}/pycompat (100%) rename {debian => packaging/debian}/rules (100%) diff --git a/debian/ansible.dirs b/packaging/debian/ansible.dirs similarity index 100% rename from debian/ansible.dirs rename to packaging/debian/ansible.dirs diff --git a/debian/ansible.install b/packaging/debian/ansible.install similarity index 100% rename from debian/ansible.install rename to packaging/debian/ansible.install diff --git a/debian/changelog b/packaging/debian/changelog similarity index 100% rename from debian/changelog rename to packaging/debian/changelog diff --git a/debian/compat b/packaging/debian/compat similarity index 100% rename from debian/compat rename to packaging/debian/compat diff --git a/debian/control b/packaging/debian/control similarity index 100% rename from debian/control rename to packaging/debian/control diff --git a/debian/copyright b/packaging/debian/copyright similarity index 100% rename from debian/copyright rename to packaging/debian/copyright diff --git a/debian/docs b/packaging/debian/docs similarity index 100% rename from debian/docs rename to packaging/debian/docs diff --git a/debian/pycompat b/packaging/debian/pycompat similarity index 100% rename from debian/pycompat rename to packaging/debian/pycompat diff --git a/debian/rules b/packaging/debian/rules similarity index 100% rename from debian/rules rename to packaging/debian/rules From 5bf32f9c9023eccdef56b1c5733009744d2379d6 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Apr 2012 22:51:54 -0400 Subject: [PATCH 49/83] Add gentoo packaging pointer --- packaging/gentoo/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 packaging/gentoo/README.md diff --git a/packaging/gentoo/README.md b/packaging/gentoo/README.md new file mode 100644 index 00000000000..7420860642d --- /dev/null +++ b/packaging/gentoo/README.md @@ -0,0 +1,3 @@ +Gentoo ebuilds are available here: + +https://github.com/uu/ubuilds From b70ed206c630d83d6d72a61f15fb7ac057a27854 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Apr 2012 23:26:08 -0400 Subject: [PATCH 50/83] Add debian build notes... makefile target pending, some changes to instructions possibly needed due to moving into packaging/debian? --- packaging/debian/README.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 packaging/debian/README.txt diff --git a/packaging/debian/README.txt b/packaging/debian/README.txt new file mode 100644 index 00000000000..88bda090909 --- /dev/null +++ b/packaging/debian/README.txt @@ -0,0 +1,11 @@ +I have added a debian folder for use in building a .deb file for ansible. From the ansible directory you can run the following command to construct a debian package of ansible. + +~/ansible$ dpkg-buildpackage -us -uc -rfakeroot + +The debian package files will be placed in the ../ directory and can be installed with the following command: +~/$ sudo dpkg -i .deb + +Dpkg -i doesn't resolve dependencies, so if the previous command fails because of dependencies, you will need to run the following to install the dependencies (if needed) and then re-run the dpkg -i command to install the package: +$ sudo apt-get -f install + +--Henry Graham From 51dfb6f88c1f6e82ec15e501117f8160cf73bf39 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 19 Apr 2012 00:14:37 -0400 Subject: [PATCH 51/83] Add PyYAML requirement --- packaging/rpm/ansible.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/packaging/rpm/ansible.spec b/packaging/rpm/ansible.spec index 4229d12f880..9a394d51359 100644 --- a/packaging/rpm/ansible.spec +++ b/packaging/rpm/ansible.spec @@ -16,6 +16,7 @@ Url: http://ansible.github.com BuildArch: noarch BuildRequires: python2-devel +Requires: PyYAML Requires: python-paramiko Requires: python-jinja2 From 903e4f6eaeaa522a0acd9fe661e67403c05e2a1b Mon Sep 17 00:00:00 2001 From: Jeroen Hoekx Date: Thu, 19 Apr 2012 08:26:54 +0200 Subject: [PATCH 52/83] Support dicts in inventory vars. --- lib/ansible/inventory.py | 15 ++++++++++----- test/TestInventory.py | 4 ++-- test/yaml_hosts | 4 +++- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/ansible/inventory.py b/lib/ansible/inventory.py index 3295f3f2dd8..18d64d60294 100644 --- a/lib/ansible/inventory.py +++ b/lib/ansible/inventory.py @@ -216,11 +216,16 @@ class Inventory(object): def _parse_yaml_host(self, item, variables=[]): def set_variables(host, variables): - 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) + 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) diff --git a/test/TestInventory.py b/test/TestInventory.py index 750cbf189ac..96991d77d00 100644 --- a/test/TestInventory.py +++ b/test/TestInventory.py @@ -220,13 +220,13 @@ class TestInventory(unittest.TestCase): inventory = self.yaml_inventory() vars = inventory.get_variables('saturn') - assert vars == {"moon":"titan"} + assert vars == {"moon":"titan", "moon2":"enceladus"} def test_yaml_port(self): inventory = self.yaml_inventory() vars = inventory.get_variables('hera') - assert vars == {'ansible_ssh_port': 3000} + assert vars == {'ansible_ssh_port': 3000, 'ntp_server': 'olympus.example.com'} ### Test Runner class method diff --git a/test/yaml_hosts b/test/yaml_hosts index 7568ff4bda2..628c2843506 100644 --- a/test/yaml_hosts +++ b/test/yaml_hosts @@ -3,7 +3,8 @@ - jupiter - host: saturn vars: - - moon: titan + moon: titan + moon2: enceladus - zeus @@ -14,6 +15,7 @@ - poseidon vars: - ansible_ssh_port: 3000 + - ntp_server: olympus.example.com - group: norse hosts: From cdb8213dccbc8b997ca942d121aa4ce47c3e680b Mon Sep 17 00:00:00 2001 From: Jeroen Hoekx Date: Thu, 19 Apr 2012 08:49:09 +0200 Subject: [PATCH 53/83] Supported 'listed' vars in playbooks. --- lib/ansible/playbook.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py index 3e8a79d364e..0f08a18e2d8 100755 --- a/lib/ansible/playbook.py +++ b/lib/ansible/playbook.py @@ -118,8 +118,18 @@ class PlayBook(object): if play.get('vars') is None: play['vars'] = {} vars = play['vars'] - if type(vars) != dict: + if type(vars) not in [dict, list]: raise errors.AnsibleError("'vars' section must contain only key/value pairs") + + # translate a list of vars into a dict + if type(vars) == list: + varlist = vars + vars = {} + for item in varlist: + k, v = item.items()[0] + vars[k] = v + play['vars'] = vars + vars_prompt = play.get('vars_prompt', {}) if type(vars_prompt) != dict: raise errors.AnsibleError("'vars_prompt' section must contain only key/value pairs") From 4578b1721325488d059273aec461c9c74b165532 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 19 Apr 2012 09:03:40 -0400 Subject: [PATCH 54/83] Add missing file (slurp) used by templates in non-playbook mode. --- library/slurp | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100755 library/slurp diff --git a/library/slurp b/library/slurp new file mode 100755 index 00000000000..8b80e5da4b9 --- /dev/null +++ b/library/slurp @@ -0,0 +1,71 @@ +#!/usr/bin/python + +# (c) 2012, Michael DeHaan +# +# 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 . + +import sys +import os +import shlex +import base64 + +try: + import json +except ImportError: + import simplejson as json + +# =========================================== +# convert arguments of form a=b c=d +# to a dictionary + +if len(sys.argv) == 1: + sys.exit(1) +argfile = sys.argv[1] +if not os.path.exists(argfile): + sys.exit(1) +items = shlex.split(open(argfile, 'r').read()) + +params = {} +for x in items: + (k, v) = x.split("=") + params[k] = v +source = params['src'] + +# ========================================== + +# raise an error if there is no template metadata +if not os.path.exists(source): + print json.dumps(dict( + failed = 1, + msg = "file not found: %s" % metadata + )) + sys.exit(1) + +if not os.access(source, os.R_OK): + print json.dumps(dict( + failed = 1, + msg = "file is not readable: %s" % source + )) + sys.exit(1) + +# ========================================== + +data = file(source).read() +data = base64.b64encode(data) + +print json.dumps(dict(content=data, encoding='base64')) +sys.exit(0) + From a672a5549abf4fd46072705fb0af462562a897d2 Mon Sep 17 00:00:00 2001 From: Michel Blanc Date: Thu, 19 Apr 2012 15:30:26 +0200 Subject: [PATCH 55/83] Adds ArchLinux build file Adds PKGBUILD file required to build Arch Linux packages --- packaging/arch/PKGBUILD | 44 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 packaging/arch/PKGBUILD diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD new file mode 100644 index 00000000000..63b6e4330ab --- /dev/null +++ b/packaging/arch/PKGBUILD @@ -0,0 +1,44 @@ +#Maintainer: Michel Blanc +pkgname=ansible-git +pkgver=20120419 +pkgrel=1 +pkgdesc="A radically simple deployment, model-driven configuration management, and command execution framework" +arch=('any') +url="https://github.com/ansible/ansible" +license=('GPL3') +depends=('python2' 'python-paramiko>=1.7.7' 'python2-jinja' 'python-simplejson') +makedepends=('git' 'asciidoc') + +_gitroot="https://github.com/ansible/ansible" +_gitname="ansible" + +build() { + cd "$srcdir" + msg "Connecting to GIT server...." + + if [ -d $_gitname ] ; then + cd $_gitname && git pull origin + msg "The local files are updated." + else + git clone $_gitroot $_gitname + fi + + msg "GIT checkout done or server timeout" + + cd "$srcdir/$_gitname" + make +} + +package() { + cd "$srcdir/$_gitname" + + mkdir -p ${pkgdir}/usr/share/ansible + cp ./library/* ${pkgdir}/usr/share/ansible/ + python setup.py install -O1 --root=${pkgdir} + + install -D docs/man/man1/ansible.1 ${pkgdir}/usr/share/man/man1/ansible.1 + install -D docs/man/man1/ansible-playbook.1 ${pkgdir}/usr/share/man/man1/ansible-playbook.1 + + gzip -9 ${pkgdir}/usr/share/man/man1/ansible.1 + gzip -9 ${pkgdir}/usr/share/man/man1/ansible-playbook.1 +} From 3fae2ea8a4aa9ddca6edf16c583184398bc5f504 Mon Sep 17 00:00:00 2001 From: Michel Blanc Date: Thu, 19 Apr 2012 19:26:41 +0200 Subject: [PATCH 56/83] Fixes Arch PKGBUILD dependencies Adds missing depends (python2-yaml helps...) Adds missing build deps (fakeroot) --- packaging/arch/PKGBUILD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD index 63b6e4330ab..59a64e669c7 100644 --- a/packaging/arch/PKGBUILD +++ b/packaging/arch/PKGBUILD @@ -6,8 +6,8 @@ pkgdesc="A radically simple deployment, model-driven configuration management, a arch=('any') url="https://github.com/ansible/ansible" license=('GPL3') -depends=('python2' 'python-paramiko>=1.7.7' 'python2-jinja' 'python-simplejson') -makedepends=('git' 'asciidoc') +depends=('python2' 'python2-yaml' 'python-paramiko>=1.7.7' 'python2-jinja' 'python-simplejson') +makedepends=('git' 'asciidoc' 'fakeroot') _gitroot="https://github.com/ansible/ansible" _gitname="ansible" From 516df5f64fa12b33282848e3598031abcd6b0312 Mon Sep 17 00:00:00 2001 From: Tim Bielawa Date: Thu, 19 Apr 2012 13:42:44 -0400 Subject: [PATCH 57/83] Move packaging related things out of the root directory: Distutils MANIFEST.in, setup.py -> packaging/distutils/ directory. --- Makefile | 11 ++++++++--- MANIFEST.in => packaging/distutils/MANIFEST.in | 1 + setup.py => packaging/distutils/setup.py | 0 packaging/rpm/ansible.spec | 6 ++++-- 4 files changed, 13 insertions(+), 5 deletions(-) rename MANIFEST.in => packaging/distutils/MANIFEST.in (78%) rename setup.py => packaging/distutils/setup.py (100%) diff --git a/Makefile b/Makefile index b6aa3bba8a0..dae28e5b9b9 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,11 @@ RPMVERSION := $(shell awk '/Version/{print $$2; exit}' < $(RPMSPEC) | cut -d "%" RPMRELEASE := $(shell awk '/Release/{print $$2; exit}' < $(RPMSPEC) | cut -d "%" -f1) RPMDIST = $(shell rpm --eval '%dist') RPMNVR = "$(NAME)-$(RPMVERSION)-$(RPMRELEASE)$(RPMDIST)" +# Python distutils options +DUDIR = packaging/distutils +DUSETUP = $(DUDIR)/setup.py +DUMANIFEST = $(DUDIR)/MANIFEST.in + all: clean python @@ -65,15 +70,15 @@ clean: rm -rf MANIFEST rpm-build python: - python setup.py build + python $(DUSETUP) build install: mkdir -p /usr/share/ansible cp ./library/* /usr/share/ansible/ - python setup.py install + python $(DUSETUP) install sdist: clean - python ./setup.py sdist + python ./$(DUSETUP) sdist -t $(DUMANIFEST) rpmcommon: sdist @mkdir -p rpm-build diff --git a/MANIFEST.in b/packaging/distutils/MANIFEST.in similarity index 78% rename from MANIFEST.in rename to packaging/distutils/MANIFEST.in index 60bc97f1872..4765bd548dd 100644 --- a/MANIFEST.in +++ b/packaging/distutils/MANIFEST.in @@ -1,5 +1,6 @@ include README.md packaging/rpm/ansible.spec include examples/hosts +include packaging/distutils/setup.py recursive-include docs * recursive-include library * include Makefile diff --git a/setup.py b/packaging/distutils/setup.py similarity index 100% rename from setup.py rename to packaging/distutils/setup.py diff --git a/packaging/rpm/ansible.spec b/packaging/rpm/ansible.spec index 9a394d51359..bd1350e73d6 100644 --- a/packaging/rpm/ansible.spec +++ b/packaging/rpm/ansible.spec @@ -2,6 +2,8 @@ %{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot %endif +%define _dusetup packaging/distutils/setup.py + Name: ansible Release: 1%{?dist} @@ -33,10 +35,10 @@ are transferred to managed machines automatically. %setup -q %build -%{__python} setup.py build +%{__python} %{_dusetup} build %install -%{__python} setup.py install -O1 --root=$RPM_BUILD_ROOT +%{__python} %{_dusetup} install -O1 --root=$RPM_BUILD_ROOT mkdir -p $RPM_BUILD_ROOT/etc/ansible/ cp examples/hosts $RPM_BUILD_ROOT/etc/ansible/ mkdir -p $RPM_BUILD_ROOT/%{_mandir}/man1/ From 626e8f3543bdc57738c2dc1f5af509f027d6bfcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20N=C3=A9ri?= Date: Thu, 19 Apr 2012 23:40:44 +0200 Subject: [PATCH 58/83] Fix two misspellings of the apt module's "fail_json" function --- library/apt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/apt b/library/apt index 90e4b17a1bd..81b40ea2a08 100755 --- a/library/apt +++ b/library/apt @@ -76,7 +76,7 @@ def install(pkgspec, cache, upgrade=False): cmd = "%s -q -y install '%s'" % (APT, pkgspec) rc, out, err = run_apt(cmd) if rc: - json_fail(msg="'apt-get install %s' failed: %s" % (pkgspec, err)) + fail_json(msg="'apt-get install %s' failed: %s" % (pkgspec, err)) return True else: return False @@ -90,7 +90,7 @@ def remove(pkgspec, cache, purge=False): cmd = "%s -q -y %s remove '%s'" % (APT, purge, pkgspec) rc, out, err = run_apt(cmd) if rc: - json_fail(msg="'apt-get remove %s' failed: %s" % (pkgspec, err)) + fail_json(msg="'apt-get remove %s' failed: %s" % (pkgspec, err)) return True From 70734f5968f54e4031c0b41b4311795e0aa5f063 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 19 Apr 2012 09:21:21 -0400 Subject: [PATCH 59/83] Update bin/ansible to fix usage of inventory API + no more verbose option --- bin/ansible | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/ansible b/bin/ansible index 9eb5d205d2c..1a227b35319 100755 --- a/bin/ansible +++ b/bin/ansible @@ -104,7 +104,7 @@ class Cli(object): module_args="jid=%s" % jid, remote_user=old_runner.remote_user, remote_pass=old_runner.remote_pass, inventory=old_runner.inventory, timeout=old_runner.timeout, forks=old_runner.forks, - pattern='*', callbacks=self.silent_callbacks, verbose=True, + pattern='*', callbacks=self.silent_callbacks, ) # ---------------------------------------------- @@ -140,7 +140,7 @@ class Cli(object): runner.inventory.restrict_to(poll_hosts) polling_runner = self.get_polling_runner(runner, jid) poll_results = polling_runner.run() - runner.inventory.lift_restrictions() + runner.inventory.lift_restriction() if poll_results is None: break for (host, host_result) in poll_results['contacted'].iteritems(): From 13ba31231e38c45001a0b9b8a19964dd8249a647 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 19 Apr 2012 11:38:44 -0400 Subject: [PATCH 60/83] Fixup slurp module usage when not running as root, fix error handling path in slurp module. --- lib/ansible/runner.py | 2 +- library/slurp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index 0b6ede10044..323ebe71221 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -471,7 +471,7 @@ class Runner(object): if self.remote_user == 'root': metadata = '/etc/ansible/setup' else: - metadata = '~/.ansible/setup' + metadata = "/home/%s/.ansible/setup" % self.remote_user # install the template module slurp_module = self._transfer_module(conn, tmp, 'slurp') diff --git a/library/slurp b/library/slurp index 8b80e5da4b9..ab0c879db39 100755 --- a/library/slurp +++ b/library/slurp @@ -50,7 +50,7 @@ source = params['src'] if not os.path.exists(source): print json.dumps(dict( failed = 1, - msg = "file not found: %s" % metadata + msg = "file not found: %s" % source )) sys.exit(1) From c6b8e1621dec9ca54f716a907437ba817f2beb75 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 20 Apr 2012 07:54:38 -0400 Subject: [PATCH 61/83] A better fix for slurp, expand path in the module. --- lib/ansible/runner.py | 3 ++- library/slurp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index 323ebe71221..2a49b9bbfc1 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -471,7 +471,8 @@ class Runner(object): if self.remote_user == 'root': metadata = '/etc/ansible/setup' else: - metadata = "/home/%s/.ansible/setup" % self.remote_user + # path is expanded on remote side + metadata = "~/.ansible/setup" # install the template module slurp_module = self._transfer_module(conn, tmp, 'slurp') diff --git a/library/slurp b/library/slurp index ab0c879db39..36e84ecc09d 100755 --- a/library/slurp +++ b/library/slurp @@ -42,7 +42,7 @@ params = {} for x in items: (k, v) = x.split("=") params[k] = v -source = params['src'] +source = os.path.expanduser(params['src']) # ========================================== From 445e48b9919f7854617d98b91798e3f454a2372c Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 20 Apr 2012 07:57:39 -0400 Subject: [PATCH 62/83] Call os.path.expanduser in modules so things work as expected even when using ./hacking/test-module script --- library/copy | 4 ++-- library/file | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/library/copy b/library/copy index fecfeeafacb..ef6c30a6b66 100755 --- a/library/copy +++ b/library/copy @@ -40,8 +40,8 @@ for x in items: (k, v) = x.split("=") params[k] = v -src = params['src'] -dest = params['dest'] +src = os.path.expanduser(params['src']) +dest = os.path.expanduser(params['dest']) # raise an error if there is no src file diff --git a/library/file b/library/file index 742a7d7b227..2922c3d0803 100755 --- a/library/file +++ b/library/file @@ -88,8 +88,8 @@ for x in items: params[k] = v state = params.get('state','file') -path = params.get('path', params.get('dest', params.get('name', None))) -src = params.get('src', None) +path = os.path.expanduser(params.get('path', params.get('dest', params.get('name', None)))) +src = os.path.expanduser(params.get('src', None)) dest = params.get('dest', None) mode = params.get('mode', None) owner = params.get('owner', None) From 77944939525a7b86dd33171eaee733244ef1a10d Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 20 Apr 2012 08:02:12 -0400 Subject: [PATCH 63/83] Don't try to expand path for None values --- library/copy | 9 ++++++--- library/file | 8 ++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/library/copy b/library/copy index ef6c30a6b66..b5a52647a95 100755 --- a/library/copy +++ b/library/copy @@ -40,9 +40,12 @@ for x in items: (k, v) = x.split("=") params[k] = v -src = os.path.expanduser(params['src']) -dest = os.path.expanduser(params['dest']) - +src = params['src'] +dest = params['dest'] +if src: + src = os.path.expanduser(src) +if dest: + dest = os.path.expanduser(dest) # raise an error if there is no src file if not os.path.exists(src): diff --git a/library/file b/library/file index 2922c3d0803..b672934e4ee 100755 --- a/library/file +++ b/library/file @@ -88,8 +88,12 @@ for x in items: params[k] = v state = params.get('state','file') -path = os.path.expanduser(params.get('path', params.get('dest', params.get('name', None)))) -src = os.path.expanduser(params.get('src', None)) +path = params.get('path', params.get('dest', params.get('name', None))) +if path: + path = os.path.expanduser(path) +src = params.get('src', None) +if src: + path = os.path.expanduser(src) dest = params.get('dest', None) mode = params.get('mode', None) owner = params.get('owner', None) From c0c691089d8f1b4e22603518698d70de0df87fe9 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 20 Apr 2012 08:09:43 -0400 Subject: [PATCH 64/83] Fix bug in src. Should not code this early :) --- library/file | 2 +- test/TestPlayBook.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/library/file b/library/file index b672934e4ee..90d6892601d 100755 --- a/library/file +++ b/library/file @@ -93,7 +93,7 @@ if path: path = os.path.expanduser(path) src = params.get('src', None) if src: - path = os.path.expanduser(src) + src = os.path.expanduser(src) dest = params.get('dest', None) mode = params.get('mode', None) owner = params.get('owner', None) diff --git a/test/TestPlayBook.py b/test/TestPlayBook.py index 97e3f102c3b..20a00c43cf4 100644 --- a/test/TestPlayBook.py +++ b/test/TestPlayBook.py @@ -140,7 +140,9 @@ class TestPlaybook(unittest.TestCase): callbacks = self.test_callbacks, runner_callbacks = self.test_callbacks ) - return self.playbook.run() + result = self.playbook.run() + print utils.bigjson(dict(events=EVENTS)) + return result def test_one(self): pb = os.path.join(self.test_dir, 'playbook1.yml') From 3d72260887c639b68cf83773e3a03fa91993f4a2 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Sat, 21 Apr 2012 11:26:48 -0400 Subject: [PATCH 65/83] Make it such that modules with no arguments work fine in playbooks (like ping, which is non-sensical, but also if the user wrote a module that took none) --- lib/ansible/playbook.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py index 0f08a18e2d8..791d3811784 100755 --- a/lib/ansible/playbook.py +++ b/lib/ansible/playbook.py @@ -378,7 +378,9 @@ class PlayBook(object): tokens = action.split(None, 1) module_name = tokens[0] - module_args = tokens[1] + module_args = '' + if len(tokens) > 1: + module_args = tokens[1] # include task specific vars module_vars = task.get('vars') From 3081bb93f162d0591a3bd6db9d2a9bb01ca263ec Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Sat, 21 Apr 2012 11:38:39 -0400 Subject: [PATCH 66/83] Use /var/tmp for root by default to avoid /tmp being mounted noexec, and segregate tmp files for other users into their home directories. --- lib/ansible/runner.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index 2a49b9bbfc1..f49c02ff1ec 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -187,7 +187,7 @@ class Runner(object): if type(files) == str: files = [ files ] for filename in files: - if not filename.startswith('/tmp/'): + if filename.find('/tmp/') == -1: raise Exception("not going to happen") self._exec_command(conn, "rm -rf %s" % filename, None) @@ -599,7 +599,14 @@ class Runner(object): def _get_tmp_path(self, conn): ''' gets a temporary path on a remote box ''' - result, err = self._exec_command(conn, "mktemp -d /tmp/ansible.XXXXXX", None, sudoable=False) + basetmp = "/var/tmp" + if self.remote_user != 'root': + basetmp = "/home/%s/.ansible/tmp" % self.remote_user + cmd = "mktemp -d %s/ansible.XXXXXX" % basetmp + if self.remote_user != 'root': + cmd = "mkdir -p %s && %s" % (basetmp, cmd) + + result, err = self._exec_command(conn, cmd, None, sudoable=False) cleaned = result.split("\n")[0].strip() + '/' return cleaned From a8d748220b9017c0508af95d19d054605e9a0a5c Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Sat, 21 Apr 2012 11:46:32 -0400 Subject: [PATCH 67/83] Don't prompt for password info if the pattern wouldn't have matched any hosts. Also convert Runner() usage to the new inventory model. --- bin/ansible | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/bin/ansible b/bin/ansible index 1a227b35319..d3eafd2d362 100755 --- a/bin/ansible +++ b/bin/ansible @@ -29,6 +29,7 @@ import ansible.constants as C from ansible import utils from ansible import errors from ansible import callbacks +from ansible import inventory ######################################################## @@ -69,6 +70,13 @@ class Cli(object): ''' use Runner lib to do SSH things ''' pattern = args[0] + + inventory_manager = inventory.Inventory(options.inventory) + hosts = inventory_manager.list_hosts(pattern) + if len(hosts) == 0: + print >>sys.stderr, "No hosts matched" + sys.exit(1) + sshpass = None sudopass = None if options.ask_pass: @@ -78,7 +86,6 @@ class Cli(object): if options.tree: utils.prepare_writeable_dir(options.tree) - if options.seconds: print "background launch...\n\n" @@ -86,7 +93,7 @@ class Cli(object): module_name=options.module_name, module_path=options.module_path, module_args=options.module_args, remote_user=options.remote_user, remote_pass=sshpass, - host_list=options.inventory, timeout=options.timeout, + inventory=inventory_manager, timeout=options.timeout, forks=options.forks, background=options.seconds, pattern=pattern, callbacks=self.callbacks, sudo=options.sudo, From f72114c657bb663c046441f5134694435f9a520d Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Sat, 21 Apr 2012 11:56:06 -0400 Subject: [PATCH 68/83] Move setup.py stuff back to top level as "make sdist" was not working, plus, it makes sources cleaner. --- packaging/distutils/MANIFEST.in => MANIFEST.in | 0 Makefile | 18 ++++++++++-------- packaging/rpm/ansible.spec | 6 ++---- packaging/distutils/setup.py => setup.py | 0 4 files changed, 12 insertions(+), 12 deletions(-) rename packaging/distutils/MANIFEST.in => MANIFEST.in (100%) rename packaging/distutils/setup.py => setup.py (100%) diff --git a/packaging/distutils/MANIFEST.in b/MANIFEST.in similarity index 100% rename from packaging/distutils/MANIFEST.in rename to MANIFEST.in diff --git a/Makefile b/Makefile index dae28e5b9b9..72e9608df4d 100644 --- a/Makefile +++ b/Makefile @@ -16,11 +16,6 @@ RPMVERSION := $(shell awk '/Version/{print $$2; exit}' < $(RPMSPEC) | cut -d "%" RPMRELEASE := $(shell awk '/Release/{print $$2; exit}' < $(RPMSPEC) | cut -d "%" -f1) RPMDIST = $(shell rpm --eval '%dist') RPMNVR = "$(NAME)-$(RPMVERSION)-$(RPMRELEASE)$(RPMDIST)" -# Python distutils options -DUDIR = packaging/distutils -DUSETUP = $(DUDIR)/setup.py -DUMANIFEST = $(DUDIR)/MANIFEST.in - all: clean python @@ -70,15 +65,15 @@ clean: rm -rf MANIFEST rpm-build python: - python $(DUSETUP) build + python setup.py build install: mkdir -p /usr/share/ansible cp ./library/* /usr/share/ansible/ - python $(DUSETUP) install + python setup.py install sdist: clean - python ./$(DUSETUP) sdist -t $(DUMANIFEST) + python setup.py sdist -t MANIFEST.in rpmcommon: sdist @mkdir -p rpm-build @@ -109,3 +104,10 @@ rpm: rpmcommon @echo "Ansible RPM is built:" @echo " rpm-build/noarch/$(RPMNVR).noarch.rpm" @echo "#############################################" + +debian: + # stub target, FIXME! + (cd packaging/debian; dpkg-buildpackage -us -uc -rfakeroot) + +# for arch or gentoo, read instructions in the appropriate 'packaging' subdirectory directory + diff --git a/packaging/rpm/ansible.spec b/packaging/rpm/ansible.spec index bd1350e73d6..9a394d51359 100644 --- a/packaging/rpm/ansible.spec +++ b/packaging/rpm/ansible.spec @@ -2,8 +2,6 @@ %{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot %endif -%define _dusetup packaging/distutils/setup.py - Name: ansible Release: 1%{?dist} @@ -35,10 +33,10 @@ are transferred to managed machines automatically. %setup -q %build -%{__python} %{_dusetup} build +%{__python} setup.py build %install -%{__python} %{_dusetup} install -O1 --root=$RPM_BUILD_ROOT +%{__python} setup.py install -O1 --root=$RPM_BUILD_ROOT mkdir -p $RPM_BUILD_ROOT/etc/ansible/ cp examples/hosts $RPM_BUILD_ROOT/etc/ansible/ mkdir -p $RPM_BUILD_ROOT/%{_mandir}/man1/ diff --git a/packaging/distutils/setup.py b/setup.py similarity index 100% rename from packaging/distutils/setup.py rename to setup.py From 9d0f2a6e9b9fa2007fdb0e91c9f6e353908309de Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Sat, 21 Apr 2012 12:01:15 -0400 Subject: [PATCH 69/83] Unused import --- Makefile | 25 +++++++++++++++++++++++-- lib/ansible/playbook.py | 1 - 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 72e9608df4d..7d910160fd1 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,34 @@ #!/usr/bin/make +######################################################## +# Makefile for Ansible +# +# useful targets: +# make sdist ---------------- produce a tarball +# make rpm ----------------- produce RPMs +# make debian --------------- produce a dpkg (FIXME?) +# make docs ----------------- rebuild the manpages (results are checked in) +# make tests ---------------- run the tests +# make pyflakes, make pep8 -- source code checks + +######################################################## +# variable section + NAME = "ansible" + +# Manpages are currently built with asciidoc -- would like to move to markdown # This doesn't evaluate until it's called. The -D argument is the # directory of the target file ($@), kinda like `dirname`. ASCII2MAN = a2x -D $(dir $@) -d manpage -f manpage $< ASCII2HTMLMAN = a2x -D docs/html/man/ -d manpage -f xhtml -# Space separated list of all the manpages we want to end up with. MANPAGES := docs/man/man1/ansible.1 docs/man/man1/ansible-playbook.1 + SITELIB = $(shell python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()") + +# VERSION file provides one place to update the software version VERSION := $(shell cat VERSION) -# These are for building the RPM. + +# RPM build parameters RPMSPECDIR= packaging/rpm RPMSPEC = $(RPMSPECDIR)/ansible.spec RPMVERSION := $(shell awk '/Version/{print $$2; exit}' < $(RPMSPEC) | cut -d "%" -f1) @@ -17,6 +36,8 @@ RPMRELEASE := $(shell awk '/Release/{print $$2; exit}' < $(RPMSPEC) | cut -d "%" RPMDIST = $(shell rpm --eval '%dist') RPMNVR = "$(NAME)-$(RPMVERSION)-$(RPMRELEASE)$(RPMDIST)" +######################################################## + all: clean python tests: diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py index 791d3811784..ccb87291e07 100755 --- a/lib/ansible/playbook.py +++ b/lib/ansible/playbook.py @@ -22,7 +22,6 @@ import ansible.runner import ansible.constants as C from ansible import utils from ansible import errors -import shlex import os import time From 1e7b60b9a597e21db4ea5e13d12ebb3cf6fdbfd9 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Sat, 21 Apr 2012 12:01:26 -0400 Subject: [PATCH 70/83] Unused import --- lib/ansible/runner.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index f49c02ff1ec..6b378d1b90d 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -26,7 +26,6 @@ import Queue import random import traceback import tempfile -import subprocess import getpass import base64 From ddc034292027299ccb3e374e3059ead8b6539684 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Sat, 21 Apr 2012 12:01:37 -0400 Subject: [PATCH 71/83] Unused import --- lib/ansible/runner.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index 6b378d1b90d..4a3140e1f7a 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -26,7 +26,6 @@ import Queue import random import traceback import tempfile -import getpass import base64 import ansible.constants as C From bed5da6086858ec731bfca1cbfcde9cc22676d46 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Sat, 21 Apr 2012 12:03:29 -0400 Subject: [PATCH 72/83] Remove unused assignment --- lib/ansible/runner.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index 4a3140e1f7a..0e4f0e09fbb 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -417,7 +417,6 @@ class Runner(object): # fetch the file and check for changes conn.fetch_file(source, dest) new_md5 = os.popen("md5sum %s" % dest).read().split()[0] - changed = (new_md5 != local_md5) if new_md5 != remote_md5: return (host, True, dict(failed=True, msg="md5 mismatch", md5sum=new_md5), '') return (host, True, dict(changed=True, md5sum=new_md5), '') From edb8988e8eb222285aef7480377ff0d0b6ad266b Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Sat, 21 Apr 2012 12:04:21 -0400 Subject: [PATCH 73/83] Unused imports --- bin/ansible | 1 - bin/ansible-playbook | 1 - 2 files changed, 2 deletions(-) diff --git a/bin/ansible b/bin/ansible index d3eafd2d362..b18aeb2488e 100755 --- a/bin/ansible +++ b/bin/ansible @@ -22,7 +22,6 @@ import sys import getpass import time -from optparse import OptionParser import ansible.runner import ansible.constants as C diff --git a/bin/ansible-playbook b/bin/ansible-playbook index 403ac19ee47..6dd3a1cee78 100755 --- a/bin/ansible-playbook +++ b/bin/ansible-playbook @@ -20,7 +20,6 @@ import sys import getpass -from optparse import OptionParser import ansible.playbook import ansible.constants as C From e36a52f06c9bd8b649d21aab865d293c424fe429 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Sat, 21 Apr 2012 12:05:15 -0400 Subject: [PATCH 74/83] Ignore pyflakes E261 style output (multiple spaces before inline comment) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7d910160fd1..7bb7b2ab39e 100644 --- a/Makefile +++ b/Makefile @@ -63,7 +63,7 @@ pep8: @echo "#############################################" @echo "# Running PEP8 Compliance Tests" @echo "#############################################" - pep8 -r --ignore=E501,E221,W291,W391,E302,E251,E203,W293,E231,E303,E201,E225 lib/ bin/ + pep8 -r --ignore=E501,E221,W291,W391,E302,E251,E203,W293,E231,E303,E201,E225,E261 lib/ bin/ pyflakes: pyflakes lib/ansible/*.py bin/* From 767282df2a4fc489be590edc2acc105a3febc82b Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Sat, 21 Apr 2012 12:06:54 -0400 Subject: [PATCH 75/83] Small style fixes for indentation and spacing. --- lib/ansible/playbook.py | 2 +- lib/ansible/runner.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py index ccb87291e07..1b8e557791c 100755 --- a/lib/ansible/playbook.py +++ b/lib/ansible/playbook.py @@ -123,7 +123,7 @@ class PlayBook(object): # translate a list of vars into a dict if type(vars) == list: varlist = vars - vars = {} + vars = {} for item in varlist: k, v = item.items()[0] vars[k] = v diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index 0e4f0e09fbb..c305126b342 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -280,7 +280,7 @@ class Runner(object): args = self._add_setup_metadata(args) if type(args) == dict: - args = utils.bigjson(args) + args = utils.bigjson(args) args = utils.template(args, inject) module_name_tail = remote_module_path.split("/")[-1] @@ -598,10 +598,10 @@ class Runner(object): basetmp = "/var/tmp" if self.remote_user != 'root': - basetmp = "/home/%s/.ansible/tmp" % self.remote_user + basetmp = "/home/%s/.ansible/tmp" % self.remote_user cmd = "mktemp -d %s/ansible.XXXXXX" % basetmp if self.remote_user != 'root': - cmd = "mkdir -p %s && %s" % (basetmp, cmd) + cmd = "mkdir -p %s && %s" % (basetmp, cmd) result, err = self._exec_command(conn, cmd, None, sudoable=False) cleaned = result.split("\n")[0].strip() + '/' From 533c2c61269e0a4888a60e5843ebb1682c9257e8 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Sat, 21 Apr 2012 12:45:37 -0400 Subject: [PATCH 76/83] Make it possible to use facts from hosts in templates for other hosts. It works like this: {{ hostvars['127.0.0.1']['ansible_eth0']['ipv4']['address'] }} --- hacking/test-module | 6 +++--- lib/ansible/playbook.py | 10 +++++----- lib/ansible/runner.py | 12 ++++++------ lib/ansible/utils.py | 9 +++++---- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/hacking/test-module b/hacking/test-module index 971b56a11c5..ce31b3bffcd 100755 --- a/hacking/test-module +++ b/hacking/test-module @@ -30,7 +30,7 @@ import sys import os import subprocess import traceback -import ansible.utils +from ansible import utils try: import json @@ -70,7 +70,7 @@ try: print "***********************************" print "RAW OUTPUT" print out - results = ansible.utils.parse_json(out) + results = utils.parse_json(out) except: print "***********************************" @@ -82,7 +82,7 @@ except: print "***********************************" print "PARSED OUTPUT" -print results +print utils.bigjson(results) sys.exit(0) diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py index 1b8e557791c..ed9452ecd68 100755 --- a/lib/ansible/playbook.py +++ b/lib/ansible/playbook.py @@ -207,10 +207,10 @@ class PlayBook(object): if action is None: raise errors.AnsibleError('action is required') produced_task = task.copy() - produced_task['action'] = utils.template(action, dict(item=item)) - produced_task['name'] = utils.template(name, dict(item=item)) + produced_task['action'] = utils.template(action, dict(item=item), SETUP_CACHE) + produced_task['name'] = utils.template(name, dict(item=item), SETUP_CACHE) if only_if: - produced_task['only_if'] = utils.template(only_if, dict(item=item)) + produced_task['only_if'] = utils.template(only_if, dict(item=item), SETUP_CACHE) new_tasks2.append(produced_task) else: new_tasks2.append(task) @@ -465,7 +465,7 @@ class PlayBook(object): found = False sequence = [] for real_filename in filename: - filename2 = utils.path_dwim(self.basedir, utils.template(real_filename, cache_vars)) + filename2 = utils.path_dwim(self.basedir, utils.template(real_filename, cache_vars, SETUP_CACHE)) sequence.append(filename2) if os.path.exists(filename2): found = True @@ -481,7 +481,7 @@ class PlayBook(object): ) else: - filename2 = utils.path_dwim(self.basedir, utils.template(filename, cache_vars)) + filename2 = utils.path_dwim(self.basedir, utils.template(filename, cache_vars, SETUP_CACHE)) if not os.path.exists(filename2): raise errors.AnsibleError("no file matched for vars_file import: %s" % filename2) data = utils.parse_yaml_from_file(filename2) diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index c305126b342..c7ef3663bfa 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -268,7 +268,7 @@ class Runner(object): ''' runs a module that has already been transferred ''' inject = self.setup_cache.get(conn.host,{}) - conditional = utils.double_template(self.conditional, inject) + conditional = utils.double_template(self.conditional, inject, self.setup_cache) if not eval(conditional): return [ utils.smjson(dict(skipped=True)), None, 'skipped' ] @@ -281,7 +281,7 @@ class Runner(object): if type(args) == dict: args = utils.bigjson(args) - args = utils.template(args, inject) + args = utils.template(args, inject, self.setup_cache) module_name_tail = remote_module_path.split("/")[-1] @@ -369,7 +369,7 @@ class Runner(object): # apply templating to source argument inject = self.setup_cache.get(conn.host,{}) - source = utils.template(source, inject) + source = utils.template(source, inject, self.setup_cache) # transfer the file to a remote tmp location tmp_src = tmp + source.split('/')[-1] @@ -456,7 +456,7 @@ class Runner(object): # apply templating to source argument so vars can be used in the path inject = self.setup_cache.get(conn.host,{}) - source = utils.template(source, inject) + source = utils.template(source, inject, self.setup_cache) (host, ok, data, err) = (None, None, None, None) @@ -491,7 +491,7 @@ class Runner(object): source_data = file(utils.path_dwim(self.basedir, source)).read() resultant = '' try: - resultant = utils.template(source_data, inject) + resultant = utils.template(source_data, inject, self.setup_cache) except Exception, e: return (host, False, dict(failed=True, msg=str(e)), '') xfered = self._transfer_str(conn, tmp, 'source', resultant) @@ -537,7 +537,7 @@ class Runner(object): return [ host, False, "FAILED: %s" % str(e), None ] cache = self.setup_cache.get(host, {}) - module_name = utils.template(self.module_name, cache) + module_name = utils.template(self.module_name, cache, self.setup_cache) tmp = self._get_tmp_path(conn) result = None diff --git a/lib/ansible/utils.py b/lib/ansible/utils.py index d1bb9490e68..ac4228f2c1b 100755 --- a/lib/ansible/utils.py +++ b/lib/ansible/utils.py @@ -33,7 +33,6 @@ except ImportError: from ansible import errors import ansible.constants as C - ############################################################### # UTILITY FUNCTIONS FOR COMMAND LINE TOOLS ############################################################### @@ -239,14 +238,16 @@ def varReplace(raw, vars): return ''.join(done) -def template(text, vars): +def template(text, vars, setup_cache): ''' run a text buffer through the templating engine ''' + vars = vars.copy() text = varReplace(str(text), vars) + vars['hostvars'] = setup_cache template = jinja2.Template(text) return template.render(vars) -def double_template(text, vars): - return template(template(text, vars), vars) +def double_template(text, vars, setup_cache): + return template(template(text, vars, setup_cache), vars, setup_cache) def template_from_file(path, vars): ''' run a file through the templating engine ''' From ec4d5fa2879880743b029c103267a4288069a670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20N=C3=A9ri?= Date: Sun, 22 Apr 2012 02:48:58 +0200 Subject: [PATCH 77/83] Add apt module support for installing/removing specific version of package This uses standard APT syntax, e.g.: ansible webservers -m apt -a "pkg=nginx=1.1.19-1 state=installed" --- library/apt | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/library/apt b/library/apt index 81b40ea2a08..51191d4fd97 100755 --- a/library/apt +++ b/library/apt @@ -63,16 +63,27 @@ def run_apt(command): rc = cmd.returncode return rc, out, err -def package_status(pkgspec, cache): +def package_split(pkgspec): + parts = pkgspec.split('=') + if len(parts) > 1: + return parts[0], parts[1] + else: + return parts[0], None + +def package_status(pkgname, version, cache): try: - pkg = cache[pkgspec] - except: - fail_json(msg="No package matching '%s' is available" % pkgspec) - return (pkg.is_installed, pkg.is_upgradable) + pkg = cache[pkgname] + except KeyError: + fail_json(msg="No package matching '%s' is available" % pkgname) + if version: + return pkg.is_installed and pkg.installed.version == version, False + else: + return pkg.is_installed, pkg.is_upgradable def install(pkgspec, cache, upgrade=False): - (installed, upgradable) = package_status(pkgspec, cache) - if (not installed) or (upgrade and upgradable): + name, version = package_split(pkgspec) + installed, upgradable = package_status(name, version, cache) + if not installed or (upgrade and upgradable): cmd = "%s -q -y install '%s'" % (APT, pkgspec) rc, out, err = run_apt(cmd) if rc: @@ -82,15 +93,16 @@ def install(pkgspec, cache, upgrade=False): return False def remove(pkgspec, cache, purge=False): - (installed, upgradable) = package_status(pkgspec, cache) + name, version = package_split(pkgspec) + installed, upgradable = package_status(name, version, cache) if not installed: return False else: purge = '--purge' if purge else '' - cmd = "%s -q -y %s remove '%s'" % (APT, purge, pkgspec) + cmd = "%s -q -y %s remove '%s'" % (APT, purge, name) rc, out, err = run_apt(cmd) if rc: - fail_json(msg="'apt-get remove %s' failed: %s" % (pkgspec, err)) + fail_json(msg="'apt-get remove %s' failed: %s" % (name, err)) return True @@ -109,7 +121,7 @@ if not len(items): params = {} for x in items: - (k, v) = x.split("=") + (k, v) = x.split("=", 1) params[k] = v state = params.get('state','installed') @@ -137,7 +149,12 @@ if update_cache == 'yes': if package == None: exit_json(changed=False) +if package.count('=') > 1: + fail_json(msg='invalid package spec') + if state == 'latest': + if '=' in package: + fail_json(msg='version number inconsistent with state=latest') changed = install(package, cache, upgrade=True) elif state == 'installed': changed = install(package, cache) From be5899527891e889a2daa51c1b780b8d55f8ef33 Mon Sep 17 00:00:00 2001 From: Stephen Fromm Date: Sat, 21 Apr 2012 23:27:34 -0700 Subject: [PATCH 78/83] Add context=default option to file module This adjusts behavior of file module such that removal of se* option does not revert the file's selinux context to the default. In order to go back to the default context according to the policy, you can use the context=default option. --- library/file | 45 ++++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/library/file b/library/file index 90d6892601d..e0ebbecb171 100755 --- a/library/file +++ b/library/file @@ -72,6 +72,21 @@ def add_path_info(kwargs): kwargs['state'] = 'absent' return kwargs +# If selinux fails to find a default, return an array of None +def selinux_default_context(path, mode=0): + context = [None, None, None, None] + if not HAVE_SELINUX: + return context + try: + ret = selinux.matchpathcon(path, mode) + except OSError: + return context + if ret[0] == -1: + return context + context = ret[1].split(':') + debug("got default secontext=%s" % ret[1]) + return context + # =========================================== argfile = sys.argv[1] @@ -107,8 +122,16 @@ seuser = params.get('seuser', None) serole = params.get('serole', None) setype = params.get('setype', None) selevel = params.get('serange', 's0') +context = params.get('context', None) secontext = [seuser, serole, setype, selevel] +if context is not None: + if context != 'default': + fail_json(msg='invalid context: %s' % context) + if seuser is not None or serole is not None or setype is not None: + fail_json(msg='cannot define context=default and seuser, serole or setype') + secontext = selinux_default_context(path) + if state not in [ 'file', 'directory', 'link', 'absent']: fail_json(msg='invalid state: %s' % state) @@ -148,34 +171,14 @@ def selinux_context(path): debug("got current secontext=%s" % ret[1]) return context -# If selinux fails to find a default, return an array of None -def selinux_default_context(path, mode=0): - context = [None, None, None, None] - print >>sys.stderr, path - if not HAVE_SELINUX: - return context - try: - ret = selinux.matchpathcon(path, mode) - except OSError: - return context - if ret[0] == -1: - return context - context = ret[1].split(':') - debug("got default secontext=%s" % ret[1]) - return context - def set_context_if_different(path, context, changed): if not HAVE_SELINUX: return changed cur_context = selinux_context(path) - new_context = selinux_default_context(path) + new_context = list(cur_context) for i in range(len(context)): if context[i] is not None and context[i] != cur_context[i]: - debug('new context was %s' % new_context[i]) new_context[i] = context[i] - debug('new context is %s' % new_context[i]) - elif new_context[i] is None: - new_context[i] = cur_context[i] debug("current secontext is %s" % ':'.join(cur_context)) debug("new secontext is %s" % ':'.join(new_context)) if cur_context != new_context: From 0f044e64f85dbfe18410b881eb8c30de9de08a39 Mon Sep 17 00:00:00 2001 From: Stephen Fromm Date: Sat, 21 Apr 2012 23:30:08 -0700 Subject: [PATCH 79/83] Add example playbook of file module's selinux capabilities --- examples/playbooks/file_secontext.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 examples/playbooks/file_secontext.yml diff --git a/examples/playbooks/file_secontext.yml b/examples/playbooks/file_secontext.yml new file mode 100644 index 00000000000..75ab6ad49ad --- /dev/null +++ b/examples/playbooks/file_secontext.yml @@ -0,0 +1,12 @@ +--- +# This is a demo of how to manage the selinux context using the file module +- hosts: test + user: root + tasks: + - name: Change setype of /etc/exports to non-default value + action: file path=/etc/exports setype=etc_t + - name: Change seuser of /etc/exports to non-default value + action: file path=/etc/exports seuser=unconfined_u + - name: Set selinux context back to default value + action: file path=/etc/exports context=default + From bcfa6a7865a4461b5240f1bcb57504de4589811d Mon Sep 17 00:00:00 2001 From: Stephen Fromm Date: Sun, 22 Apr 2012 00:14:40 -0700 Subject: [PATCH 80/83] Add another example to file_secontext.yml Demonstrate what happens when there is no default context in the policy. --- examples/playbooks/file_secontext.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/playbooks/file_secontext.yml b/examples/playbooks/file_secontext.yml index 75ab6ad49ad..117a930dc0a 100644 --- a/examples/playbooks/file_secontext.yml +++ b/examples/playbooks/file_secontext.yml @@ -9,4 +9,10 @@ action: file path=/etc/exports seuser=unconfined_u - name: Set selinux context back to default value action: file path=/etc/exports context=default - + - name: Create empty file + action: command /bin/touch /tmp/foo + - name: Change setype of /tmp/foo + action: file path=/tmp/foo setype=default_t + - name: Try to set secontext to default, but this will fail + because of the lack of a default in the policy + action: file path=/tmp/foo context=default From d5f3760accc009a3c5e7059bb1dfe5645bc2e5b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20N=C3=A9ri?= Date: Mon, 23 Apr 2012 00:17:07 +0200 Subject: [PATCH 81/83] Add optional "default-release" argument for apt module The value is passed to apt-get's "-t" option. Useful for installing backports, e.g.: ansible webservers -m apt -a "pkg=nginx state=latest default-release=squeeze-backports" --- library/apt | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/library/apt b/library/apt index 51191d4fd97..e3ebf0c6733 100755 --- a/library/apt +++ b/library/apt @@ -42,7 +42,7 @@ def fail_json(**kwargs): exit_json(rc=1, **kwargs) try: - import apt + import apt, apt_pkg except ImportError: fail_json(msg="could not import apt, please install the python-apt package on this host") @@ -80,11 +80,13 @@ def package_status(pkgname, version, cache): else: return pkg.is_installed, pkg.is_upgradable -def install(pkgspec, cache, upgrade=False): +def install(pkgspec, cache, upgrade=False, default_release=None): name, version = package_split(pkgspec) installed, upgradable = package_status(name, version, cache) if not installed or (upgrade and upgradable): cmd = "%s -q -y install '%s'" % (APT, pkgspec) + if default_release: + cmd += " -t '%s'" % (default_release,) rc, out, err = run_apt(cmd) if rc: fail_json(msg="'apt-get install %s' failed: %s" % (pkgspec, err)) @@ -124,10 +126,11 @@ for x in items: (k, v) = x.split("=", 1) params[k] = v -state = params.get('state','installed') -package = params.get('pkg', params.get('package', params.get('name', None))) -update_cache = params.get('update-cache', 'no') -purge = params.get('purge', 'no') +state = params.get('state', 'installed') +package = params.get('pkg', params.get('package', params.get('name', None))) +update_cache = params.get('update-cache', 'no') +purge = params.get('purge', 'no') +default_release = params.get('default-release', None) if state not in ['installed', 'latest', 'removed']: fail_json(msg='invalid state') @@ -142,6 +145,10 @@ if package is None and update_cache != 'yes': fail_json(msg='pkg=name and/or update-cache=yes is required') cache = apt.Cache() +if default_release: + apt_pkg.config['APT::Default-Release'] = default_release + # reopen cache w/ modified config + cache.open() if update_cache == 'yes': cache.update() @@ -155,9 +162,10 @@ if package.count('=') > 1: if state == 'latest': if '=' in package: fail_json(msg='version number inconsistent with state=latest') - changed = install(package, cache, upgrade=True) + changed = install(package, cache, upgrade=True, + default_release=default_release) elif state == 'installed': - changed = install(package, cache) + changed = install(package, cache, default_release=default_release) elif state == 'removed': changed = remove(package, cache, purge == 'yes') From 21269a845f0ee04fcf1e7b7aea8eabe4b3c97a70 Mon Sep 17 00:00:00 2001 From: Henry Graham Date: Mon, 23 Apr 2012 19:17:01 -0400 Subject: [PATCH 82/83] debian packaging --- Makefile | 13 ++++++++++--- packaging/debian/rules | 0 2 files changed, 10 insertions(+), 3 deletions(-) mode change 100644 => 100755 packaging/debian/rules diff --git a/Makefile b/Makefile index 7bb7b2ab39e..b685863def2 100644 --- a/Makefile +++ b/Makefile @@ -84,6 +84,9 @@ clean: rm -rf test/test_data @echo "Cleaning up RPM building stuff" rm -rf MANIFEST rpm-build + @echo "Cleaning up Debian building stuff" + rm -rf debian + rm -rf deb-build python: python setup.py build @@ -126,9 +129,13 @@ rpm: rpmcommon @echo " rpm-build/noarch/$(RPMNVR).noarch.rpm" @echo "#############################################" -debian: - # stub target, FIXME! - (cd packaging/debian; dpkg-buildpackage -us -uc -rfakeroot) +debian: sdist +deb: debian + cp -r packaging/debian ./ + chmod 755 debian/rules + fakeroot debian/rules clean + fakeroot dh_install + fakeroot debian/rules binary # for arch or gentoo, read instructions in the appropriate 'packaging' subdirectory directory diff --git a/packaging/debian/rules b/packaging/debian/rules old mode 100644 new mode 100755 From 321ed53e3ac0bdf0ae2e509ed3311c7e04d6354f Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 23 Apr 2012 21:00:28 -0400 Subject: [PATCH 83/83] Fetch module doesn't set invocation parameter as it invokes nothing, so don't let that be an error. --- lib/ansible/callbacks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ansible/callbacks.py b/lib/ansible/callbacks.py index 0b36a62ab33..a3f46fc9d4a 100755 --- a/lib/ansible/callbacks.py +++ b/lib/ansible/callbacks.py @@ -151,7 +151,7 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks): print "failed: [%s] => %s => %s\n" % (host, invocation, utils.smjson(results)) def on_ok(self, host, host_result): - invocation = host_result.get('invocation',None) + invocation = host_result.get('invocation','') if invocation.startswith('async_status'): pass elif not invocation or invocation.startswith('setup '):