Browse code

Moving over all lookup plugins to v2

James Cammarata authored on 2015/01/10 00:37:31
Showing 54 changed files
... ...
@@ -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:
166 166
new file mode 100644
... ...
@@ -0,0 +1,5 @@
0
+- hosts: localhost
1
+  connection: local
2
+  gather_facts: no
3
+  tasks:
4
+  - debug: msg="the pubkey is {{lookup('file', '~/.ssh/id_rsa.pub')}}"
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 7
new file mode 100644
... ...
@@ -0,0 +1,4 @@
0
+- hosts: localhost
1
+  gather_facts: no
2
+  tasks:
3
+  - debug: msg="the date is {{ lookup('pipe', 'date') }}"
0 4
new file mode 100644
... ...
@@ -0,0 +1,7 @@
0
+- hosts: localhost
1
+  gather_facts: no
2
+  vars:
3
+    my_var: "Bazinga!"
4
+  tasks:
5
+  - debug: msg="the rendered template is {{ lookup('template', 'template.j2') }}"
6
+
0 7
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+the variable is {{my_var}}
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 15
new file mode 100644
... ...
@@ -0,0 +1,5 @@
0
+- hosts: localhost
1
+  connection: local
2
+  gather_facts: no
3
+  tasks:
4
+  - debug: msg="{{ lookup('env','HOME') }} is an environment variable"
0 5
new file mode 100644
... ...
@@ -0,0 +1,7 @@
0
+- hosts: localhost
1
+  connection: local
2
+  gather_facts: no
3
+  tasks:
4
+  - debug: msg="file is {{item}}"
5
+    with_fileglob:
6
+    - "*.yml"
0 7
new file mode 100644
... ...
@@ -0,0 +1,10 @@
0
+- hosts: localhost
1
+  connection: local
2
+  gather_facts: no
3
+  tasks:
4
+  - debug: msg="file is {{item}}"
5
+    with_first_found:
6
+    - /etc/foo
7
+    - /etc/bar
8
+    - /etc/passwd
9
+    - /etc/shadow
0 10
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+- hosts: localhost
1
+  connection: local
2
+  gather_facts:
3
+  vars:
4
+    list_a:
5
+    - ['foo', 'bar']
6
+    list_b:
7
+    - [['bam', 'baz']]
8
+  tasks:
9
+  - debug: msg="item is {{item}}"
10
+    with_flattened:
11
+    - list_a
12
+    - list_b
0 13
new file mode 100644
... ...
@@ -0,0 +1,11 @@
0
+- hosts: localhost
1
+  connection: local
2
+  gather_facts: no
3
+  vars:
4
+    some_list:
5
+    - a
6
+    - b
7
+    - c
8
+  tasks:
9
+  - debug: msg="at array position {{ item.0 }} there is a value {{ item.1 }}"
10
+    with_indexed_items: some_list
0 11
new file mode 100644
... ...
@@ -0,0 +1,6 @@
0
+- hosts: localhost
1
+  gather_facts: no
2
+  tasks:
3
+  - debug: msg="line is {{item}}"
4
+    with_lines:
5
+    - "cat /etc/hosts"
0 6
new file mode 100644
... ...
@@ -0,0 +1,10 @@
0
+- hosts: localhost
1
+  connection: local
2
+  gather_facts: no
3
+  tasks:
4
+  - debug: msg={{ item }}
5
+    with_random_choice:
6
+    - "go through the door"
7
+    - "drink from the goblet"
8
+    - "press the red button"
9
+    - "do nothing"
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
0 18
new file mode 100644
... ...
@@ -0,0 +1,11 @@
0
+- hosts: localhost
1
+  connection: local
2
+  gather_facts: no
3
+  vars:
4
+    alpha: [ 'a', 'b', 'c', 'd' ]
5
+    numbers:  [ 1, 2, 3, 4 ]
6
+  tasks:
7
+  - debug: msg="{{ item.0 }} and {{ item.1 }}"
8
+    with_together:
9
+    - alpha
10
+    - numbers