Factoids and push variables via setup are now available to be templated in command args

as well as template files.  PLUS, variables are now expressed in playbooks without having
to know about the setup task, which means playbooks are simpler to read now.
This commit is contained in:
Michael DeHaan 2012-03-05 20:09:03 -05:00
parent e0b1ad790c
commit 8d57ceecf1
7 changed files with 81 additions and 19 deletions

View file

@ -41,6 +41,7 @@ Requirements are extremely minimal.
If you are running python 2.6 on the 'overlord' machine, you will need: If you are running python 2.6 on the 'overlord' machine, you will need:
* paramiko * paramiko
* python-jinja2
* PyYAML (if using playbooks) * PyYAML (if using playbooks)
If you are running less than Python 2.6, you will also need 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 - hosts: all
vars:
http_port: 80
max_clients: 200
user: root user: root
tasks: tasks:
- include: base.yml - include: base.yml

View file

@ -13,6 +13,7 @@ BuildArch: noarch
Url: http://github.com/mpdehaan/ansible/ Url: http://github.com/mpdehaan/ansible/
BuildRequires: asciidoc BuildRequires: asciidoc
Requires: python-paramiko Requires: python-paramiko
Requires: python-jinja2
%description %description
Ansible is a extra-simple tool/API for doing 'parallel remote things' over SSH Ansible is a extra-simple tool/API for doing 'parallel remote things' over SSH

View file

@ -1,15 +1,18 @@
--- ---
- hosts: all - hosts: all
user: root user: root
vars:
http_port: 80
max_clients: 200
tasks: tasks:
- include: base.yml - include: base.yml
- name: configure template & module variables for future template calls - name: write the apache config file using vars set above
action: setup http_port=80 max_clients=200
- name: write the apache config file
action: template src=/srv/httpd.j2 dest=/etc/httpd.conf action: template src=/srv/httpd.j2 dest=/etc/httpd.conf
notify: notify:
- restart apache - restart apache
- name: ensure apache is running - name: ensure apache is running
action: service name=httpd state=started action: service name=httpd state=started
- name: pointless test action
action: command /bin/echo {{ http_port }}
handlers: handlers:
- include: handlers.yml - include: handlers.yml

View file

@ -1,8 +1,10 @@
--- ---
- hosts: '*' - hosts: '*'
vars:
a: 2
b: 3
c: 4
tasks: tasks:
- name: config step
action: setup a=2 b=3 c=4
- name: copy comand - name: copy comand
action: copy src=/srv/a dest=/srv/b action: copy src=/srv/a dest=/srv/b
notify: notify:

View file

@ -24,6 +24,8 @@ import yaml
import shlex import shlex
import os import os
SETUP_CACHE={ 'foo' : {} }
############################################# #############################################
class PlayBook(object): class PlayBook(object):
@ -150,7 +152,8 @@ class PlayBook(object):
remote_pass=self.remote_pass, remote_pass=self.remote_pass,
module_path=self.module_path, module_path=self.module_path,
timeout=self.timeout, timeout=self.timeout,
remote_user=remote_user remote_user=remote_user,
setup_cache=SETUP_CACHE
).run() ).run()
def _run_task(self, pattern=None, task=None, host_list=None, def _run_task(self, pattern=None, task=None, host_list=None,
@ -274,6 +277,7 @@ class PlayBook(object):
# get configuration information about the pattern # get configuration information about the pattern
pattern = pg['hosts'] pattern = pg['hosts']
vars = pg.get('vars', {})
tasks = pg['tasks'] tasks = pg['tasks']
handlers = pg['handlers'] handlers = pg['handlers']
user = pg.get('user', C.DEFAULT_REMOTE_USER) user = pg.get('user', C.DEFAULT_REMOTE_USER)
@ -283,8 +287,35 @@ class PlayBook(object):
if self.verbose: if self.verbose:
print "PLAY [%s] ****************************\n" % pattern 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: for task in tasks:
self._run_task( self._run_task(
pattern=pattern, pattern=pattern,

View file

@ -29,8 +29,9 @@ import signal
import os import os
import ansible.constants as C import ansible.constants as C
import Queue import Queue
import paramiko
import random import random
import paramiko
import jinja2
################################################ ################################################
@ -62,6 +63,7 @@ class Runner(object):
remote_user=C.DEFAULT_REMOTE_USER, remote_user=C.DEFAULT_REMOTE_USER,
remote_pass=C.DEFAULT_REMOTE_PASS, remote_pass=C.DEFAULT_REMOTE_PASS,
background=0, background=0,
setup_cache={},
verbose=False): verbose=False):
''' '''
@ -75,9 +77,10 @@ class Runner(object):
remote_user -- who to login as (default root) remote_user -- who to login as (default root)
remote_pass -- provide only if you don't want to use keys or ssh-agent 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 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.host_list, self.groups = self.parse_hosts(host_list)
self.module_path = module_path self.module_path = module_path
@ -91,6 +94,7 @@ class Runner(object):
self.remote_pass = remote_pass self.remote_pass = remote_pass
self.background = background self.background = background
# hosts in each group name in the inventory file # hosts in each group name in the inventory file
self._tmp_paths = {} self._tmp_paths = {}
@ -201,10 +205,17 @@ class Runner(object):
def _execute_module(self, conn, tmp, remote_module_path, module_args): 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 = module_args
args = " ".join(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) cmd = "%s %s" % (remote_module_path, args)
result = self._exec_command(conn, cmd) result = self._exec_command(conn, cmd)
self._delete_remote_files(conn, [ tmp ]) self._delete_remote_files(conn, [ tmp ])
@ -217,6 +228,16 @@ class Runner(object):
''' '''
module = self._transfer_module(conn, tmp, self.module_name) module = self._transfer_module(conn, tmp, self.module_name)
result = self._execute_module(conn, tmp, module, self.module_args) 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) self._delete_remote_files(conn, tmp)
return self._return_from_module(conn, host, result) return self._return_from_module(conn, host, result)
@ -303,6 +324,7 @@ class Runner(object):
# module, call the appropriate executor function # module, call the appropriate executor function
ok, conn = self._connect(host) ok, conn = self._connect(host)
conn._host = host
if not ok: if not ok:
return [ host, False, conn ] return [ host, False, conn ]

View file

@ -96,10 +96,9 @@ md5sum2 = os.popen("md5sum %s" % ansible_file).read().split()[0]
if md5sum != md5sum2: if md5sum != md5sum2:
changed = True changed = True
result = { new_options['written'] = ansible_file
"written" : ansible_file, new_options['changed'] = changed
"changed" : changed, new_options['md5sum'] = md5sum2
"md5sum" : md5sum2
} print json.dumps(new_options)
print json.dumps(result)