diff --git a/README.md b/README.md index a6410300abc..db3bfa29900 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Requirements are extremely minimal. If you are running python 2.6 on the 'overlord' machine, you will need: * paramiko + * python-jinja2 * PyYAML (if using playbooks) If you are running less than Python 2.6, you will also need @@ -161,6 +162,9 @@ An example showing a small playbook: --- - hosts: all + vars: + http_port: 80 + max_clients: 200 user: root tasks: - include: base.yml diff --git a/ansible.spec b/ansible.spec index c9aa6516209..b8d36012c5b 100644 --- a/ansible.spec +++ b/ansible.spec @@ -13,6 +13,7 @@ BuildArch: noarch Url: http://github.com/mpdehaan/ansible/ BuildRequires: asciidoc Requires: python-paramiko +Requires: python-jinja2 %description Ansible is a extra-simple tool/API for doing 'parallel remote things' over SSH diff --git a/examples/playbook.yml b/examples/playbook.yml index 4cda6dde3d6..7890c35b06e 100644 --- a/examples/playbook.yml +++ b/examples/playbook.yml @@ -1,15 +1,18 @@ --- - hosts: all user: root + vars: + http_port: 80 + max_clients: 200 tasks: - include: base.yml - - name: configure template & module variables for future template calls - action: setup http_port=80 max_clients=200 - - name: write the apache config file + - name: write the apache config file using vars set above action: template src=/srv/httpd.j2 dest=/etc/httpd.conf notify: - restart apache - name: ensure apache is running action: service name=httpd state=started + - name: pointless test action + action: command /bin/echo {{ http_port }} handlers: - include: handlers.yml diff --git a/examples/playbook2.yml b/examples/playbook2.yml index 342a9e79cad..494d1089a13 100644 --- a/examples/playbook2.yml +++ b/examples/playbook2.yml @@ -1,8 +1,10 @@ --- - hosts: '*' + vars: + a: 2 + b: 3 + c: 4 tasks: - - name: config step - action: setup a=2 b=3 c=4 - name: copy comand action: copy src=/srv/a dest=/srv/b notify: diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py index e89ee67cf16..522078abeee 100755 --- a/lib/ansible/playbook.py +++ b/lib/ansible/playbook.py @@ -24,6 +24,8 @@ import yaml import shlex import os +SETUP_CACHE={ 'foo' : {} } + ############################################# class PlayBook(object): @@ -150,7 +152,8 @@ class PlayBook(object): remote_pass=self.remote_pass, module_path=self.module_path, timeout=self.timeout, - remote_user=remote_user + remote_user=remote_user, + setup_cache=SETUP_CACHE ).run() def _run_task(self, pattern=None, task=None, host_list=None, @@ -274,6 +277,7 @@ class PlayBook(object): # get configuration information about the pattern pattern = pg['hosts'] + vars = pg.get('vars', {}) tasks = pg['tasks'] handlers = pg['handlers'] user = pg.get('user', C.DEFAULT_REMOTE_USER) @@ -283,8 +287,35 @@ class PlayBook(object): if self.verbose: print "PLAY [%s] ****************************\n" % pattern - # run all the top level tasks, these get run on every node + # first run the setup task on every node, which gets the variables + # written to the JSON file and will also bubble facts back up via + # magic in Runner() + push_var_str='' + for (k,v) in vars.items(): + push_var_str += "%s=%s " % (k,v) + if user != 'root': + push_var_str = "%s metadata=~/.ansible_setup" % (push_var_str) + # push any variables down to the system + setup_results = ansible.runner.Runner( + pattern=pattern, + module_name='setup', + module_args=push_var_str, + host_list=self.host_list, + forks=self.forks, + module_path=self.module_path, + timeout=self.timeout, + remote_user=user, + setup_cache=SETUP_CACHE + ).run() + + # now for each result, load into the setup cache so we can + # let runner template out future commands + setup_ok = setup_results.get('contacted', {}) + for (host, result) in setup_ok.items(): + SETUP_CACHE[host] = result + + # run all the top level tasks, these get run on every node for task in tasks: self._run_task( pattern=pattern, diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index 59b0491b34e..a0370e8a393 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -29,9 +29,10 @@ import signal import os import ansible.constants as C import Queue -import paramiko import random - +import paramiko +import jinja2 + ################################################ def noop(*args, **kwargs): @@ -62,6 +63,7 @@ class Runner(object): remote_user=C.DEFAULT_REMOTE_USER, remote_pass=C.DEFAULT_REMOTE_PASS, background=0, + setup_cache={}, verbose=False): ''' @@ -75,9 +77,10 @@ class Runner(object): remote_user -- who to login as (default root) remote_pass -- provide only if you don't want to use keys or ssh-agent background --- if non 0, run async, failing after X seconds, -1 == infinite + setup_cache -- used only by playbook (complex explanation pending) ''' - # save input values + self.setup_cache = setup_cache self.host_list, self.groups = self.parse_hosts(host_list) self.module_path = module_path @@ -91,6 +94,7 @@ class Runner(object): self.remote_pass = remote_pass self.background = background + # hosts in each group name in the inventory file self._tmp_paths = {} @@ -201,10 +205,17 @@ class Runner(object): def _execute_module(self, conn, tmp, remote_module_path, module_args): ''' - runs a module that has already been transferred + runs a module that has already been transferred, but first + modifies the command using setup_cache variables (see playbook) ''' - args = [ str(x) for x in module_args ] - args = " ".join(args) + args = module_args + if type(args) == list: + args = [ str(x) for x in module_args ] + args = " ".join(args) + inject_vars = self.setup_cache.get(conn._host,{}) + template = jinja2.Template(args) + args = template.render(inject_vars) + cmd = "%s %s" % (remote_module_path, args) result = self._exec_command(conn, cmd) self._delete_remote_files(conn, [ tmp ]) @@ -217,6 +228,16 @@ class Runner(object): ''' module = self._transfer_module(conn, tmp, self.module_name) result = self._execute_module(conn, tmp, module, self.module_args) + # when running the setup module, which pushes vars to the host and ALSO + # returns them (+factoids), store the variables that were returned such that commands + # run AFTER setup use these variables for templating when executed + # from playbooks + if self.module_name == 'setup': + host = conn._host + try: + var_result = json.loads(result) + except: + var_result = {} self._delete_remote_files(conn, tmp) return self._return_from_module(conn, host, result) @@ -303,6 +324,7 @@ class Runner(object): # module, call the appropriate executor function ok, conn = self._connect(host) + conn._host = host if not ok: return [ host, False, conn ] diff --git a/library/setup b/library/setup index c5de2e9f221..30e16032381 100755 --- a/library/setup +++ b/library/setup @@ -96,10 +96,9 @@ md5sum2 = os.popen("md5sum %s" % ansible_file).read().split()[0] if md5sum != md5sum2: changed = True -result = { - "written" : ansible_file, - "changed" : changed, - "md5sum" : md5sum2 -} +new_options['written'] = ansible_file +new_options['changed'] = changed +new_options['md5sum'] = md5sum2 + +print json.dumps(new_options) -print json.dumps(result)