... | ... |
@@ -60,6 +60,7 @@ class PlayState: |
60 | 60 |
(rescue/always) |
61 | 61 |
''' |
62 | 62 |
|
63 |
+ self._parent_iterator = parent_iterator |
|
63 | 64 |
self._run_state = ITERATING_SETUP |
64 | 65 |
self._failed_state = FAILED_NONE |
65 | 66 |
self._task_list = parent_iterator._play.compile() |
... | ... |
@@ -104,6 +105,8 @@ class PlayState: |
104 | 104 |
if self._gather_facts == 'smart' and not self._host.gathered_facts or boolean(self._gather_facts): |
105 | 105 |
self._host.set_gathered_facts(True) |
106 | 106 |
task = Task() |
107 |
+ # FIXME: this is not the best way to get this... |
|
108 |
+ task.set_loader(self._parent_iterator._play._loader) |
|
107 | 109 |
task.action = 'setup' |
108 | 110 |
break |
109 | 111 |
elif run_state == ITERATING_TASKS: |
... | ... |
@@ -97,9 +97,23 @@ class WorkerProcess(multiprocessing.Process): |
97 | 97 |
try: |
98 | 98 |
if not self._main_q.empty(): |
99 | 99 |
debug("there's work to be done!") |
100 |
- (host, task, job_vars, connection_info) = self._main_q.get(block=False) |
|
100 |
+ (host, task, basedir, job_vars, connection_info) = self._main_q.get(block=False) |
|
101 | 101 |
debug("got a task/handler to work on: %s" % task) |
102 | 102 |
|
103 |
+ # because the task queue manager starts workers (forks) before the |
|
104 |
+ # playbook is loaded, set the basedir of the loader inherted by |
|
105 |
+ # this fork now so that we can find files correctly |
|
106 |
+ self._loader.set_basedir(basedir) |
|
107 |
+ |
|
108 |
+ # Serializing/deserializing tasks does not preserve the loader attribute, |
|
109 |
+ # since it is passed to the worker during the forking of the process and |
|
110 |
+ # would be wasteful to serialize. So we set it here on the task now, and |
|
111 |
+ # the task handles updating parent/child objects as needed. |
|
112 |
+ task.set_loader(self._loader) |
|
113 |
+ |
|
114 |
+ # apply the given task's information to the connection info, |
|
115 |
+ # which may override some fields already set by the play or |
|
116 |
+ # the options specified on the command line |
|
103 | 117 |
new_connection_info = connection_info.set_task_override(task) |
104 | 118 |
|
105 | 119 |
# execute the task and build a TaskResult from the result |
... | ... |
@@ -58,7 +58,7 @@ class TaskExecutor: |
58 | 58 |
|
59 | 59 |
try: |
60 | 60 |
items = self._get_loop_items() |
61 |
- if items: |
|
61 |
+ if items is not None: |
|
62 | 62 |
if len(items) > 0: |
63 | 63 |
item_results = self._run_loop(items) |
64 | 64 |
res = dict(results=item_results) |
... | ... |
@@ -84,7 +84,7 @@ class TaskExecutor: |
84 | 84 |
|
85 | 85 |
items = None |
86 | 86 |
if self._task.loop and self._task.loop in lookup_loader: |
87 |
- items = lookup_loader.get(self._task.loop).run(terms=self._task.loop_args, variables=self._job_vars) |
|
87 |
+ items = lookup_loader.get(self._task.loop, loader=self._loader).run(terms=self._task.loop_args, variables=self._job_vars) |
|
88 | 88 |
|
89 | 89 |
return items |
90 | 90 |
|
... | ... |
@@ -204,18 +204,3 @@ class DataLoader(): |
204 | 204 |
self.set_basedir(cur_basedir) |
205 | 205 |
return source2 # which does not exist |
206 | 206 |
|
207 |
- #def __getstate__(self): |
|
208 |
- # data = dict( |
|
209 |
- # basedir = self._basedir, |
|
210 |
- # vault_password = self._vault_password, |
|
211 |
- # FILE_CACHE = self._FILE_CACHE, |
|
212 |
- # ) |
|
213 |
- # return data |
|
214 |
- |
|
215 |
- #def __setstate__(self, data): |
|
216 |
- # self._basedir = data.get('basedir', '.') |
|
217 |
- # self._FILE_CACHE = data.get('FILE_CACHE', dict()) |
|
218 |
- # self._vault_password = data.get('vault_password', '') |
|
219 |
- # |
|
220 |
- # self._vault = VaultLib(password=self._vault_password) |
|
221 |
- |
... | ... |
@@ -178,7 +178,7 @@ class Base: |
178 | 178 |
if self._loader is not None: |
179 | 179 |
basedir = self._loader.get_basedir() |
180 | 180 |
|
181 |
- templar = Templar(basedir=basedir, variables=all_vars, fail_on_undefined=fail_on_undefined) |
|
181 |
+ templar = Templar(loader=self._loader, variables=all_vars, fail_on_undefined=fail_on_undefined) |
|
182 | 182 |
|
183 | 183 |
for (name, attribute) in iteritems(self._get_base_attributes()): |
184 | 184 |
|
... | ... |
@@ -162,3 +162,10 @@ class Block(Base, Conditional, Taggable): |
162 | 162 |
return False |
163 | 163 |
return super(Block, self).evaluate_tags(only_tags=only_tags, skip_tags=skip_tags) |
164 | 164 |
|
165 |
+ def set_loader(self, loader): |
|
166 |
+ self._loader = loader |
|
167 |
+ if self._parent_block: |
|
168 |
+ self._parent_block.set_loader(loader) |
|
169 |
+ elif self._role: |
|
170 |
+ self._role.set_loader(loader) |
|
171 |
+ |
... | ... |
@@ -45,7 +45,7 @@ class Conditional: |
45 | 45 |
False if any of them evaluate as such. |
46 | 46 |
''' |
47 | 47 |
|
48 |
- templar = Templar(variables=all_vars) |
|
48 |
+ templar = Templar(loader=self._loader, variables=all_vars) |
|
49 | 49 |
for conditional in self.when: |
50 | 50 |
if not self._check_conditional(conditional, templar): |
51 | 51 |
return False |
... | ... |
@@ -358,3 +358,10 @@ class Role(Base, Conditional, Taggable): |
358 | 358 |
|
359 | 359 |
super(Role, self).deserialize(data) |
360 | 360 |
|
361 |
+ def set_loader(self, loader): |
|
362 |
+ self._loader = loader |
|
363 |
+ for parent in self._parents: |
|
364 |
+ parent.set_loader(loader) |
|
365 |
+ for dep in self.get_direct_dependencies(): |
|
366 |
+ dep.set_loader(loader) |
|
367 |
+ |
... | ... |
@@ -200,7 +200,7 @@ class Task(Base, Conditional, Taggable): |
200 | 200 |
super(Task, self).post_validate(all_vars=all_vars, fail_on_undefined=fail_on_undefined) |
201 | 201 |
|
202 | 202 |
def _post_validate_loop_args(self, attr, value, all_vars, fail_on_undefined): |
203 |
- return listify_lookup_plugin_terms(value, all_vars) |
|
203 |
+ return listify_lookup_plugin_terms(value, all_vars, loader=self._loader) |
|
204 | 204 |
|
205 | 205 |
def get_vars(self): |
206 | 206 |
return self.serialize() |
... | ... |
@@ -283,3 +283,18 @@ class Task(Base, Conditional, Taggable): |
283 | 283 |
return False |
284 | 284 |
return super(Task, self).evaluate_tags(only_tags=only_tags, skip_tags=skip_tags) |
285 | 285 |
|
286 |
+ |
|
287 |
+ def set_loader(self, loader): |
|
288 |
+ ''' |
|
289 |
+ Sets the loader on this object and recursively on parent, child objects. |
|
290 |
+ This is used primarily after the Task has been serialized/deserialized, which |
|
291 |
+ does not preserve the loader. |
|
292 |
+ ''' |
|
293 |
+ |
|
294 |
+ self._loader = loader |
|
295 |
+ |
|
296 |
+ if self._block: |
|
297 |
+ self._block.set_loader(loader) |
|
298 |
+ |
|
299 |
+ for dep in self._dep_chain: |
|
300 |
+ dep.set_loader(loader) |
... | ... |
@@ -33,7 +33,7 @@ class ActionModule(ActionBase): |
33 | 33 |
result = dict(msg=self._task.args['msg']) |
34 | 34 |
# FIXME: move the LOOKUP_REGEX somewhere else |
35 | 35 |
elif 'var' in self._task.args: # and not utils.LOOKUP_REGEX.search(self._task.args['var']): |
36 |
- templar = Templar(variables=task_vars) |
|
36 |
+ templar = Templar(loader=self._loader, variables=task_vars) |
|
37 | 37 |
results = templar.template(self._task.args['var'], convert_bare=True) |
38 | 38 |
result = dict() |
39 | 39 |
result[self._task.args['var']] = results |
... | ... |
@@ -77,7 +77,7 @@ class ActionModule(ActionBase): |
77 | 77 |
dest = os.path.join(dest, base) |
78 | 78 |
|
79 | 79 |
# template the source data locally & get ready to transfer |
80 |
- templar = Templar(basedir=self._loader.get_basedir(), variables=task_vars) |
|
80 |
+ templar = Templar(loader=self._loader, variables=task_vars) |
|
81 | 81 |
try: |
82 | 82 |
with open(source, 'r') as f: |
83 | 83 |
template_data = f.read() |
... | ... |
@@ -22,8 +22,8 @@ __metaclass__ = type |
22 | 22 |
__all__ = ['LookupBase'] |
23 | 23 |
|
24 | 24 |
class LookupBase: |
25 |
- def __init__(self, **kwargs): |
|
26 |
- pass |
|
25 |
+ def __init__(self, loader=None, **kwargs): |
|
26 |
+ self._loader = loader |
|
27 | 27 |
|
28 | 28 |
def _flatten(self, terms): |
29 | 29 |
ret = [] |
... | ... |
@@ -41,3 +41,9 @@ class LookupBase: |
41 | 41 |
results.append(self._flatten([x,y])) |
42 | 42 |
return results |
43 | 43 |
|
44 |
+ def _flatten_hash_to_list(self, terms): |
|
45 |
+ ret = [] |
|
46 |
+ for key in terms: |
|
47 |
+ ret.append({'key': key, 'value': terms[key]}) |
|
48 |
+ return ret |
|
49 |
+ |
44 | 50 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,79 @@ |
0 |
+# (c) 2013, Jan-Piet Mens <jpmens(at)gmail.com> |
|
1 |
+# |
|
2 |
+# This file is part of Ansible |
|
3 |
+# |
|
4 |
+# Ansible is free software: you can redistribute it and/or modify |
|
5 |
+# it under the terms of the GNU General Public License as published by |
|
6 |
+# the Free Software Foundation, either version 3 of the License, or |
|
7 |
+# (at your option) any later version. |
|
8 |
+# |
|
9 |
+# Ansible is distributed in the hope that it will be useful, |
|
10 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
+# GNU General Public License for more details. |
|
13 |
+# |
|
14 |
+# You should have received a copy of the GNU General Public License |
|
15 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
16 |
+ |
|
17 |
+import os |
|
18 |
+import codecs |
|
19 |
+import csv |
|
20 |
+ |
|
21 |
+from ansible.errors import * |
|
22 |
+from ansible.plugins.lookup import LookupBase |
|
23 |
+ |
|
24 |
+class LookupModule(LookupBase): |
|
25 |
+ |
|
26 |
+ def read_csv(self, filename, key, delimiter, dflt=None, col=1): |
|
27 |
+ |
|
28 |
+ try: |
|
29 |
+ f = codecs.open(filename, 'r', encoding='utf-8') |
|
30 |
+ creader = csv.reader(f, delimiter=delimiter) |
|
31 |
+ |
|
32 |
+ for row in creader: |
|
33 |
+ if row[0] == key: |
|
34 |
+ return row[int(col)] |
|
35 |
+ except Exception, e: |
|
36 |
+ raise AnsibleError("csvfile: %s" % str(e)) |
|
37 |
+ |
|
38 |
+ return dflt |
|
39 |
+ |
|
40 |
+ def run(self, terms, variables=None, **kwargs): |
|
41 |
+ |
|
42 |
+ if isinstance(terms, basestring): |
|
43 |
+ terms = [ terms ] |
|
44 |
+ |
|
45 |
+ ret = [] |
|
46 |
+ for term in terms: |
|
47 |
+ params = term.split() |
|
48 |
+ key = params[0] |
|
49 |
+ |
|
50 |
+ paramvals = { |
|
51 |
+ 'file' : 'ansible.csv', |
|
52 |
+ 'default' : None, |
|
53 |
+ 'delimiter' : "TAB", |
|
54 |
+ 'col' : "1", # column to return |
|
55 |
+ } |
|
56 |
+ |
|
57 |
+ # parameters specified? |
|
58 |
+ try: |
|
59 |
+ for param in params[1:]: |
|
60 |
+ name, value = param.split('=') |
|
61 |
+ assert(name in paramvals) |
|
62 |
+ paramvals[name] = value |
|
63 |
+ except (ValueError, AssertionError), e: |
|
64 |
+ raise AnsibleError(e) |
|
65 |
+ |
|
66 |
+ if paramvals['delimiter'] == 'TAB': |
|
67 |
+ paramvals['delimiter'] = "\t" |
|
68 |
+ |
|
69 |
+ path = self._loader.path_dwim(paramvals['file']) |
|
70 |
+ |
|
71 |
+ var = self.read_csv(path, key, paramvals['delimiter'], paramvals['default'], paramvals['col']) |
|
72 |
+ if var is not None: |
|
73 |
+ if type(var) is list: |
|
74 |
+ for v in var: |
|
75 |
+ ret.append(v) |
|
76 |
+ else: |
|
77 |
+ ret.append(var) |
|
78 |
+ return ret |
0 | 79 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,27 @@ |
0 |
+# (c) 2014, Kent R. Spillner <kspillner@acm.org> |
|
1 |
+# |
|
2 |
+# This file is part of Ansible |
|
3 |
+# |
|
4 |
+# Ansible is free software: you can redistribute it and/or modify |
|
5 |
+# it under the terms of the GNU General Public License as published by |
|
6 |
+# the Free Software Foundation, either version 3 of the License, or |
|
7 |
+# (at your option) any later version. |
|
8 |
+# |
|
9 |
+# Ansible is distributed in the hope that it will be useful, |
|
10 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
+# GNU General Public License for more details. |
|
13 |
+# |
|
14 |
+# You should have received a copy of the GNU General Public License |
|
15 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
16 |
+ |
|
17 |
+from ansible.plugins.lookup import LookupBase |
|
18 |
+ |
|
19 |
+class LookupModule(LookupBase): |
|
20 |
+ |
|
21 |
+ def run(self, terms, varibles=None, **kwargs): |
|
22 |
+ |
|
23 |
+ if not isinstance(terms, dict): |
|
24 |
+ raise errors.AnsibleError("with_dict expects a dict") |
|
25 |
+ |
|
26 |
+ return self._flatten_hash_to_list(terms) |
0 | 27 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,68 @@ |
0 |
+# (c) 2012, Jan-Piet Mens <jpmens(at)gmail.com> |
|
1 |
+# |
|
2 |
+# This file is part of Ansible |
|
3 |
+# |
|
4 |
+# Ansible is free software: you can redistribute it and/or modify |
|
5 |
+# it under the terms of the GNU General Public License as published by |
|
6 |
+# the Free Software Foundation, either version 3 of the License, or |
|
7 |
+# (at your option) any later version. |
|
8 |
+# |
|
9 |
+# Ansible is distributed in the hope that it will be useful, |
|
10 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
+# GNU General Public License for more details. |
|
13 |
+# |
|
14 |
+# You should have received a copy of the GNU General Public License |
|
15 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
16 |
+ |
|
17 |
+import os |
|
18 |
+ |
|
19 |
+HAVE_DNS=False |
|
20 |
+try: |
|
21 |
+ import dns.resolver |
|
22 |
+ from dns.exception import DNSException |
|
23 |
+ HAVE_DNS=True |
|
24 |
+except ImportError: |
|
25 |
+ pass |
|
26 |
+ |
|
27 |
+from ansible.errors import * |
|
28 |
+from ansible.plugins.lookup import LookupBase |
|
29 |
+ |
|
30 |
+# ============================================================== |
|
31 |
+# DNSTXT: DNS TXT records |
|
32 |
+# |
|
33 |
+# key=domainname |
|
34 |
+# TODO: configurable resolver IPs |
|
35 |
+# -------------------------------------------------------------- |
|
36 |
+ |
|
37 |
+class LookupModule(LookupBase): |
|
38 |
+ |
|
39 |
+ def run(self, terms, variables=None, **kwargs): |
|
40 |
+ |
|
41 |
+ if HAVE_DNS == False: |
|
42 |
+ raise AnsibleError("Can't LOOKUP(dnstxt): module dns.resolver is not installed") |
|
43 |
+ |
|
44 |
+ if isinstance(terms, basestring): |
|
45 |
+ terms = [ terms ] |
|
46 |
+ |
|
47 |
+ ret = [] |
|
48 |
+ for term in terms: |
|
49 |
+ domain = term.split()[0] |
|
50 |
+ string = [] |
|
51 |
+ try: |
|
52 |
+ answers = dns.resolver.query(domain, 'TXT') |
|
53 |
+ for rdata in answers: |
|
54 |
+ s = rdata.to_text() |
|
55 |
+ string.append(s[1:-1]) # Strip outside quotes on TXT rdata |
|
56 |
+ |
|
57 |
+ except dns.resolver.NXDOMAIN: |
|
58 |
+ string = 'NXDOMAIN' |
|
59 |
+ except dns.resolver.Timeout: |
|
60 |
+ string = '' |
|
61 |
+ except dns.exception.DNSException, e: |
|
62 |
+ raise AnsibleError("dns.resolver unhandled exception", e) |
|
63 |
+ |
|
64 |
+ ret.append(''.join(string)) |
|
65 |
+ |
|
66 |
+ return ret |
|
67 |
+ |
0 | 68 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,34 @@ |
0 |
+# (c) 2012, Jan-Piet Mens <jpmens(at)gmail.com> |
|
1 |
+# |
|
2 |
+# This file is part of Ansible |
|
3 |
+# |
|
4 |
+# Ansible is free software: you can redistribute it and/or modify |
|
5 |
+# it under the terms of the GNU General Public License as published by |
|
6 |
+# the Free Software Foundation, either version 3 of the License, or |
|
7 |
+# (at your option) any later version. |
|
8 |
+# |
|
9 |
+# Ansible is distributed in the hope that it will be useful, |
|
10 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
+# GNU General Public License for more details. |
|
13 |
+# |
|
14 |
+# You should have received a copy of the GNU General Public License |
|
15 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
16 |
+ |
|
17 |
+import os |
|
18 |
+ |
|
19 |
+from ansible.plugins.lookup import LookupBase |
|
20 |
+ |
|
21 |
+class LookupModule(LookupBase): |
|
22 |
+ |
|
23 |
+ def run(self, terms, variables, **kwargs): |
|
24 |
+ |
|
25 |
+ if isinstance(terms, basestring): |
|
26 |
+ terms = [ terms ] |
|
27 |
+ |
|
28 |
+ ret = [] |
|
29 |
+ for term in terms: |
|
30 |
+ var = term.split()[0] |
|
31 |
+ ret.append(os.getenv(var, '')) |
|
32 |
+ |
|
33 |
+ return ret |
0 | 34 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,75 @@ |
0 |
+# (c) 2013, Jan-Piet Mens <jpmens(at)gmail.com> |
|
1 |
+# |
|
2 |
+# This file is part of Ansible |
|
3 |
+# |
|
4 |
+# Ansible is free software: you can redistribute it and/or modify |
|
5 |
+# it under the terms of the GNU General Public License as published by |
|
6 |
+# the Free Software Foundation, either version 3 of the License, or |
|
7 |
+# (at your option) any later version. |
|
8 |
+# |
|
9 |
+# Ansible is distributed in the hope that it will be useful, |
|
10 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
+# GNU General Public License for more details. |
|
13 |
+# |
|
14 |
+# You should have received a copy of the GNU General Public License |
|
15 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
16 |
+ |
|
17 |
+import os |
|
18 |
+import urllib2 |
|
19 |
+try: |
|
20 |
+ import json |
|
21 |
+except ImportError: |
|
22 |
+ import simplejson as json |
|
23 |
+ |
|
24 |
+from ansible.plugins.lookup import LookupBase |
|
25 |
+ |
|
26 |
+# this can be made configurable, not should not use ansible.cfg |
|
27 |
+ANSIBLE_ETCD_URL = 'http://127.0.0.1:4001' |
|
28 |
+if os.getenv('ANSIBLE_ETCD_URL') is not None: |
|
29 |
+ ANSIBLE_ETCD_URL = os.environ['ANSIBLE_ETCD_URL'] |
|
30 |
+ |
|
31 |
+class etcd(): |
|
32 |
+ def __init__(self, url=ANSIBLE_ETCD_URL): |
|
33 |
+ self.url = url |
|
34 |
+ self.baseurl = '%s/v1/keys' % (self.url) |
|
35 |
+ |
|
36 |
+ def get(self, key): |
|
37 |
+ url = "%s/%s" % (self.baseurl, key) |
|
38 |
+ |
|
39 |
+ data = None |
|
40 |
+ value = "" |
|
41 |
+ try: |
|
42 |
+ r = urllib2.urlopen(url) |
|
43 |
+ data = r.read() |
|
44 |
+ except: |
|
45 |
+ return value |
|
46 |
+ |
|
47 |
+ try: |
|
48 |
+ # {"action":"get","key":"/name","value":"Jane Jolie","index":5} |
|
49 |
+ item = json.loads(data) |
|
50 |
+ if 'value' in item: |
|
51 |
+ value = item['value'] |
|
52 |
+ if 'errorCode' in item: |
|
53 |
+ value = "ENOENT" |
|
54 |
+ except: |
|
55 |
+ raise |
|
56 |
+ pass |
|
57 |
+ |
|
58 |
+ return value |
|
59 |
+ |
|
60 |
+class LookupModule(LookupBase): |
|
61 |
+ |
|
62 |
+ def run(self, terms, variables, **kwargs): |
|
63 |
+ |
|
64 |
+ if isinstance(terms, basestring): |
|
65 |
+ terms = [ terms ] |
|
66 |
+ |
|
67 |
+ etcd = etcd() |
|
68 |
+ |
|
69 |
+ ret = [] |
|
70 |
+ for term in terms: |
|
71 |
+ key = term.split()[0] |
|
72 |
+ value = etcd.get(key) |
|
73 |
+ ret.append(value) |
|
74 |
+ return ret |
0 | 75 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,58 @@ |
0 |
+# (c) 2012, Daniel Hokka Zakrisson <daniel@hozac.com> |
|
1 |
+# |
|
2 |
+# This file is part of Ansible |
|
3 |
+# |
|
4 |
+# Ansible is free software: you can redistribute it and/or modify |
|
5 |
+# it under the terms of the GNU General Public License as published by |
|
6 |
+# the Free Software Foundation, either version 3 of the License, or |
|
7 |
+# (at your option) any later version. |
|
8 |
+# |
|
9 |
+# Ansible is distributed in the hope that it will be useful, |
|
10 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
+# GNU General Public License for more details. |
|
13 |
+# |
|
14 |
+# You should have received a copy of the GNU General Public License |
|
15 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
16 |
+ |
|
17 |
+import os |
|
18 |
+import codecs |
|
19 |
+ |
|
20 |
+from ansible.errors import * |
|
21 |
+from ansible.plugins.lookup import LookupBase |
|
22 |
+ |
|
23 |
+class LookupModule(LookupBase): |
|
24 |
+ |
|
25 |
+ def run(self, terms, variables=None, **kwargs): |
|
26 |
+ |
|
27 |
+ if not isinstance(terms, list): |
|
28 |
+ terms = [ terms ] |
|
29 |
+ |
|
30 |
+ ret = [] |
|
31 |
+ for term in terms: |
|
32 |
+ basedir_path = self._loader.path_dwim(term) |
|
33 |
+ relative_path = None |
|
34 |
+ playbook_path = None |
|
35 |
+ |
|
36 |
+ # Special handling of the file lookup, used primarily when the |
|
37 |
+ # lookup is done from a role. If the file isn't found in the |
|
38 |
+ # basedir of the current file, use dwim_relative to look in the |
|
39 |
+ # role/files/ directory, and finally the playbook directory |
|
40 |
+ # itself (which will be relative to the current working dir) |
|
41 |
+ |
|
42 |
+ # FIXME: the original file stuff still needs to be worked out, but the |
|
43 |
+ # playbook_dir stuff should be able to be removed as it should |
|
44 |
+ # be covered by the fact that the loader contains that info |
|
45 |
+ #if '_original_file' in variables: |
|
46 |
+ # relative_path = self._loader.path_dwim_relative(variables['_original_file'], 'files', term, self.basedir, check=False) |
|
47 |
+ #if 'playbook_dir' in variables: |
|
48 |
+ # playbook_path = os.path.join(variables['playbook_dir'], term) |
|
49 |
+ |
|
50 |
+ for path in (basedir_path, relative_path, playbook_path): |
|
51 |
+ if path and os.path.exists(path): |
|
52 |
+ ret.append(codecs.open(path, encoding="utf8").read().rstrip()) |
|
53 |
+ break |
|
54 |
+ else: |
|
55 |
+ raise AnsibleError("could not locate file in lookup: %s" % term) |
|
56 |
+ |
|
57 |
+ return ret |
0 | 58 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,32 @@ |
0 |
+# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> |
|
1 |
+# |
|
2 |
+# This file is part of Ansible |
|
3 |
+# |
|
4 |
+# Ansible is free software: you can redistribute it and/or modify |
|
5 |
+# it under the terms of the GNU General Public License as published by |
|
6 |
+# the Free Software Foundation, either version 3 of the License, or |
|
7 |
+# (at your option) any later version. |
|
8 |
+# |
|
9 |
+# Ansible is distributed in the hope that it will be useful, |
|
10 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
+# GNU General Public License for more details. |
|
13 |
+# |
|
14 |
+# You should have received a copy of the GNU General Public License |
|
15 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
16 |
+ |
|
17 |
+import os |
|
18 |
+import glob |
|
19 |
+ |
|
20 |
+from ansible.plugins.lookup import LookupBase |
|
21 |
+ |
|
22 |
+class LookupModule(LookupBase): |
|
23 |
+ |
|
24 |
+ def run(self, terms, variables=None, **kwargs): |
|
25 |
+ |
|
26 |
+ ret = [] |
|
27 |
+ for term in terms: |
|
28 |
+ dwimmed = self._loader.path_dwim(term) |
|
29 |
+ globbed = glob.glob(dwimmed) |
|
30 |
+ ret.extend(g for g in globbed if os.path.isfile(g)) |
|
31 |
+ return ret |
0 | 32 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,191 @@ |
0 |
+# (c) 2013, seth vidal <skvidal@fedoraproject.org> red hat, inc |
|
1 |
+# |
|
2 |
+# This file is part of Ansible |
|
3 |
+# |
|
4 |
+# Ansible is free software: you can redistribute it and/or modify |
|
5 |
+# it under the terms of the GNU General Public License as published by |
|
6 |
+# the Free Software Foundation, either version 3 of the License, or |
|
7 |
+# (at your option) any later version. |
|
8 |
+# |
|
9 |
+# Ansible is distributed in the hope that it will be useful, |
|
10 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
+# GNU General Public License for more details. |
|
13 |
+# |
|
14 |
+# You should have received a copy of the GNU General Public License |
|
15 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
16 |
+ |
|
17 |
+# take a list of files and (optionally) a list of paths |
|
18 |
+# return the first existing file found in the paths |
|
19 |
+# [file1, file2, file3], [path1, path2, path3] |
|
20 |
+# search order is: |
|
21 |
+# path1/file1 |
|
22 |
+# path1/file2 |
|
23 |
+# path1/file3 |
|
24 |
+# path2/file1 |
|
25 |
+# path2/file2 |
|
26 |
+# path2/file3 |
|
27 |
+# path3/file1 |
|
28 |
+# path3/file2 |
|
29 |
+# path3/file3 |
|
30 |
+ |
|
31 |
+# first file found with os.path.exists() is returned |
|
32 |
+# no file matches raises ansibleerror |
|
33 |
+# EXAMPLES |
|
34 |
+# - name: copy first existing file found to /some/file |
|
35 |
+# action: copy src=$item dest=/some/file |
|
36 |
+# with_first_found: |
|
37 |
+# - files: foo ${inventory_hostname} bar |
|
38 |
+# paths: /tmp/production /tmp/staging |
|
39 |
+ |
|
40 |
+# that will look for files in this order: |
|
41 |
+# /tmp/production/foo |
|
42 |
+# ${inventory_hostname} |
|
43 |
+# bar |
|
44 |
+# /tmp/staging/foo |
|
45 |
+# ${inventory_hostname} |
|
46 |
+# bar |
|
47 |
+ |
|
48 |
+# - name: copy first existing file found to /some/file |
|
49 |
+# action: copy src=$item dest=/some/file |
|
50 |
+# with_first_found: |
|
51 |
+# - files: /some/place/foo ${inventory_hostname} /some/place/else |
|
52 |
+ |
|
53 |
+# that will look for files in this order: |
|
54 |
+# /some/place/foo |
|
55 |
+# $relative_path/${inventory_hostname} |
|
56 |
+# /some/place/else |
|
57 |
+ |
|
58 |
+# example - including tasks: |
|
59 |
+# tasks: |
|
60 |
+# - include: $item |
|
61 |
+# with_first_found: |
|
62 |
+# - files: generic |
|
63 |
+# paths: tasks/staging tasks/production |
|
64 |
+# this will include the tasks in the file generic where it is found first (staging or production) |
|
65 |
+ |
|
66 |
+# example simple file lists |
|
67 |
+#tasks: |
|
68 |
+#- name: first found file |
|
69 |
+# action: copy src=$item dest=/etc/file.cfg |
|
70 |
+# with_first_found: |
|
71 |
+# - files: foo.${inventory_hostname} foo |
|
72 |
+ |
|
73 |
+ |
|
74 |
+# example skipping if no matched files |
|
75 |
+# First_found also offers the ability to control whether or not failing |
|
76 |
+# to find a file returns an error or not |
|
77 |
+# |
|
78 |
+#- name: first found file - or skip |
|
79 |
+# action: copy src=$item dest=/etc/file.cfg |
|
80 |
+# with_first_found: |
|
81 |
+# - files: foo.${inventory_hostname} |
|
82 |
+# skip: true |
|
83 |
+ |
|
84 |
+# example a role with default configuration and configuration per host |
|
85 |
+# you can set multiple terms with their own files and paths to look through. |
|
86 |
+# consider a role that sets some configuration per host falling back on a default config. |
|
87 |
+# |
|
88 |
+#- name: some configuration template |
|
89 |
+# template: src={{ item }} dest=/etc/file.cfg mode=0444 owner=root group=root |
|
90 |
+# with_first_found: |
|
91 |
+# - files: |
|
92 |
+# - ${inventory_hostname}/etc/file.cfg |
|
93 |
+# paths: |
|
94 |
+# - ../../../templates.overwrites |
|
95 |
+# - ../../../templates |
|
96 |
+# - files: |
|
97 |
+# - etc/file.cfg |
|
98 |
+# paths: |
|
99 |
+# - templates |
|
100 |
+ |
|
101 |
+# the above will return an empty list if the files cannot be found at all |
|
102 |
+# if skip is unspecificed or if it is set to false then it will return a list |
|
103 |
+# error which can be caught bye ignore_errors: true for that action. |
|
104 |
+ |
|
105 |
+# finally - if you want you can use it, in place to replace first_available_file: |
|
106 |
+# you simply cannot use the - files, path or skip options. simply replace |
|
107 |
+# first_available_file with with_first_found and leave the file listing in place |
|
108 |
+# |
|
109 |
+# |
|
110 |
+# - name: with_first_found like first_available_file |
|
111 |
+# action: copy src=$item dest=/tmp/faftest |
|
112 |
+# with_first_found: |
|
113 |
+# - ../files/foo |
|
114 |
+# - ../files/bar |
|
115 |
+# - ../files/baz |
|
116 |
+# ignore_errors: true |
|
117 |
+ |
|
118 |
+ |
|
119 |
+import os |
|
120 |
+ |
|
121 |
+from ansible.plugins.lookup import LookupBase |
|
122 |
+ |
|
123 |
+class LookupModule(LookupBase): |
|
124 |
+ |
|
125 |
+ def run(self, terms, variables, **kwargs): |
|
126 |
+ |
|
127 |
+ result = None |
|
128 |
+ anydict = False |
|
129 |
+ skip = False |
|
130 |
+ |
|
131 |
+ for term in terms: |
|
132 |
+ if isinstance(term, dict): |
|
133 |
+ anydict = True |
|
134 |
+ |
|
135 |
+ total_search = [] |
|
136 |
+ if anydict: |
|
137 |
+ for term in terms: |
|
138 |
+ if isinstance(term, dict): |
|
139 |
+ files = term.get('files', []) |
|
140 |
+ paths = term.get('paths', []) |
|
141 |
+ skip = boolean(term.get('skip', False)) |
|
142 |
+ |
|
143 |
+ filelist = files |
|
144 |
+ if isinstance(files, basestring): |
|
145 |
+ files = files.replace(',', ' ') |
|
146 |
+ files = files.replace(';', ' ') |
|
147 |
+ filelist = files.split(' ') |
|
148 |
+ |
|
149 |
+ pathlist = paths |
|
150 |
+ if paths: |
|
151 |
+ if isinstance(paths, basestring): |
|
152 |
+ paths = paths.replace(',', ' ') |
|
153 |
+ paths = paths.replace(':', ' ') |
|
154 |
+ paths = paths.replace(';', ' ') |
|
155 |
+ pathlist = paths.split(' ') |
|
156 |
+ |
|
157 |
+ if not pathlist: |
|
158 |
+ total_search = filelist |
|
159 |
+ else: |
|
160 |
+ for path in pathlist: |
|
161 |
+ for fn in filelist: |
|
162 |
+ f = os.path.join(path, fn) |
|
163 |
+ total_search.append(f) |
|
164 |
+ else: |
|
165 |
+ total_search.append(term) |
|
166 |
+ else: |
|
167 |
+ total_search = terms |
|
168 |
+ |
|
169 |
+ for fn in total_search: |
|
170 |
+ # FIXME: the original file stuff needs to be fixed/implemented |
|
171 |
+ #if variables and '_original_file' in variables: |
|
172 |
+ # # check the templates and vars directories too, |
|
173 |
+ # # if they exist |
|
174 |
+ # for roledir in ('templates', 'vars'): |
|
175 |
+ # path = self._loader.path_dwim(os.path.join(self.basedir, '..', roledir), fn) |
|
176 |
+ # if os.path.exists(path): |
|
177 |
+ # return [path] |
|
178 |
+ |
|
179 |
+ # if none of the above were found, just check the |
|
180 |
+ # current filename against the basedir (this will already |
|
181 |
+ # have ../files from runner, if it's a role task |
|
182 |
+ path = self._loader.path_dwim(fn) |
|
183 |
+ if os.path.exists(path): |
|
184 |
+ return [path] |
|
185 |
+ else: |
|
186 |
+ if skip: |
|
187 |
+ return [] |
|
188 |
+ else: |
|
189 |
+ return [None] |
|
190 |
+ |
0 | 191 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,69 @@ |
0 |
+# (c) 2013, Serge van Ginderachter <serge@vanginderachter.be> |
|
1 |
+# |
|
2 |
+# This file is part of Ansible |
|
3 |
+# |
|
4 |
+# Ansible is free software: you can redistribute it and/or modify |
|
5 |
+# it under the terms of the GNU General Public License as published by |
|
6 |
+# the Free Software Foundation, either version 3 of the License, or |
|
7 |
+# (at your option) any later version. |
|
8 |
+# |
|
9 |
+# Ansible is distributed in the hope that it will be useful, |
|
10 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
+# GNU General Public License for more details. |
|
13 |
+# |
|
14 |
+# You should have received a copy of the GNU General Public License |
|
15 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
16 |
+ |
|
17 |
+ |
|
18 |
+from ansible.errors import * |
|
19 |
+from ansible.plugins.lookup import LookupBase |
|
20 |
+from ansible.utils.listify import listify_lookup_plugin_terms |
|
21 |
+ |
|
22 |
+class LookupModule(LookupBase): |
|
23 |
+ |
|
24 |
+ def _check_list_of_one_list(self, term): |
|
25 |
+ # make sure term is not a list of one (list of one..) item |
|
26 |
+ # return the final non list item if so |
|
27 |
+ |
|
28 |
+ if isinstance(term,list) and len(term) == 1: |
|
29 |
+ term = term[0] |
|
30 |
+ if isinstance(term,list): |
|
31 |
+ term = self._check_list_of_one_list(term) |
|
32 |
+ |
|
33 |
+ return term |
|
34 |
+ |
|
35 |
+ def _do_flatten(self, terms, variables): |
|
36 |
+ |
|
37 |
+ ret = [] |
|
38 |
+ for term in terms: |
|
39 |
+ term = self._check_list_of_one_list(term) |
|
40 |
+ |
|
41 |
+ if term == 'None' or term == 'null': |
|
42 |
+ # ignore undefined items |
|
43 |
+ break |
|
44 |
+ |
|
45 |
+ if isinstance(term, basestring): |
|
46 |
+ # convert a variable to a list |
|
47 |
+ term2 = listify_lookup_plugin_terms(term, variables, loader=self._loader) |
|
48 |
+ # but avoid converting a plain string to a list of one string |
|
49 |
+ if term2 != [ term ]: |
|
50 |
+ term = term2 |
|
51 |
+ |
|
52 |
+ if isinstance(term, list): |
|
53 |
+ # if it's a list, check recursively for items that are a list |
|
54 |
+ term = self._do_flatten(term, variables) |
|
55 |
+ ret.extend(term) |
|
56 |
+ else: |
|
57 |
+ ret.append(term) |
|
58 |
+ |
|
59 |
+ return ret |
|
60 |
+ |
|
61 |
+ |
|
62 |
+ def run(self, terms, variables, **kwargs): |
|
63 |
+ |
|
64 |
+ if not isinstance(terms, list): |
|
65 |
+ raise AnsibleError("with_flattened expects a list") |
|
66 |
+ |
|
67 |
+ return self._do_flatten(terms, variables) |
|
68 |
+ |
0 | 69 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,32 @@ |
0 |
+# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> |
|
1 |
+# |
|
2 |
+# This file is part of Ansible |
|
3 |
+# |
|
4 |
+# Ansible is free software: you can redistribute it and/or modify |
|
5 |
+# it under the terms of the GNU General Public License as published by |
|
6 |
+# the Free Software Foundation, either version 3 of the License, or |
|
7 |
+# (at your option) any later version. |
|
8 |
+# |
|
9 |
+# Ansible is distributed in the hope that it will be useful, |
|
10 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
+# GNU General Public License for more details. |
|
13 |
+# |
|
14 |
+# You should have received a copy of the GNU General Public License |
|
15 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
16 |
+ |
|
17 |
+from ansible.plugins.lookup import LookupBase |
|
18 |
+ |
|
19 |
+class LookupModule(LookupBase): |
|
20 |
+ |
|
21 |
+ def __init__(self, basedir=None, **kwargs): |
|
22 |
+ self.basedir = basedir |
|
23 |
+ |
|
24 |
+ def run(self, terms, variables, **kwargs): |
|
25 |
+ |
|
26 |
+ if not isinstance(terms, list): |
|
27 |
+ raise errors.AnsibleError("with_indexed_items expects a list") |
|
28 |
+ |
|
29 |
+ items = self._flatten(terms) |
|
30 |
+ return zip(range(len(items)), items) |
|
31 |
+ |
0 | 32 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,34 @@ |
0 |
+# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> |
|
1 |
+# (c) 2013, Steven Dossett <sdossett@panath.com> |
|
2 |
+# |
|
3 |
+# This file is part of Ansible |
|
4 |
+# |
|
5 |
+# Ansible is free software: you can redistribute it and/or modify |
|
6 |
+# it under the terms of the GNU General Public License as published by |
|
7 |
+# the Free Software Foundation, either version 3 of the License, or |
|
8 |
+# (at your option) any later version. |
|
9 |
+# |
|
10 |
+# Ansible is distributed in the hope that it will be useful, |
|
11 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 |
+# GNU General Public License for more details. |
|
14 |
+# |
|
15 |
+# You should have received a copy of the GNU General Public License |
|
16 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
17 |
+ |
|
18 |
+from ansible.errors import * |
|
19 |
+from ansible.plugins.lookup import LookupBase |
|
20 |
+ |
|
21 |
+class LookupModule(LookupBase): |
|
22 |
+ |
|
23 |
+ def run(self, terms, inject=None, **kwargs): |
|
24 |
+ if not isinstance(terms, list): |
|
25 |
+ raise AnsibleError("with_inventory_hostnames expects a list") |
|
26 |
+ |
|
27 |
+ # FIXME: the inventory is no longer available this way, so we may have |
|
28 |
+ # to dump the host list into the list of variables and read it back |
|
29 |
+ # in here (or the inventory sources, so we can recreate the list |
|
30 |
+ # of hosts) |
|
31 |
+ #return self._flatten(inventory.Inventory(self.host_list).list_hosts(terms)) |
|
32 |
+ return terms |
|
33 |
+ |
0 | 34 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,35 @@ |
0 |
+# (c) 2012, Daniel Hokka Zakrisson <daniel@hozac.com> |
|
1 |
+# |
|
2 |
+# This file is part of Ansible |
|
3 |
+# |
|
4 |
+# Ansible is free software: you can redistribute it and/or modify |
|
5 |
+# it under the terms of the GNU General Public License as published by |
|
6 |
+# the Free Software Foundation, either version 3 of the License, or |
|
7 |
+# (at your option) any later version. |
|
8 |
+# |
|
9 |
+# Ansible is distributed in the hope that it will be useful, |
|
10 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
+# GNU General Public License for more details. |
|
13 |
+# |
|
14 |
+# You should have received a copy of the GNU General Public License |
|
15 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
16 |
+ |
|
17 |
+import subprocess |
|
18 |
+ |
|
19 |
+from ansible.errors import * |
|
20 |
+from ansible.plugins.lookup import LookupBase |
|
21 |
+ |
|
22 |
+class LookupModule(LookupBase): |
|
23 |
+ |
|
24 |
+ def run(self, terms, variables, **kwargs): |
|
25 |
+ |
|
26 |
+ ret = [] |
|
27 |
+ for term in terms: |
|
28 |
+ p = subprocess.Popen(term, cwd=self._loader.get_basedir(), shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE) |
|
29 |
+ (stdout, stderr) = p.communicate() |
|
30 |
+ if p.returncode == 0: |
|
31 |
+ ret.extend(stdout.splitlines()) |
|
32 |
+ else: |
|
33 |
+ raise AnsibleError("lookup_plugin.lines(%s) returned %d" % (term, p.returncode)) |
|
34 |
+ return ret |
... | ... |
@@ -24,7 +24,7 @@ class LookupModule(LookupBase): |
24 | 24 |
def __lookup_variabless(self, terms, variables): |
25 | 25 |
results = [] |
26 | 26 |
for x in terms: |
27 |
- intermediate = listify_lookup_plugin_terms(x, variables) |
|
27 |
+ intermediate = listify_lookup_plugin_terms(x, variables, loader=self._loader) |
|
28 | 28 |
results.append(intermediate) |
29 | 29 |
return results |
30 | 30 |
|
31 | 31 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,146 @@ |
0 |
+# (c) 2012, Daniel Hokka Zakrisson <daniel@hozac.com> |
|
1 |
+# (c) 2013, Javier Candeira <javier@candeira.com> |
|
2 |
+# (c) 2013, Maykel Moya <mmoya@speedyrails.com> |
|
3 |
+# |
|
4 |
+# This file is part of Ansible |
|
5 |
+# |
|
6 |
+# Ansible is free software: you can redistribute it and/or modify |
|
7 |
+# it under the terms of the GNU General Public License as published by |
|
8 |
+# the Free Software Foundation, either version 3 of the License, or |
|
9 |
+# (at your option) any later version. |
|
10 |
+# |
|
11 |
+# Ansible is distributed in the hope that it will be useful, |
|
12 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 |
+# GNU General Public License for more details. |
|
15 |
+# |
|
16 |
+# You should have received a copy of the GNU General Public License |
|
17 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
18 |
+ |
|
19 |
+import os |
|
20 |
+import errno |
|
21 |
+import string |
|
22 |
+import random |
|
23 |
+ |
|
24 |
+from string import ascii_letters, digits |
|
25 |
+ |
|
26 |
+from ansible import constants as C |
|
27 |
+from ansible.errors import AnsibleError |
|
28 |
+from ansible.plugins.lookup import LookupBase |
|
29 |
+from ansible.utils.encrypt import do_encrypt |
|
30 |
+ |
|
31 |
+DEFAULT_LENGTH = 20 |
|
32 |
+ |
|
33 |
+class LookupModule(LookupBase): |
|
34 |
+ |
|
35 |
+ def random_password(self, length=DEFAULT_LENGTH, chars=C.DEFAULT_PASSWORD_CHARS): |
|
36 |
+ ''' |
|
37 |
+ Return a random password string of length containing only chars. |
|
38 |
+ NOTE: this was moved from the old ansible utils code, as nothing |
|
39 |
+ else appeared to use it. |
|
40 |
+ ''' |
|
41 |
+ |
|
42 |
+ password = [] |
|
43 |
+ while len(password) < length: |
|
44 |
+ new_char = os.urandom(1) |
|
45 |
+ if new_char in chars: |
|
46 |
+ password.append(new_char) |
|
47 |
+ |
|
48 |
+ return ''.join(password) |
|
49 |
+ |
|
50 |
+ def random_salt(self): |
|
51 |
+ salt_chars = ascii_letters + digits + './' |
|
52 |
+ return self.random_password(length=8, chars=salt_chars) |
|
53 |
+ |
|
54 |
+ def run(self, terms, variables, **kwargs): |
|
55 |
+ |
|
56 |
+ ret = [] |
|
57 |
+ |
|
58 |
+ if not isinstance(terms, list): |
|
59 |
+ terms = [ terms ] |
|
60 |
+ |
|
61 |
+ for term in terms: |
|
62 |
+ # you can't have escaped spaces in yor pathname |
|
63 |
+ params = term.split() |
|
64 |
+ relpath = params[0] |
|
65 |
+ |
|
66 |
+ paramvals = { |
|
67 |
+ 'length': DEFAULT_LENGTH, |
|
68 |
+ 'encrypt': None, |
|
69 |
+ 'chars': ['ascii_letters','digits',".,:-_"], |
|
70 |
+ } |
|
71 |
+ |
|
72 |
+ # get non-default parameters if specified |
|
73 |
+ try: |
|
74 |
+ for param in params[1:]: |
|
75 |
+ name, value = param.split('=') |
|
76 |
+ assert(name in paramvals) |
|
77 |
+ if name == 'length': |
|
78 |
+ paramvals[name] = int(value) |
|
79 |
+ elif name == 'chars': |
|
80 |
+ use_chars=[] |
|
81 |
+ if ",," in value: |
|
82 |
+ use_chars.append(',') |
|
83 |
+ use_chars.extend(value.replace(',,',',').split(',')) |
|
84 |
+ paramvals['chars'] = use_chars |
|
85 |
+ else: |
|
86 |
+ paramvals[name] = value |
|
87 |
+ except (ValueError, AssertionError), e: |
|
88 |
+ raise AnsibleError(e) |
|
89 |
+ |
|
90 |
+ length = paramvals['length'] |
|
91 |
+ encrypt = paramvals['encrypt'] |
|
92 |
+ use_chars = paramvals['chars'] |
|
93 |
+ |
|
94 |
+ # get password or create it if file doesn't exist |
|
95 |
+ path = self._loader.path_dwim(relpath) |
|
96 |
+ if not os.path.exists(path): |
|
97 |
+ pathdir = os.path.dirname(path) |
|
98 |
+ if not os.path.isdir(pathdir): |
|
99 |
+ try: |
|
100 |
+ os.makedirs(pathdir, mode=0700) |
|
101 |
+ except OSError, e: |
|
102 |
+ raise AnsibleError("cannot create the path for the password lookup: %s (error was %s)" % (pathdir, str(e))) |
|
103 |
+ |
|
104 |
+ chars = "".join([getattr(string,c,c) for c in use_chars]).replace('"','').replace("'",'') |
|
105 |
+ password = ''.join(random.choice(chars) for _ in range(length)) |
|
106 |
+ |
|
107 |
+ if encrypt is not None: |
|
108 |
+ salt = self.random_salt() |
|
109 |
+ content = '%s salt=%s' % (password, salt) |
|
110 |
+ else: |
|
111 |
+ content = password |
|
112 |
+ with open(path, 'w') as f: |
|
113 |
+ os.chmod(path, 0600) |
|
114 |
+ f.write(content + '\n') |
|
115 |
+ else: |
|
116 |
+ content = open(path).read().rstrip() |
|
117 |
+ sep = content.find(' ') |
|
118 |
+ |
|
119 |
+ if sep >= 0: |
|
120 |
+ password = content[:sep] |
|
121 |
+ salt = content[sep+1:].split('=')[1] |
|
122 |
+ else: |
|
123 |
+ password = content |
|
124 |
+ salt = None |
|
125 |
+ |
|
126 |
+ # crypt requested, add salt if missing |
|
127 |
+ if (encrypt is not None and not salt): |
|
128 |
+ salt = self.random_salt() |
|
129 |
+ content = '%s salt=%s' % (password, salt) |
|
130 |
+ with open(path, 'w') as f: |
|
131 |
+ os.chmod(path, 0600) |
|
132 |
+ f.write(content + '\n') |
|
133 |
+ # crypt not requested, remove salt if present |
|
134 |
+ elif (encrypt is None and salt): |
|
135 |
+ with open(path, 'w') as f: |
|
136 |
+ os.chmod(path, 0600) |
|
137 |
+ f.write(password + '\n') |
|
138 |
+ |
|
139 |
+ if encrypt: |
|
140 |
+ password = do_encrypt(password, encrypt, salt=salt) |
|
141 |
+ |
|
142 |
+ ret.append(password) |
|
143 |
+ |
|
144 |
+ return ret |
|
145 |
+ |
0 | 146 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,49 @@ |
0 |
+# (c) 2012, Daniel Hokka Zakrisson <daniel@hozac.com> |
|
1 |
+# |
|
2 |
+# This file is part of Ansible |
|
3 |
+# |
|
4 |
+# Ansible is free software: you can redistribute it and/or modify |
|
5 |
+# it under the terms of the GNU General Public License as published by |
|
6 |
+# the Free Software Foundation, either version 3 of the License, or |
|
7 |
+# (at your option) any later version. |
|
8 |
+# |
|
9 |
+# Ansible is distributed in the hope that it will be useful, |
|
10 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
+# GNU General Public License for more details. |
|
13 |
+# |
|
14 |
+# You should have received a copy of the GNU General Public License |
|
15 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
16 |
+ |
|
17 |
+import subprocess |
|
18 |
+ |
|
19 |
+from ansible.errors import AnsibleError |
|
20 |
+from ansible.plugins.lookup import LookupBase |
|
21 |
+ |
|
22 |
+class LookupModule(LookupBase): |
|
23 |
+ |
|
24 |
+ def run(self, terms, variables, **kwargs): |
|
25 |
+ |
|
26 |
+ if isinstance(terms, basestring): |
|
27 |
+ terms = [ terms ] |
|
28 |
+ |
|
29 |
+ ret = [] |
|
30 |
+ for term in terms: |
|
31 |
+ ''' |
|
32 |
+ http://docs.python.org/2/library/subprocess.html#popen-constructor |
|
33 |
+ |
|
34 |
+ The shell argument (which defaults to False) specifies whether to use the |
|
35 |
+ shell as the program to execute. If shell is True, it is recommended to pass |
|
36 |
+ args as a string rather than as a sequence |
|
37 |
+ |
|
38 |
+ https://github.com/ansible/ansible/issues/6550 |
|
39 |
+ ''' |
|
40 |
+ term = str(term) |
|
41 |
+ |
|
42 |
+ p = subprocess.Popen(term, cwd=self._loader.get_basedir(), shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE) |
|
43 |
+ (stdout, stderr) = p.communicate() |
|
44 |
+ if p.returncode == 0: |
|
45 |
+ ret.append(stdout.decode("utf-8").rstrip()) |
|
46 |
+ else: |
|
47 |
+ raise AnsibleError("lookup_plugin.pipe(%s) returned %d" % (term, p.returncode)) |
|
48 |
+ return ret |
0 | 49 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,37 @@ |
0 |
+# (c) 2013, Michael DeHaan <michael.dehaan@gmail.com> |
|
1 |
+# |
|
2 |
+# This file is part of Ansible |
|
3 |
+# |
|
4 |
+# Ansible is free software: you can redistribute it and/or modify |
|
5 |
+# it under the terms of the GNU General Public License as published by |
|
6 |
+# the Free Software Foundation, either version 3 of the License, or |
|
7 |
+# (at your option) any later version. |
|
8 |
+# |
|
9 |
+# Ansible is distributed in the hope that it will be useful, |
|
10 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
+# GNU General Public License for more details. |
|
13 |
+# |
|
14 |
+# You should have received a copy of the GNU General Public License |
|
15 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
16 |
+ |
|
17 |
+import random |
|
18 |
+ |
|
19 |
+from ansible.plugins.lookup import LookupBase |
|
20 |
+ |
|
21 |
+# useful for introducing chaos ... or just somewhat reasonably fair selection |
|
22 |
+# amongst available mirrors |
|
23 |
+# |
|
24 |
+# tasks: |
|
25 |
+# - debug: msg=$item |
|
26 |
+# with_random_choice: |
|
27 |
+# - one |
|
28 |
+# - two |
|
29 |
+# - three |
|
30 |
+ |
|
31 |
+class LookupModule(LookupBase): |
|
32 |
+ |
|
33 |
+ def run(self, terms, inject=None, **kwargs): |
|
34 |
+ |
|
35 |
+ return [ random.choice(terms) ] |
|
36 |
+ |
0 | 37 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,73 @@ |
0 |
+# (c) 2012, Jan-Piet Mens <jpmens(at)gmail.com> |
|
1 |
+# |
|
2 |
+# This file is part of Ansible |
|
3 |
+# |
|
4 |
+# Ansible is free software: you can redistribute it and/or modify |
|
5 |
+# it under the terms of the GNU General Public License as published by |
|
6 |
+# the Free Software Foundation, either version 3 of the License, or |
|
7 |
+# (at your option) any later version. |
|
8 |
+# |
|
9 |
+# Ansible is distributed in the hope that it will be useful, |
|
10 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
+# GNU General Public License for more details. |
|
13 |
+# |
|
14 |
+# You should have received a copy of the GNU General Public License |
|
15 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
16 |
+ |
|
17 |
+import os |
|
18 |
+import re |
|
19 |
+ |
|
20 |
+HAVE_REDIS=False |
|
21 |
+try: |
|
22 |
+ import redis # https://github.com/andymccurdy/redis-py/ |
|
23 |
+ HAVE_REDIS=True |
|
24 |
+except ImportError: |
|
25 |
+ pass |
|
26 |
+ |
|
27 |
+from ansible.errors import AnsibleError |
|
28 |
+from ansible.plugins.lookup import LookupBase |
|
29 |
+ |
|
30 |
+# ============================================================== |
|
31 |
+# REDISGET: Obtain value from a GET on a Redis key. Terms |
|
32 |
+# expected: 0 = URL, 1 = Key |
|
33 |
+# URL may be empty, in which case redis://localhost:6379 assumed |
|
34 |
+# -------------------------------------------------------------- |
|
35 |
+ |
|
36 |
+class LookupModule(LookupBase): |
|
37 |
+ |
|
38 |
+ def run(self, terms, variables, **kwargs): |
|
39 |
+ |
|
40 |
+ if not HAVE_REDIS: |
|
41 |
+ raise AnsibleError("Can't LOOKUP(redis_kv): module redis is not installed") |
|
42 |
+ |
|
43 |
+ if not isinstance(terms, list): |
|
44 |
+ terms = [ terms ] |
|
45 |
+ |
|
46 |
+ ret = [] |
|
47 |
+ for term in terms: |
|
48 |
+ (url,key) = term.split(',') |
|
49 |
+ if url == "": |
|
50 |
+ url = 'redis://localhost:6379' |
|
51 |
+ |
|
52 |
+ # urlsplit on Python 2.6.1 is broken. Hmm. Probably also the reason |
|
53 |
+ # Redis' from_url() doesn't work here. |
|
54 |
+ |
|
55 |
+ p = '(?P<scheme>[^:]+)://?(?P<host>[^:/ ]+).?(?P<port>[0-9]*).*' |
|
56 |
+ |
|
57 |
+ try: |
|
58 |
+ m = re.search(p, url) |
|
59 |
+ host = m.group('host') |
|
60 |
+ port = int(m.group('port')) |
|
61 |
+ except AttributeError: |
|
62 |
+ raise AnsibleError("Bad URI in redis lookup") |
|
63 |
+ |
|
64 |
+ try: |
|
65 |
+ conn = redis.Redis(host=host, port=port) |
|
66 |
+ res = conn.get(key) |
|
67 |
+ if res is None: |
|
68 |
+ res = "" |
|
69 |
+ ret.append(res) |
|
70 |
+ except: |
|
71 |
+ ret.append("") # connection failed or key not found |
|
72 |
+ return ret |
0 | 73 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,197 @@ |
0 |
+# (c) 2013, Jayson Vantuyl <jayson@aggressive.ly> |
|
1 |
+# |
|
2 |
+# This file is part of Ansible |
|
3 |
+# |
|
4 |
+# Ansible is free software: you can redistribute it and/or modify |
|
5 |
+# it under the terms of the GNU General Public License as published by |
|
6 |
+# the Free Software Foundation, either version 3 of the License, or |
|
7 |
+# (at your option) any later version. |
|
8 |
+# |
|
9 |
+# Ansible is distributed in the hope that it will be useful, |
|
10 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
+# GNU General Public License for more details. |
|
13 |
+# |
|
14 |
+# You should have received a copy of the GNU General Public License |
|
15 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
16 |
+ |
|
17 |
+from re import compile as re_compile, IGNORECASE |
|
18 |
+ |
|
19 |
+from ansible.errors import * |
|
20 |
+from ansible.parsing.splitter import parse_kv |
|
21 |
+from ansible.plugins.lookup import LookupBase |
|
22 |
+ |
|
23 |
+# shortcut format |
|
24 |
+NUM = "(0?x?[0-9a-f]+)" |
|
25 |
+SHORTCUT = re_compile( |
|
26 |
+ "^(" + # Group 0 |
|
27 |
+ NUM + # Group 1: Start |
|
28 |
+ "-)?" + |
|
29 |
+ NUM + # Group 2: End |
|
30 |
+ "(/" + # Group 3 |
|
31 |
+ NUM + # Group 4: Stride |
|
32 |
+ ")?" + |
|
33 |
+ "(:(.+))?$", # Group 5, Group 6: Format String |
|
34 |
+ IGNORECASE |
|
35 |
+) |
|
36 |
+ |
|
37 |
+ |
|
38 |
+class LookupModule(LookupBase): |
|
39 |
+ """ |
|
40 |
+ sequence lookup module |
|
41 |
+ |
|
42 |
+ Used to generate some sequence of items. Takes arguments in two forms. |
|
43 |
+ |
|
44 |
+ The simple / shortcut form is: |
|
45 |
+ |
|
46 |
+ [start-]end[/stride][:format] |
|
47 |
+ |
|
48 |
+ As indicated by the brackets: start, stride, and format string are all |
|
49 |
+ optional. The format string is in the style of printf. This can be used |
|
50 |
+ to pad with zeros, format in hexadecimal, etc. All of the numerical values |
|
51 |
+ can be specified in octal (i.e. 0664) or hexadecimal (i.e. 0x3f8). |
|
52 |
+ Negative numbers are not supported. |
|
53 |
+ |
|
54 |
+ Some examples: |
|
55 |
+ |
|
56 |
+ 5 -> ["1","2","3","4","5"] |
|
57 |
+ 5-8 -> ["5", "6", "7", "8"] |
|
58 |
+ 2-10/2 -> ["2", "4", "6", "8", "10"] |
|
59 |
+ 4:host%02d -> ["host01","host02","host03","host04"] |
|
60 |
+ |
|
61 |
+ The standard Ansible key-value form is accepted as well. For example: |
|
62 |
+ |
|
63 |
+ start=5 end=11 stride=2 format=0x%02x -> ["0x05","0x07","0x09","0x0a"] |
|
64 |
+ |
|
65 |
+ This format takes an alternate form of "end" called "count", which counts |
|
66 |
+ some number from the starting value. For example: |
|
67 |
+ |
|
68 |
+ count=5 -> ["1", "2", "3", "4", "5"] |
|
69 |
+ start=0x0f00 count=4 format=%04x -> ["0f00", "0f01", "0f02", "0f03"] |
|
70 |
+ start=0 count=5 stride=2 -> ["0", "2", "4", "6", "8"] |
|
71 |
+ start=1 count=5 stride=2 -> ["1", "3", "5", "7", "9"] |
|
72 |
+ |
|
73 |
+ The count option is mostly useful for avoiding off-by-one errors and errors |
|
74 |
+ calculating the number of entries in a sequence when a stride is specified. |
|
75 |
+ """ |
|
76 |
+ |
|
77 |
+ def reset(self): |
|
78 |
+ """set sensible defaults""" |
|
79 |
+ self.start = 1 |
|
80 |
+ self.count = None |
|
81 |
+ self.end = None |
|
82 |
+ self.stride = 1 |
|
83 |
+ self.format = "%d" |
|
84 |
+ |
|
85 |
+ def parse_kv_args(self, args): |
|
86 |
+ """parse key-value style arguments""" |
|
87 |
+ for arg in ["start", "end", "count", "stride"]: |
|
88 |
+ try: |
|
89 |
+ arg_raw = args.pop(arg, None) |
|
90 |
+ if arg_raw is None: |
|
91 |
+ continue |
|
92 |
+ arg_cooked = int(arg_raw, 0) |
|
93 |
+ setattr(self, arg, arg_cooked) |
|
94 |
+ except ValueError: |
|
95 |
+ raise AnsibleError( |
|
96 |
+ "can't parse arg %s=%r as integer" |
|
97 |
+ % (arg, arg_raw) |
|
98 |
+ ) |
|
99 |
+ if 'format' in args: |
|
100 |
+ self.format = args.pop("format") |
|
101 |
+ if args: |
|
102 |
+ raise AnsibleError( |
|
103 |
+ "unrecognized arguments to with_sequence: %r" |
|
104 |
+ % args.keys() |
|
105 |
+ ) |
|
106 |
+ |
|
107 |
+ def parse_simple_args(self, term): |
|
108 |
+ """parse the shortcut forms, return True/False""" |
|
109 |
+ match = SHORTCUT.match(term) |
|
110 |
+ if not match: |
|
111 |
+ return False |
|
112 |
+ |
|
113 |
+ _, start, end, _, stride, _, format = match.groups() |
|
114 |
+ |
|
115 |
+ if start is not None: |
|
116 |
+ try: |
|
117 |
+ start = int(start, 0) |
|
118 |
+ except ValueError: |
|
119 |
+ raise AnsibleError("can't parse start=%s as integer" % start) |
|
120 |
+ if end is not None: |
|
121 |
+ try: |
|
122 |
+ end = int(end, 0) |
|
123 |
+ except ValueError: |
|
124 |
+ raise AnsibleError("can't parse end=%s as integer" % end) |
|
125 |
+ if stride is not None: |
|
126 |
+ try: |
|
127 |
+ stride = int(stride, 0) |
|
128 |
+ except ValueError: |
|
129 |
+ raise AnsibleError("can't parse stride=%s as integer" % stride) |
|
130 |
+ |
|
131 |
+ if start is not None: |
|
132 |
+ self.start = start |
|
133 |
+ if end is not None: |
|
134 |
+ self.end = end |
|
135 |
+ if stride is not None: |
|
136 |
+ self.stride = stride |
|
137 |
+ if format is not None: |
|
138 |
+ self.format = format |
|
139 |
+ |
|
140 |
+ def sanity_check(self): |
|
141 |
+ if self.count is None and self.end is None: |
|
142 |
+ raise AnsibleError( |
|
143 |
+ "must specify count or end in with_sequence" |
|
144 |
+ ) |
|
145 |
+ elif self.count is not None and self.end is not None: |
|
146 |
+ raise AnsibleError( |
|
147 |
+ "can't specify both count and end in with_sequence" |
|
148 |
+ ) |
|
149 |
+ elif self.count is not None: |
|
150 |
+ # convert count to end |
|
151 |
+ self.end = self.start + self.count * self.stride - 1 |
|
152 |
+ del self.count |
|
153 |
+ if self.end < self.start: |
|
154 |
+ raise AnsibleError("can't count backwards") |
|
155 |
+ if self.format.count('%') != 1: |
|
156 |
+ raise AnsibleError("bad formatting string: %s" % self.format) |
|
157 |
+ |
|
158 |
+ def generate_sequence(self): |
|
159 |
+ numbers = xrange(self.start, self.end + 1, self.stride) |
|
160 |
+ |
|
161 |
+ for i in numbers: |
|
162 |
+ try: |
|
163 |
+ formatted = self.format % i |
|
164 |
+ yield formatted |
|
165 |
+ except (ValueError, TypeError): |
|
166 |
+ raise AnsibleError( |
|
167 |
+ "problem formatting %r with %r" % self.format |
|
168 |
+ ) |
|
169 |
+ |
|
170 |
+ def run(self, terms, variables, **kwargs): |
|
171 |
+ results = [] |
|
172 |
+ |
|
173 |
+ if isinstance(terms, basestring): |
|
174 |
+ terms = [ terms ] |
|
175 |
+ |
|
176 |
+ for term in terms: |
|
177 |
+ try: |
|
178 |
+ self.reset() # clear out things for this iteration |
|
179 |
+ |
|
180 |
+ try: |
|
181 |
+ if not self.parse_simple_args(term): |
|
182 |
+ self.parse_kv_args(parse_kv(term)) |
|
183 |
+ except Exception, e: |
|
184 |
+ raise AnsibleError("unknown error parsing with_sequence arguments: %r" % term) |
|
185 |
+ |
|
186 |
+ self.sanity_check() |
|
187 |
+ |
|
188 |
+ results.extend(self.generate_sequence()) |
|
189 |
+ except AnsibleError: |
|
190 |
+ raise |
|
191 |
+ except Exception: |
|
192 |
+ raise AnsibleError( |
|
193 |
+ "unknown error generating sequence" |
|
194 |
+ ) |
|
195 |
+ |
|
196 |
+ return results |
0 | 197 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,59 @@ |
0 |
+# (c) 2013, Serge van Ginderachter <serge@vanginderachter.be> |
|
1 |
+# |
|
2 |
+# This file is part of Ansible |
|
3 |
+# |
|
4 |
+# Ansible is free software: you can redistribute it and/or modify |
|
5 |
+# it under the terms of the GNU General Public License as published by |
|
6 |
+# the Free Software Foundation, either version 3 of the License, or |
|
7 |
+# (at your option) any later version. |
|
8 |
+# |
|
9 |
+# Ansible is distributed in the hope that it will be useful, |
|
10 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
+# GNU General Public License for more details. |
|
13 |
+# |
|
14 |
+# You should have received a copy of the GNU General Public License |
|
15 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
16 |
+ |
|
17 |
+from ansible.errors import * |
|
18 |
+from ansible.plugins.lookup import LookupBase |
|
19 |
+from ansible.utils.listify import listify_lookup_plugin_terms |
|
20 |
+ |
|
21 |
+class LookupModule(LookupBase): |
|
22 |
+ |
|
23 |
+ def run(self, terms, variables, **kwargs): |
|
24 |
+ |
|
25 |
+ terms[0] = listify_lookup_plugin_terms(terms[0], variables, loader=self._loader) |
|
26 |
+ |
|
27 |
+ if not isinstance(terms, list) or not len(terms) == 2: |
|
28 |
+ raise AnsibleError("subelements lookup expects a list of two items, first a dict or a list, and second a string") |
|
29 |
+ |
|
30 |
+ if isinstance(terms[0], dict): # convert to list: |
|
31 |
+ if terms[0].get('skipped',False) != False: |
|
32 |
+ # the registered result was completely skipped |
|
33 |
+ return [] |
|
34 |
+ elementlist = [] |
|
35 |
+ for key in terms[0].iterkeys(): |
|
36 |
+ elementlist.append(terms[0][key]) |
|
37 |
+ else: |
|
38 |
+ elementlist = terms[0] |
|
39 |
+ |
|
40 |
+ subelement = terms[1] |
|
41 |
+ |
|
42 |
+ ret = [] |
|
43 |
+ for item0 in elementlist: |
|
44 |
+ if not isinstance(item0, dict): |
|
45 |
+ raise AnsibleError("subelements lookup expects a dictionary, got '%s'" %item0) |
|
46 |
+ if item0.get('skipped', False) != False: |
|
47 |
+ # this particular item is to be skipped |
|
48 |
+ continue |
|
49 |
+ if not subelement in item0: |
|
50 |
+ raise AnsibleError("could not find '%s' key in iterated item '%s'" % (subelement, item0)) |
|
51 |
+ if not isinstance(item0[subelement], list): |
|
52 |
+ raise AnsibleError("the key %s should point to a list, got '%s'" % (subelement, item0[subelement])) |
|
53 |
+ sublist = item0.pop(subelement, []) |
|
54 |
+ for item1 in sublist: |
|
55 |
+ ret.append((item0, item1)) |
|
56 |
+ |
|
57 |
+ return ret |
|
58 |
+ |
0 | 59 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,43 @@ |
0 |
+# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> |
|
1 |
+# |
|
2 |
+# This file is part of Ansible |
|
3 |
+# |
|
4 |
+# Ansible is free software: you can redistribute it and/or modify |
|
5 |
+# it under the terms of the GNU General Public License as published by |
|
6 |
+# the Free Software Foundation, either version 3 of the License, or |
|
7 |
+# (at your option) any later version. |
|
8 |
+# |
|
9 |
+# Ansible is distributed in the hope that it will be useful, |
|
10 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
+# GNU General Public License for more details. |
|
13 |
+# |
|
14 |
+# You should have received a copy of the GNU General Public License |
|
15 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
16 |
+ |
|
17 |
+import os |
|
18 |
+ |
|
19 |
+from ansible.errors import AnsibleError |
|
20 |
+from ansible.plugins.lookup import LookupBase |
|
21 |
+from ansible.template import Templar |
|
22 |
+ |
|
23 |
+class LookupModule(LookupBase): |
|
24 |
+ |
|
25 |
+ def run(self, terms, variables, **kwargs): |
|
26 |
+ |
|
27 |
+ if not isinstance(terms, list): |
|
28 |
+ terms = [ terms ] |
|
29 |
+ |
|
30 |
+ templar = Templar(loader=self._loader, variables=variables) |
|
31 |
+ |
|
32 |
+ ret = [] |
|
33 |
+ for term in terms: |
|
34 |
+ path = self._loader.path_dwim(term) |
|
35 |
+ if os.path.exists(path): |
|
36 |
+ with open(path, 'r') as f: |
|
37 |
+ template_data = f.read() |
|
38 |
+ res = templar.template(template_data, preserve_trailing_newlines=True) |
|
39 |
+ ret.append(res) |
|
40 |
+ else: |
|
41 |
+ raise AnsibleError("the template file %s could not be found for the lookup" % term) |
|
42 |
+ return ret |
0 | 43 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,48 @@ |
0 |
+# (c) 2013, Bradley Young <young.bradley@gmail.com> |
|
1 |
+# |
|
2 |
+# This file is part of Ansible |
|
3 |
+# |
|
4 |
+# Ansible is free software: you can redistribute it and/or modify |
|
5 |
+# it under the terms of the GNU General Public License as published by |
|
6 |
+# the Free Software Foundation, either version 3 of the License, or |
|
7 |
+# (at your option) any later version. |
|
8 |
+# |
|
9 |
+# Ansible is distributed in the hope that it will be useful, |
|
10 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
+# GNU General Public License for more details. |
|
13 |
+# |
|
14 |
+# You should have received a copy of the GNU General Public License |
|
15 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
16 |
+ |
|
17 |
+from itertools import izip_longest |
|
18 |
+ |
|
19 |
+from ansible.errors import * |
|
20 |
+from ansible.plugins.lookup import LookupBase |
|
21 |
+from ansible.utils.listify import listify_lookup_plugin_terms |
|
22 |
+ |
|
23 |
+class LookupModule(LookupBase): |
|
24 |
+ """ |
|
25 |
+ Transpose a list of arrays: |
|
26 |
+ [1, 2, 3], [4, 5, 6] -> [1, 4], [2, 5], [3, 6] |
|
27 |
+ Replace any empty spots in 2nd array with None: |
|
28 |
+ [1, 2], [3] -> [1, 3], [2, None] |
|
29 |
+ """ |
|
30 |
+ |
|
31 |
+ def __lookup_variabless(self, terms, variables): |
|
32 |
+ results = [] |
|
33 |
+ for x in terms: |
|
34 |
+ intermediate = listify_lookup_plugin_terms(x, variables) |
|
35 |
+ results.append(intermediate) |
|
36 |
+ return results |
|
37 |
+ |
|
38 |
+ def run(self, terms, variables=None, **kwargs): |
|
39 |
+ |
|
40 |
+ terms = self.__lookup_variabless(terms, variables) |
|
41 |
+ |
|
42 |
+ my_list = terms[:] |
|
43 |
+ if len(my_list) == 0: |
|
44 |
+ raise errors.AnsibleError("with_together requires at least one element in each list") |
|
45 |
+ |
|
46 |
+ return [self._flatten(x) for x in izip_longest(*my_list, fillvalue=None)] |
|
47 |
+ |
... | ... |
@@ -116,7 +116,7 @@ class StrategyBase: |
116 | 116 |
self._cur_worker = 0 |
117 | 117 |
|
118 | 118 |
self._pending_results += 1 |
119 |
- main_q.put((host, new_task, task_vars, connection_info), block=False) |
|
119 |
+ main_q.put((host, new_task, self._loader.get_basedir(), task_vars, connection_info), block=False) |
|
120 | 120 |
except (EOFError, IOError, AssertionError), e: |
121 | 121 |
# most likely an abort |
122 | 122 |
debug("got an error while queuing: %s" % e) |
... | ... |
@@ -41,8 +41,9 @@ class Templar: |
41 | 41 |
The main class for templating, with the main entry-point of template(). |
42 | 42 |
''' |
43 | 43 |
|
44 |
- def __init__(self, basedir=None, variables=dict(), fail_on_undefined=C.DEFAULT_UNDEFINED_VAR_BEHAVIOR): |
|
45 |
- self._basedir = basedir |
|
44 |
+ def __init__(self, loader, variables=dict(), fail_on_undefined=C.DEFAULT_UNDEFINED_VAR_BEHAVIOR): |
|
45 |
+ self._loader = loader |
|
46 |
+ self._basedir = loader.get_basedir() |
|
46 | 47 |
self._filters = None |
47 | 48 |
self._available_variables = variables |
48 | 49 |
|
... | ... |
@@ -180,15 +181,17 @@ class Templar: |
180 | 180 |
return thing if thing is not None else '' |
181 | 181 |
|
182 | 182 |
def _lookup(self, name, *args, **kwargs): |
183 |
- instance = lookup_loader.get(name.lower(), basedir=kwargs.get('basedir',None)) |
|
183 |
+ instance = lookup_loader.get(name.lower(), loader=self._loader) |
|
184 | 184 |
|
185 | 185 |
if instance is not None: |
186 | 186 |
# safely catch run failures per #5059 |
187 | 187 |
try: |
188 |
- ran = instance.run(*args, inject=self._available_vars, **kwargs) |
|
188 |
+ ran = instance.run(*args, variables=self._available_variables, **kwargs) |
|
189 | 189 |
except AnsibleUndefinedVariable: |
190 | 190 |
raise |
191 | 191 |
except Exception, e: |
192 |
+ if self._fail_on_lookup_errors: |
|
193 |
+ raise |
|
192 | 194 |
ran = None |
193 | 195 |
if ran: |
194 | 196 |
ran = ",".join(ran) |
195 | 197 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,46 @@ |
0 |
+# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> |
|
1 |
+# |
|
2 |
+# This file is part of Ansible |
|
3 |
+# |
|
4 |
+# Ansible is free software: you can redistribute it and/or modify |
|
5 |
+# it under the terms of the GNU General Public License as published by |
|
6 |
+# the Free Software Foundation, either version 3 of the License, or |
|
7 |
+# (at your option) any later version. |
|
8 |
+# |
|
9 |
+# Ansible is distributed in the hope that it will be useful, |
|
10 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
+# GNU General Public License for more details. |
|
13 |
+# |
|
14 |
+# You should have received a copy of the GNU General Public License |
|
15 |
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
|
16 |
+ |
|
17 |
+PASSLIB_AVAILABLE = False |
|
18 |
+try: |
|
19 |
+ import passlib.hash |
|
20 |
+ PASSLIB_AVAILABLE = True |
|
21 |
+except: |
|
22 |
+ pass |
|
23 |
+ |
|
24 |
+from ansible.errors import AnsibleError |
|
25 |
+ |
|
26 |
+__all__ = ['do_encrypt'] |
|
27 |
+ |
|
28 |
+def do_encrypt(result, encrypt, salt_size=None, salt=None): |
|
29 |
+ if PASSLIB_AVAILABLE: |
|
30 |
+ try: |
|
31 |
+ crypt = getattr(passlib.hash, encrypt) |
|
32 |
+ except: |
|
33 |
+ raise AnsibleError("passlib does not support '%s' algorithm" % encrypt) |
|
34 |
+ |
|
35 |
+ if salt_size: |
|
36 |
+ result = crypt.encrypt(result, salt_size=salt_size) |
|
37 |
+ elif salt: |
|
38 |
+ result = crypt.encrypt(result, salt=salt) |
|
39 |
+ else: |
|
40 |
+ result = crypt.encrypt(result) |
|
41 |
+ else: |
|
42 |
+ raise AnsibleError("passlib must be installed to encrypt vars_prompt values") |
|
43 |
+ |
|
44 |
+ return result |
|
45 |
+ |
... | ... |
@@ -30,7 +30,7 @@ __all__ = ['listify_lookup_plugin_terms'] |
30 | 30 |
|
31 | 31 |
LOOKUP_REGEX = re.compile(r'lookup\s*\(') |
32 | 32 |
|
33 |
-def listify_lookup_plugin_terms(terms, variables): |
|
33 |
+def listify_lookup_plugin_terms(terms, variables, loader): |
|
34 | 34 |
|
35 | 35 |
if isinstance(terms, basestring): |
36 | 36 |
# someone did: |
... | ... |
@@ -46,7 +46,7 @@ def listify_lookup_plugin_terms(terms, variables): |
46 | 46 |
# if not already a list, get ready to evaluate with Jinja2 |
47 | 47 |
# not sure why the "/" is in above code :) |
48 | 48 |
try: |
49 |
- templar = Templar(variables=variables) |
|
49 |
+ templar = Templar(loader=loader, variables=variables) |
|
50 | 50 |
new_terms = templar.template("{{ %s }}" % terms) |
51 | 51 |
if isinstance(new_terms, basestring) and "{{" in new_terms: |
52 | 52 |
pass |
... | ... |
@@ -41,8 +41,6 @@ class VariableManager: |
41 | 41 |
self._host_vars_files = defaultdict(dict) |
42 | 42 |
self._group_vars_files = defaultdict(dict) |
43 | 43 |
|
44 |
- self._templar = Templar() |
|
45 |
- |
|
46 | 44 |
def _get_cache_entry(self, play=None, host=None, task=None): |
47 | 45 |
play_id = "NONE" |
48 | 46 |
if play: |
... | ... |
@@ -156,10 +154,10 @@ class VariableManager: |
156 | 156 |
|
157 | 157 |
if play: |
158 | 158 |
all_vars = self._merge_dicts(all_vars, play.get_vars()) |
159 |
+ templar = Templar(loader=loader, variables=all_vars) |
|
159 | 160 |
for vars_file in play.get_vars_files(): |
160 |
- self._templar.set_available_variables(all_vars) |
|
161 | 161 |
try: |
162 |
- vars_file = self._templar.template(vars_file) |
|
162 |
+ vars_file = templar.template(vars_file) |
|
163 | 163 |
data = loader.load_from_file(vars_file) |
164 | 164 |
all_vars = self._merge_dicts(all_vars, data) |
165 | 165 |
except: |
0 | 5 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,7 @@ |
0 |
+- hosts: localhost |
|
1 |
+ gather_facts: no |
|
2 |
+ #vars: |
|
3 |
+ # my_password: "{{ lookup('password', '/tmp/test_lookup_password length=15') }}" |
|
4 |
+ tasks: |
|
5 |
+ #- debug: msg="the password is {{my_password}}" |
|
6 |
+ - debug: msg="the password is {{ lookup('password', '/tmp/test_lookup_password length=15') }}" |
0 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,15 @@ |
0 |
+- hosts: localhost |
|
1 |
+ connection: local |
|
2 |
+ gather_facts: no |
|
3 |
+ vars: |
|
4 |
+ users: |
|
5 |
+ alice: |
|
6 |
+ name: Alice Appleworth |
|
7 |
+ telephone: 123-456-7890 |
|
8 |
+ bob: |
|
9 |
+ name: Bob Bananarama |
|
10 |
+ telephone: 987-654-3210 |
|
11 |
+ tasks: |
|
12 |
+ - name: Print phone records |
|
13 |
+ debug: msg="User {{ item.key }} is {{ item.value.name }} ({{ item.value.telephone }})" |
|
14 |
+ with_dict: users |
0 | 10 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,13 @@ |
0 |
+- hosts: localhost |
|
1 |
+ connection: local |
|
2 |
+ gather_facts: no |
|
3 |
+ tasks: |
|
4 |
+ |
|
5 |
+ - debug: msg="name={{ item }} state=present groups=evens" |
|
6 |
+ with_sequence: start=0 end=32 format=testuser%02x |
|
7 |
+ |
|
8 |
+ - debug: msg="dest=/var/stuff/{{ item }} state=directory" |
|
9 |
+ with_sequence: start=4 end=16 stride=2 |
|
10 |
+ |
|
11 |
+ - debug: msg="name=group{{ item }} state=present" |
|
12 |
+ with_sequence: count=4 |
0 | 13 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,18 @@ |
0 |
+- hosts: localhost |
|
1 |
+ connection: local |
|
2 |
+ gather_facts: no |
|
3 |
+ vars: |
|
4 |
+ users: |
|
5 |
+ - name: alice |
|
6 |
+ authorized: |
|
7 |
+ - /tmp/alice/onekey.pub |
|
8 |
+ - /tmp/alice/twokey.pub |
|
9 |
+ - name: bob |
|
10 |
+ authorized: |
|
11 |
+ - /tmp/bob/id_rsa.pub |
|
12 |
+ |
|
13 |
+ tasks: |
|
14 |
+ - debug: msg="user={{ item.0.name }} key='{{ item.1 }}'" |
|
15 |
+ with_subelements: |
|
16 |
+ - users |
|
17 |
+ - authorized |