Allow variables coming in from the playbook and the API to be expressed as dictionaries throughout their full life cycle

such that nested data can be made available in templates and playbooks.
This commit is contained in:
Michael DeHaan 2012-04-10 20:58:40 -04:00
parent 5a4d4bc051
commit 1d75a29ec9
4 changed files with 75 additions and 36 deletions

View file

@ -436,24 +436,17 @@ class PlayBook(object):
else: else:
self.callbacks.on_setup_primary() self.callbacks.on_setup_primary()
# 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.iteritems():
push_var_str += "%s=\"%s\" " % (k,v)
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.host_list if not (h in self.stats.failures or h in self.stats.dark) ]
# push any variables down to the system # push any variables down to the system
setup_results = ansible.runner.Runner( setup_results = ansible.runner.Runner(
pattern=pattern, groups=self.groups, module_name='setup', pattern=pattern, groups=self.groups, module_name='setup',
module_args=push_var_str, host_list=host_list, module_args=vars, host_list=host_list,
forks=self.forks, module_path=self.module_path, forks=self.forks, module_path=self.module_path,
timeout=self.timeout, remote_user=user, timeout=self.timeout, remote_user=user,
remote_pass=self.remote_pass, remote_port=self.remote_port, remote_pass=self.remote_pass, remote_port=self.remote_port,
setup_cache=SETUP_CACHE, setup_cache=SETUP_CACHE,
callbacks=self.runner_callbacks, sudo=sudo, callbacks=self.runner_callbacks, sudo=sudo, debug=self.debug,
).run() ).run()
self.stats.compute(setup_results, setup=True) self.stats.compute(setup_results, setup=True)

View file

@ -113,8 +113,8 @@ class Runner(object):
self.basedir = basedir self.basedir = basedir
self.sudo = sudo self.sudo = sudo
if type(self.module_args) != str: if type(self.module_args) != str and type(self.module_args) != dict:
raise Exception("module_args must be a string: %s" % self.module_args) raise Exception("module_args must be a string or dict: %s" % self.module_args)
self._tmp_paths = {} self._tmp_paths = {}
random.seed() random.seed()
@ -271,6 +271,9 @@ class Runner(object):
def _transfer_str(self, conn, tmp, name, args_str): def _transfer_str(self, conn, tmp, name, args_str):
''' transfer arguments as a single file to be fed to the module. ''' ''' transfer arguments as a single file to be fed to the module. '''
if type(args_str) == dict:
args_str = utils.smjson(args_str)
args_fd, args_file = tempfile.mkstemp() args_fd, args_file = tempfile.mkstemp()
args_fo = os.fdopen(args_fd, 'w') args_fo = os.fdopen(args_fd, 'w')
args_fo.write(args_str) args_fo.write(args_str)
@ -316,23 +319,43 @@ class Runner(object):
def _add_setup_vars(self, inject, args): def _add_setup_vars(self, inject, args):
''' setup module variables need special handling ''' ''' setup module variables need special handling '''
is_dict = False
if type(args) == dict:
is_dict = True
# TODO: keep this as a dict through the whole path to simplify this code
for (k,v) in inject.iteritems(): 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_'):
if str(v).find(" ") != -1: if not is_dict:
v = "\"%s\"" % v if str(v).find(" ") != -1:
args += " %s=%s" % (k, str(v).replace(" ","~~~")) v = "\"%s\"" % v
args += " %s=%s" % (k, str(v).replace(" ","~~~"))
else:
args[k]=v
return args return args
# ***************************************************** # *****************************************************
def _add_setup_metadata(self, args): def _add_setup_metadata(self, args):
''' automatically determine where to store variables for the setup module ''' ''' automatically determine where to store variables for the setup module '''
is_dict = False
if type(args) == dict:
is_dict = True
if args.find("metadata=") == -1: # TODO: keep this as a dict through the whole path to simplify this code
if self.remote_user == 'root': if not is_dict:
args = "%s metadata=/etc/ansible/setup" % args if args.find("metadata=") == -1:
else: if self.remote_user == 'root':
args = "%s metadata=/home/%s/.ansible/setup" % (args, self.remote_user) args = "%s metadata=/etc/ansible/setup" % args
else:
args = "%s metadata=/home/%s/.ansible/setup" % (args, self.remote_user)
else:
if not 'metadata' in args:
if self.remote_user == 'root':
args['metadata'] = '/etc/ansible/setup'
else:
args['metadata'] = "/home/%s/.ansible/setup" % (self.remote_user)
return args return args
# ***************************************************** # *****************************************************
@ -352,9 +375,11 @@ class Runner(object):
args = self._add_setup_vars(inject, args) args = self._add_setup_vars(inject, args)
args = self._add_setup_metadata(args) args = self._add_setup_metadata(args)
if type(args) == dict:
args = utils.bigjson(args)
args = utils.template(args, inject) args = utils.template(args, inject)
module_name_tail = remote_module_path.split("/")[-1] module_name_tail = remote_module_path.split("/")[-1]
client_executed_str = "%s %s" % (module_name_tail, args.strip())
argsfile = self._transfer_str(conn, tmp, 'arguments', args) argsfile = self._transfer_str(conn, tmp, 'arguments', args)
if async_jid is None: if async_jid is None:
@ -368,6 +393,7 @@ class Runner(object):
res, err = self._exec_command(conn, cmd, tmp, sudoable=True) res, err = self._exec_command(conn, cmd, tmp, sudoable=True)
client_executed_str = "%s %s" % (module_name_tail, args.strip())
return ( res, err, client_executed_str ) return ( res, err, client_executed_str )
# ***************************************************** # *****************************************************

View file

@ -23,6 +23,7 @@ import sys
import os import os
import shlex import shlex
import subprocess import subprocess
import traceback
try: try:
import json import json
@ -34,18 +35,22 @@ except ImportError:
if len(sys.argv) == 1: if len(sys.argv) == 1:
sys.exit(1) sys.exit(1)
argfile = sys.argv[1] argfile = sys.argv[1]
if not os.path.exists(argfile): if not os.path.exists(argfile):
sys.exit(1) sys.exit(1)
input_data = shlex.split(open(argfile, 'r').read()) setup_options = open(argfile).read().strip()
try:
setup_options = json.loads(setup_options)
except:
list_options = shlex.split(setup_options)
setup_options = {}
for opt in list_options:
(k,v) = opt.split("=")
setup_options[k]=v
# turn urlencoded k=v string (space delimited) to regular k=v directionary ansible_file = setup_options.get('metadata', DEFAULT_ANSIBLE_SETUP)
splitted = [x.split('=',1) for x in input_data ]
splitted = [ (x[0], x[1].replace("~~~"," ")) for x in splitted ]
new_options = dict(splitted)
ansible_file = new_options.get('metadata', DEFAULT_ANSIBLE_SETUP)
ansible_dir = os.path.dirname(ansible_file) ansible_dir = os.path.dirname(ansible_file)
# create the config dir if it doesn't exist # create the config dir if it doesn't exist
@ -74,7 +79,7 @@ if os.path.exists("/usr/bin/facter"):
facter = False facter = False
if facter: if facter:
for (k,v) in facter_ds.items(): for (k,v) in facter_ds.items():
new_options["facter_%s" % k] = v setup_options["facter_%s" % k] = v
# ditto for ohai, but just top level string keys # ditto for ohai, but just top level string keys
# because it contains a lot of nested stuff we can't use for # because it contains a lot of nested stuff we can't use for
@ -93,13 +98,13 @@ if os.path.exists("/usr/bin/ohai"):
for (k,v) in ohai_ds.items(): for (k,v) in ohai_ds.items():
if type(v) == str or type(v) == unicode: if type(v) == str or type(v) == unicode:
k2 = "ohai_%s" % k k2 = "ohai_%s" % k
new_options[k2] = v setup_options[k2] = v
# write the template/settings file using # write the template/settings file using
# instructions from server # instructions from server
f = open(ansible_file, "w+") f = open(ansible_file, "w+")
reformat = json.dumps(new_options, sort_keys=True, indent=4) reformat = json.dumps(setup_options, sort_keys=True, indent=4)
f.write(reformat) f.write(reformat)
f.close() f.close()
@ -108,9 +113,9 @@ md5sum2 = os.popen("md5sum %s" % ansible_file).read().split()[0]
if md5sum != md5sum2: if md5sum != md5sum2:
changed = True changed = True
new_options['written'] = ansible_file setup_options['written'] = ansible_file
new_options['changed'] = changed setup_options['changed'] = changed
new_options['md5sum'] = md5sum2 setup_options['md5sum'] = md5sum2
print json.dumps(new_options) print json.dumps(setup_options)

View file

@ -15,7 +15,7 @@
"answer": "Wuh, I think so, Brain, but if we didn't have ears, we'd look like weasels.", "answer": "Wuh, I think so, Brain, but if we didn't have ears, we'd look like weasels.",
"changed": true, "changed": true,
"metadata": "/etc/ansible/setup", "metadata": "/etc/ansible/setup",
"port": "5150", "port": 5150,
"written": "/etc/ansible/setup" "written": "/etc/ansible/setup"
} }
] ]
@ -44,7 +44,7 @@
"cow": "moo", "cow": "moo",
"duck": "quack", "duck": "quack",
"metadata": "/etc/ansible/setup", "metadata": "/etc/ansible/setup",
"port": "5150", "port": 5150,
"testing": "default", "testing": "default",
"written": "/etc/ansible/setup" "written": "/etc/ansible/setup"
} }
@ -228,6 +228,21 @@
"127.0.0.2" "127.0.0.2"
] ]
], ],
[
"ok",
[
"127.0.0.2",
{
"started": 1
}
]
],
[
"async poll",
[
"127.0.0.2"
]
],
[ [
"ok", "ok",
[ [