├── .gitignore ├── README.md ├── bin └── ansible-inventory ├── callback_plugins ├── human_log.py └── timestamp.py ├── dumpall.j2 ├── examples └── jvspherecontrol.yml ├── filter_plugins └── hash.py ├── library ├── README.md ├── check_process ├── dpkg_reconfigure ├── jvspherecontrol ├── known_host └── seds ├── lookup_plugins ├── hash_items.py └── hashrays.py └── vars_plugins ├── README.md └── group_vars_dirs.py /.gitignore: -------------------------------------------------------------------------------- 1 | # build byproducts 2 | *.py[co] 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ansible-plugins 2 | =============== 3 | 4 | My custom modules for ansible (https://github.com/ansible/ansible) configuration management: 5 | a multi-node deployment/orchestration, and remote task execution system. 6 | 7 | This repo is ordered by the subdirectories as mentioned in ansible's lib/ansible/utils/plugins.py 8 | ```python 9 | action_loader = PluginLoader('ActionModule', 'ansible.runner.action_plugins', C.DEFAULT_ACTION_PLUGIN_PATH, 'action_plugins') 10 | callback_loader = PluginLoader('CallbackModule', 'ansible.callback_plugins', C.DEFAULT_CALLBACK_PLUGIN_PATH, 'callback_plugins') 11 | connection_loader = PluginLoader('Connection', 'ansible.runner.connection_plugins', C.DEFAULT_CONNECTION_PLUGIN_PATH, 'connection_plugins', aliases={'paramiko': 'paramiko_ssh'}) 12 | module_finder = PluginLoader('', '', C.DEFAULT_MODULE_PATH, 'library') 13 | lookup_loader = PluginLoader('LookupModule', 'ansible.runner.lookup_plugins', C.DEFAULT_LOOKUP_PLUGIN_PATH, 'lookup_plugins') 14 | vars_loader = PluginLoader('VarsModule', 'ansible.inventory.vars_plugins', C.DEFAULT_VARS_PLUGIN_PATH, 'vars_plugins') 15 | filter_loader = PluginLoader('FilterModule', 'ansible.runner.filter_plugins', C.DEFAULT_FILTER_PLUGIN_PATH, 'filter_plugins') 16 | ``` 17 | 18 | + `action_plugins/` 19 | + `callback_plugins/` 20 | + `connection_plugins/` 21 | + `library/` (action modules, those which get transferred and executed to the target host) 22 | + `lookup_plugins/` 23 | + `vars_plugins/` 24 | + `filter_plugins/` 25 | + `inventory_plugins` (No part of ansible's base code; [as described in ansible's api docs](https://github.com/ansible/ansible/blob/devel/docsite/latest/rst/api.rst#external-inventory-scripts)) 26 | 27 | contributing 28 | ============ 29 | 30 | If you'd like to contribute your modules, or patch existent modules, please send a pull request. 31 | Be sure however 32 | * to send your pull request from a **feature branch**, derived from the `devel` branch 33 | * one (set of) plugin file(s) per commit only! 34 | * e.g. a set: action_plugins and library/ modules form a pair 35 | 36 | -------------------------------------------------------------------------------- /bin/ansible-inventory: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # (c) 2013-2016 Serge van Ginderachter 4 | # 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 . 18 | 19 | 20 | 21 | from __future__ import (absolute_import, print_function) 22 | __metaclass__ = type 23 | 24 | __requires__ = ['ansible'] 25 | try: 26 | import pkg_resources 27 | except Exception: 28 | # Use pkg_resources to find the correct versions of libraries and set 29 | # sys.path appropriately when there are multiversion installs. But we 30 | # have code that better expresses the errors in the places where the code 31 | # is actually used (the deps are optional for many code paths) so we don't 32 | # want to fail here. 33 | pass 34 | 35 | import os 36 | import sys 37 | import traceback 38 | import yaml 39 | 40 | from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleParserError 41 | from ansible.utils.display import Display 42 | 43 | ######################################################## 44 | from ansible import constants as C 45 | from ansible.cli import CLI 46 | from ansible.errors import AnsibleOptionsError 47 | from ansible.inventory import Inventory 48 | from ansible.parsing.dataloader import DataLoader 49 | from ansible.parsing.splitter import parse_kv 50 | from ansible.utils.vars import load_extra_vars 51 | from ansible.vars import VariableManager 52 | from ansible.parsing.utils.jsonify import jsonify 53 | ######################################################## 54 | 55 | class InventoryCLI(CLI): 56 | ''' code behind ansible-inventory cli''' 57 | 58 | def parse(self): 59 | ''' create an options parser for bin/ansible ''' 60 | 61 | self.parser = CLI.base_parser( 62 | usage='%prog [host-pattern] [options]', 63 | inventory_opts=True, 64 | runtask_opts=True, 65 | fork_opts=True, 66 | vault_opts=True 67 | ) 68 | 69 | # options unique to ansible-inventory 70 | # dump 71 | self.parser.add_option('-y', '--yaml', dest='yaml', action='store_true', 72 | help="dump variables in yaml format") 73 | self.parser.add_option('-j', '--json', '--list', dest='json', action='store_true', 74 | help="dump inventory in InventoryScript json format") 75 | self.parser.add_option('-p', '--pretty', dest='pretty', action='store_true', 76 | help="dump inventory in pretty InventoryScript json format") 77 | self.parser.add_option('-m', '--merge', dest='merge', action='store_true', 78 | help="when dumping, merge host and group variables into host variables") 79 | self.parser.add_option('--host', dest='host', action='store_true', 80 | help="n/a for backwards compatibility as ansible inventory script") 81 | # tree 82 | self.parser.add_option('-t', '--tree', dest='tree', action='store_true', 83 | help="print a tree/forest view of the inventory") 84 | self.parser.add_option('-n', '--nodes', dest='nodes', action='store_true', 85 | help="also show nodes when printing the inventory tree") 86 | self.parser.add_option('-d', '--depth', dest='depth', 87 | help="limit the inventory tree to this given depth") 88 | 89 | self.options, self.args = self.parser.parse_args() 90 | 91 | # need one of these options at least 92 | if not (self.options.yaml or 93 | self.options.json or 94 | self.options.pretty or 95 | self.options.host or 96 | self.options.tree 97 | ) or len(self.args) > 1: 98 | self.parser.print_help() 99 | sys.exit(1) 100 | # only 1 arg needed, save it as an option 101 | if len(self.args) == 0: 102 | self.options.pattern = 'all' 103 | else: 104 | self.options.pattern = self.args[0] 105 | if self.options.pretty: 106 | self.options.json = True 107 | 108 | display.verbosity = self.options.verbosity 109 | self.validate_conflicts(vault_opts=True, fork_opts=True) 110 | 111 | return True 112 | 113 | 114 | def run(self): 115 | ''' use Runner lib to do SSH things ''' 116 | 117 | super(InventoryCLI, self).run() 118 | 119 | vault_pass = None 120 | 121 | if self.options.vault_password_file: 122 | # read vault_pass from a file 123 | vault_pass = CLI.read_vault_password_file(self.options.vault_password_file) 124 | elif self.options.ask_vault_pass: 125 | vault_pass = self.ask_vault_passwords(ask_vault_pass=True, ask_new_vault_pass=False, confirm_new=False)[0] 126 | 127 | loader = DataLoader() 128 | variable_manager = VariableManager() 129 | 130 | self.inventory = Inventory(loader=loader, variable_manager=variable_manager, host_list=self.options.inventory) 131 | variable_manager.set_inventory(self.inventory) 132 | 133 | hosts = self.inventory.list_hosts(self.options.pattern) 134 | if len(hosts) == 0: 135 | display.warning("provided hosts list is empty, only localhost is available") 136 | 137 | if self.options.listhosts: 138 | display.display(' hosts (%d):' % len(hosts)) 139 | for host in hosts: 140 | display.display(' %s' % host) 141 | return 0 142 | 143 | if self.options.tree: 144 | C.DEFAULT_CALLBACK_WHITELIST.append('tree') 145 | C.TREE_DIR = self.options.tree 146 | 147 | if self.options.host: 148 | print ('{}') 149 | elif self.options.json or cli.options.yaml: 150 | results = cli.dump() 151 | elif self.options.tree: 152 | results = cli.tree() 153 | 154 | return 0 155 | 156 | 157 | def dump(self): 158 | 159 | results = {'_meta': {'hostvars': {}}} 160 | 161 | # get inventory groups, their vars, children and hosts into result set 162 | groups = self.inventory.get_groups() 163 | for group in groups.values(): 164 | host_list = [h.name for h in group.hosts ] 165 | child_groups_list = [g.name for g in group.child_groups ] 166 | results[group.name] = dict(children=child_groups_list, hosts=host_list) 167 | # get groupvars into result set 168 | if not self.options.merge: 169 | results[group.name]['vars'] = group.get_vars() 170 | 171 | # get hostvars into result set 172 | hosts = self.inventory.get_hosts() 173 | for host in hosts: 174 | if not self.options.merge: 175 | results['_meta']['hostvars'][host.name] = host.vars 176 | else: 177 | results['_meta']['hostvars'][host.name] = host.get_vars() 178 | 179 | 180 | if self.options.yaml: 181 | print (yaml.safe_dump(results)) 182 | else: 183 | print (jsonify(results, format=self.options.pretty)) 184 | 185 | return results 186 | 187 | # ---------------------------------------------- 188 | 189 | def tree(self): 190 | 191 | if self.options.depth: 192 | maxdepth = int(self.options.depth) 193 | else: 194 | maxdepth = None 195 | 196 | def print_depth(name, depth, extra=""): 197 | 198 | if depth != 0: 199 | tree = "| " * (depth-1) + "|--" 200 | else: 201 | tree = "" 202 | print ("%s %s %s" % (tree, name, extra)) 203 | 204 | 205 | def print_group_tree(group, maxdepth): 206 | 207 | if maxdepth is None or group.depth <= maxdepth: 208 | parent_groups_names = [ '%s(%s)' % (x.name, x.depth) for x in group.parent_groups ] 209 | groups_hosts_names = [h.name for h in group.hosts] 210 | 211 | print_depth(group.name, group.depth) 212 | for child in group.child_groups: 213 | print_group_tree(child, maxdepth) 214 | if self.options.nodes and len(groups_hosts_names) > 0: 215 | for h in groups_hosts_names: 216 | print_depth(h+'*', group.depth+1) 217 | 218 | basegroup = self.inventory.get_group(self.options.pattern) 219 | print_group_tree(basegroup, maxdepth) 220 | 221 | 222 | ############################################################################### 223 | 224 | ######################################## 225 | ### OUTPUT OF LAST RESORT ### 226 | class LastResort(object): 227 | def display(self, msg): 228 | print(msg, file=sys.stderr) 229 | 230 | def error(self, msg, wrap_text=None): 231 | print(msg, file=sys.stderr) 232 | 233 | 234 | ######################################## 235 | 236 | if __name__ == '__main__': 237 | 238 | display = LastResort() 239 | cli = None 240 | me = os.path.basename(sys.argv[0]) 241 | 242 | try: 243 | display = Display() 244 | cli = InventoryCLI(sys.argv) 245 | cli.parse() 246 | sys.exit(cli.run()) 247 | 248 | except AnsibleOptionsError as e: 249 | cli.parser.print_help() 250 | display.error(str(e), wrap_text=False) 251 | sys.exit(5) 252 | except AnsibleParserError as e: 253 | display.error(str(e), wrap_text=False) 254 | sys.exit(4) 255 | # TQM takes care of these, but leaving comment to reserve the exit codes 256 | # except AnsibleHostUnreachable as e: 257 | # display.error(str(e)) 258 | # sys.exit(3) 259 | # except AnsibleHostFailed as e: 260 | # display.error(str(e)) 261 | # sys.exit(2) 262 | except AnsibleError as e: 263 | display.error(str(e), wrap_text=False) 264 | sys.exit(1) 265 | except KeyboardInterrupt: 266 | display.error("User interrupted execution") 267 | sys.exit(99) 268 | except Exception as e: 269 | have_cli_options = cli is not None and cli.options is not None 270 | display.error("Unexpected Exception: %s" % str(e), wrap_text=False) 271 | if not have_cli_options or have_cli_options and cli.options.verbosity > 2: 272 | display.display("the full traceback was:\n\n%s" % traceback.format_exc()) 273 | else: 274 | display.display("to see the full traceback, use -vvv") 275 | sys.exit(250) 276 | -------------------------------------------------------------------------------- /callback_plugins/human_log.py: -------------------------------------------------------------------------------- 1 | # source: https://gist.github.com/cliffano/9868180 2 | # info: # http://blog.cliffano.com/2014/04/06/human-readable-ansible-playbook-log-output-using-callback-plugin/ 3 | 4 | FIELDS = ['cmd', 'command', 'start', 'end', 'delta', 'msg', 'stdout', 'stderr'] 5 | 6 | def human_log(res): 7 | 8 | if type(res) == type(dict()): 9 | for field in FIELDS: 10 | if field in res.keys(): 11 | print '\n{0}:\n{1}'.format(field, res[field]) 12 | 13 | class CallbackModule(object): 14 | 15 | def on_any(self, *args, **kwargs): 16 | pass 17 | 18 | def runner_on_failed(self, host, res, ignore_errors=False): 19 | human_log(res) 20 | 21 | def runner_on_ok(self, host, res): 22 | human_log(res) 23 | 24 | def runner_on_error(self, host, msg): 25 | pass 26 | 27 | def runner_on_skipped(self, host, item=None): 28 | pass 29 | 30 | def runner_on_unreachable(self, host, res): 31 | human_log(res) 32 | 33 | def runner_on_no_hosts(self): 34 | pass 35 | 36 | def runner_on_async_poll(self, host, res, jid, clock): 37 | human_log(res) 38 | 39 | def runner_on_async_ok(self, host, res, jid): 40 | human_log(res) 41 | 42 | def runner_on_async_failed(self, host, res, jid): 43 | human_log(res) 44 | 45 | def playbook_on_start(self): 46 | pass 47 | 48 | def playbook_on_notify(self, host, handler): 49 | pass 50 | 51 | def playbook_on_no_hosts_matched(self): 52 | pass 53 | 54 | def playbook_on_no_hosts_remaining(self): 55 | pass 56 | 57 | def playbook_on_task_start(self, name, is_conditional): 58 | pass 59 | 60 | def playbook_on_vars_prompt(self, varname, private=True, prompt=None, encrypt=None, confirm=False, salt_size=None, salt=None, default=None): 61 | pass 62 | 63 | def playbook_on_setup(self): 64 | pass 65 | 66 | def playbook_on_import_for_host(self, host, imported_file): 67 | pass 68 | 69 | def playbook_on_not_import_for_host(self, host, missing_file): 70 | pass 71 | 72 | def playbook_on_play_start(self, pattern): 73 | pass 74 | 75 | def playbook_on_stats(self, stats): 76 | pass 77 | -------------------------------------------------------------------------------- /callback_plugins/timestamp.py: -------------------------------------------------------------------------------- 1 | # (C) 2012-2013, Michael DeHaan, 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 . 17 | 18 | from ansible.plugins.callback import CallbackBase 19 | import datetime 20 | 21 | # define start time 22 | t0 = tn = datetime.datetime.utcnow() 23 | 24 | def filled(msg, fchar="*"): 25 | 26 | if len(msg) == 0: 27 | width = 79 28 | else: 29 | msg = "%s " % msg 30 | width = 79 - len(msg) 31 | if width < 3: 32 | width = 3 33 | filler = fchar * width 34 | return "%s%s " % (msg, filler) 35 | 36 | def timestamp(): 37 | 38 | global tn 39 | time_current = datetime.datetime.utcnow() 40 | time_elapsed = (time_current - tn).total_seconds() 41 | time_total_elapsed = (time_current - t0).total_seconds() 42 | print( filled( '%s (delta: %s) %s elapsed: %s' % (time_current.isoformat(), 43 | time_elapsed, ' ' * 7, time_total_elapsed ))) 44 | tn = datetime.datetime.utcnow() 45 | 46 | 47 | 48 | class CallbackModule(CallbackBase): 49 | 50 | """ 51 | this is an example ansible callback file that does nothing. You can drop 52 | other classes in the same directory to define your own handlers. Methods 53 | you do not use can be omitted. 54 | 55 | example uses include: logging, emailing, storing info, etc 56 | """ 57 | 58 | 59 | def on_any(self, *args, **kwargs): 60 | pass 61 | 62 | def runner_on_failed(self, host, res, ignore_errors=False): 63 | pass 64 | 65 | def runner_on_ok(self, host, res): 66 | pass 67 | 68 | def runner_on_error(self, host, msg): 69 | pass 70 | 71 | def runner_on_skipped(self, host, item=None): 72 | pass 73 | 74 | def runner_on_unreachable(self, host, res): 75 | pass 76 | 77 | def runner_on_no_hosts(self): 78 | pass 79 | 80 | def runner_on_async_poll(self, host, res, jid, clock): 81 | pass 82 | 83 | def runner_on_async_ok(self, host, res, jid): 84 | pass 85 | 86 | def runner_on_async_failed(self, host, res, jid): 87 | pass 88 | 89 | def playbook_on_start(self): 90 | pass 91 | 92 | def playbook_on_notify(self, host, handler): 93 | pass 94 | 95 | def playbook_on_no_hosts_matched(self): 96 | pass 97 | 98 | def playbook_on_no_hosts_remaining(self): 99 | pass 100 | 101 | def playbook_on_task_start(self, name, is_conditional): 102 | timestamp() 103 | pass 104 | 105 | def playbook_on_vars_prompt(self, varname, private=True, prompt=None, encrypt=None, confirm=False, salt_size=None, salt=None, default=None): 106 | pass 107 | 108 | def playbook_on_setup(self): 109 | timestamp() 110 | pass 111 | 112 | def playbook_on_import_for_host(self, host, imported_file): 113 | pass 114 | 115 | def playbook_on_not_import_for_host(self, host, missing_file): 116 | pass 117 | 118 | def playbook_on_play_start(self, pattern): 119 | timestamp() 120 | self._display.display(filled("", fchar="=")) 121 | pass 122 | 123 | def playbook_on_stats(self, stats): 124 | timestamp() 125 | self._display.display(filled("", fchar="=")) 126 | pass 127 | 128 | -------------------------------------------------------------------------------- /dumpall.j2: -------------------------------------------------------------------------------- 1 | Module Variables ("vars"): 2 | -------------------------------- 3 | {{ vars | to_nice_json }} 4 | 5 | Environment Variables ("environment"): 6 | -------------------------------- 7 | {{ environment | to_nice_json }} 8 | 9 | GROUP NAMES Variables ("group_names"): 10 | -------------------------------- 11 | {{ group_names | to_nice_json }} 12 | 13 | GROUPS Variables ("groups"): 14 | -------------------------------- 15 | {{ groups | to_nice_json }} 16 | -------------------------------------------------------------------------------- /examples/jvspherecontrol.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | gather_facts: False 3 | tasks: 4 | - action: 5 | module: jvspherecontrol 6 | path: /home/serge/src/jvspherecontrol/target/jvspherecontrol-0.0.4-SNAPSHOT-jar-with-dependencies.jar 7 | url: vcserver.local 8 | cluster: myorg 9 | user: ansible 10 | password: t0ps3cr3t 11 | command: createvm 12 | name: test 13 | disk: 14 | - size: 10485760 15 | - size: 104857600 16 | nic: 17 | - connected: yes 18 | network: 3000 19 | name: prod 20 | pxe: yes 21 | - name: mgmt 22 | bootorder: 'allow:net,hd' 23 | registermac: 'cobbler system edit --name=${inventory_hostname} --mac=%s' 24 | -------------------------------------------------------------------------------- /filter_plugins/hash.py: -------------------------------------------------------------------------------- 1 | # (c) 2013, Ali-Akber Saifee 2 | 3 | def hash_to_tuples(h): 4 | return h.items() 5 | 6 | def hash_keys(h): 7 | return h.keys() 8 | 9 | def hash_values(h): 10 | return h.values() 11 | 12 | def zipped_hash(h, key, item): 13 | ret = [] 14 | for el in h: 15 | if h[el].has_key(item): 16 | for subel in h[el][item]: 17 | ret.append({"key" : h[el][key], "value" : subel }) 18 | return ret 19 | 20 | class FilterModule(object): 21 | ''' utility filters for operating on hashes ''' 22 | 23 | def filters(self): 24 | return { 25 | 'hash_to_tuples' : hash_to_tuples 26 | ,'hash_keys' : hash_keys 27 | ,'hash_values' : hash_values 28 | ,'zipped_hash' : zipped_hash 29 | } 30 | 31 | -------------------------------------------------------------------------------- /library/README.md: -------------------------------------------------------------------------------- 1 | ansible-plugins 2 | =============== 3 | 4 | My custom modules for ansible (https://github.com/ansible/ansible) configuration management: 5 | a multi-node deployment/orchestration, and remote task execution system. 6 | 7 | This repo is ordered by the subdirectories as mentioned in ansible's lib/ansible/utils/plugins.py 8 | 9 | Inventory plugins are best written in Python, but could be in any language you like. 10 | 11 | + `inventory_plugins` (No part of ansible's base code; [as described in ansible's api docs](https://github.com/ansible/ansible/blob/devel/docsite/latest/rst/api.rst#external-inventory-scripts)) 12 | + `jvspherecontrol`: a python plugin as a frontend to [Patrick Debois' jvspherecontrol](https://github.com/jedi4ever/jvspherecontrol) 13 | + `known_hosts`: a python plugin to makes sure the known_hosts line is present or absent in the user's .ssh/known_hosts. 14 | + `seds`: a simple bash plugin which runs a `sed 's'` command 15 | 16 | -------------------------------------------------------------------------------- /library/check_process: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2013, Serge van Ginderachter 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | 21 | DOCUMENTATION = ''' 22 | --- 23 | module: check_process 24 | author: Serge van Ginderachter 25 | version_added: "1.2" 26 | short_description: Check processes and maybe kill them 27 | description: 28 | - Checks (number of) running processes for a given command argument and 29 | can kill those matching processes, with increasing kill signal priority 30 | options: 31 | name: 32 | required: true 33 | description: 34 | - name of the process, a substring to look for as would be 35 | found in the output of the I(ps) command; matches against full 36 | process argument string (as per ps auxww) 37 | count: 38 | required: false 39 | description: 40 | - exact number of processes that should be found in the process list; 41 | if 0, processess will be killed, which is the only case where action 42 | is taken to modify the target system; 43 | use of count excludes use of mincount or maxcount 44 | mincount: 45 | required: false 46 | default: 0 47 | description: 48 | - minimum number of processes that should be found in the process list; 49 | if set, module will fail if less processes are found 50 | maxcount: 51 | required: false 52 | description: 53 | - maximum number of processes that should be found in the process list; 54 | if set, module will fail if more processes are found 55 | wait: 56 | required: false 57 | default: 60,5 58 | description: 59 | - times in seconds to wait before checking the process list again, 60 | for process(es) to disappear from the process list, after receiving 61 | a kill signal; there must be at least as many wait times as there are 62 | killsignals; only used when count=0 63 | killsignals: 64 | required: false 65 | default: SIGTERM,SIGKILL 66 | description: 67 | - comma-separated ordered list of signals(7), used to sequentially 68 | terminate the process when count is set to 0 69 | if set to C(NOKILL), no kill will be executed, but will still wait for 70 | proces to come down, handy to use after e.g. an init stop script 71 | 72 | examples: 73 | - description: checks that exactly 1 process is running of this custom tomcat service 74 | code: "check_process: name=/opt/tomcat/apache-tomcat7/lib/ count=1" 75 | - description: force kill all running java processes 76 | code: "check_process: name=java count=0 killsignals=SIGKILL" 77 | - description: check for at least 10 apache processes 78 | code: "check_process: name=apache2 mincount=10" 79 | ''' 80 | 81 | import platform 82 | import time 83 | 84 | KILLSIGNALS = 'SIGTERM,SIGKILL' 85 | WAITTIMES = '30,5' 86 | 87 | def get_pids(module): 88 | ''' returns a list of pid's matching the name pattern''' 89 | 90 | pattern = module.params['name'] 91 | 92 | # Find ps binary 93 | psbin = module.get_bin_path('ps', True) 94 | 95 | # Set ps flags 96 | if platform.system() == 'SunOS': 97 | psflags = '-ef' 98 | else: 99 | psflags = 'auxww' 100 | 101 | # run ps command 102 | cmd = '%s %s' % (psbin, psflags) 103 | (rc, out, err) = module.run_command(cmd) 104 | if rc != 0: 105 | module.fail_json(msg='Fatal error running command %s' % cmd, stdout=out, stderr=err) 106 | 107 | # parse ps output into list of pid's 108 | pids = [] 109 | lines = out.split("\n") 110 | for line in lines: 111 | # exclude ps itself, hacking/test-module script 112 | if pattern in line and not 'test-module' in line: 113 | # add second PID column to list 114 | pids.extend([line.split()[1]]) 115 | return pids 116 | 117 | 118 | def kill_process(module, pids, signal): 119 | # Find kill binary 120 | killbin = module.get_bin_path('kill', True) 121 | 122 | # put pid list in a space separate string 123 | pids_str = ' '.join(str(x) for x in pids ) 124 | 125 | # kill --signal 126 | cmd = '%s -%s %s' % (killbin, signal, pids_str) 127 | if signal == 'NOKILL': 128 | # don't execute any kill command 129 | cmd = ': ' + cmd 130 | (rc, out, err) = module.run_command(cmd) 131 | 132 | 133 | def kill_process_wait(module, pids, signal, wait): 134 | kill_process(module, pids, signal) 135 | 136 | t0 = time.time() 137 | while 1: 138 | pids = get_pids(module) 139 | running = len(pids) 140 | if running == 0 or (time.time() - t0) >= int(wait): 141 | break 142 | # wait for processes to shut down or until timeout 'wait' 143 | time.sleep(1) 144 | 145 | return pids 146 | 147 | 148 | def check_with_count(module, result, fail): 149 | 150 | count = module.params['count'] 151 | # convert comma separated kilsignals into list 152 | killsignals = module.params['killsignals'].split(',') 153 | waittimes = module.params['wait'].split(',') 154 | 155 | if len(killsignals) > len(waittimes): 156 | module.fail_json(msg='you need at least as many wait times (got %s) as killsignals (got %s)' % (len(killsignals), len(waits))) 157 | 158 | pids = get_pids(module) 159 | running = len(pids) 160 | 161 | # kill! kill! kill! 162 | if count == 0: 163 | 164 | if running == 0 or module.check_mode: 165 | # nothing to do, zero killed, or 166 | # there are active processes, but we are in check mode, 167 | # so we assume all got killed 168 | still_running = 0 169 | signal = None 170 | 171 | elif not module.check_mode: 172 | # there are active processes so now really kill them all 173 | # loop through the kill signals in order, and try killing the process(es) 174 | 175 | t0 = time.time() 176 | for n in range(len(killsignals)): 177 | signal = killsignals[n] 178 | wait = waittimes[n] 179 | pids = kill_process_wait(module, pids, signal, wait) 180 | # still running after kill? 181 | still_running = len(pids) 182 | if still_running == 0: 183 | break 184 | result['time_elapsed'] = '%s s' % (time.time() - t0) 185 | 186 | killed = running - still_running 187 | result['still_running'] = still_running 188 | result['killed'] = killed 189 | result['signal'] = signal 190 | result['msg'] = 'killed %s processes' % killed 191 | 192 | # changed is only true when we killed 193 | if killed != 0: 194 | result['changed'] = True 195 | 196 | # if we killed as much as was running, exit ok 197 | if killed != running: 198 | fail = True 199 | 200 | # check if we have the right number of running processes 201 | elif count > 0: 202 | if count == running: 203 | result['msg'] = 'Number of running processes (%s) is equal to %s' % (running, count) 204 | else: 205 | result['msg'] = 'Number of running processes (%s) is not equal to %s' % (running, count) 206 | fail = True 207 | 208 | else: 209 | result['msg'] = 'count cannot be negative' 210 | fail = True 211 | 212 | return (result, fail) 213 | 214 | 215 | def check_with_minmaxcount(module, result, fail): 216 | 217 | mincount = module.params['mincount'] 218 | maxcount = module.params['maxcount'] 219 | 220 | pids = get_pids(module) 221 | running = len(pids) 222 | 223 | if mincount is not None and maxcount is not None: 224 | if mincount >= maxcount: 225 | result['msg'] = 'Minimum (%s) should be smaller than maximum (%s)' % (mincount, maxcount) 226 | fail = True 227 | 228 | elif mincount <= running <= maxcount: 229 | result['msg'] = 'Number of running processes (%s) is in the range %s -> %s.' % (running, mincount, maxcount) 230 | else: 231 | result['msg'] = 'Number of running processes (%s) is not in the range %s -> %s.' % (running, mincount, maxcount) 232 | fail = True 233 | elif mincount is not None: 234 | if mincount <= running: 235 | result['msg'] = 'Number of running processes (%s) is larger than %s' % (running, mincount) 236 | else: 237 | result['msg'] = 'Number of running processes (%s) is smaller than %s' % (running, mincount) 238 | fail = True 239 | elif maxcount is not None: 240 | result['maxcount'] = maxcount 241 | if running <= maxcount: 242 | result['msg'] = 'Number of running processes (%s) is smaller than %s' % (running, maxcount) 243 | else: 244 | result['msg'] = 'Number of running processes (%s) is larger than %s' % (running, maxcount) 245 | fail = True 246 | 247 | return (result, fail) 248 | 249 | 250 | def main(): 251 | # load ansible module object 252 | module = AnsibleModule( 253 | argument_spec = dict( 254 | name = dict(required=True), 255 | count = dict(required=False, default=None, type='int'), 256 | mincount = dict(required=False, type='int'), 257 | maxcount = dict(required=False, type='int'), 258 | wait = dict(required=False, default=WAITTIMES), 259 | killsignals = dict(required=False, default=KILLSIGNALS) 260 | ), 261 | supports_check_mode=True, 262 | mutually_exclusive=[['count', 'mincount'], ['count', 'maxcount']], 263 | required_one_of=[['count', 'mincount' , 'maxcount']] 264 | ) 265 | name = module.params['name'] 266 | count = module.params['count'] 267 | mincount = module.params['mincount'] 268 | maxcount = module.params['maxcount'] 269 | 270 | # return json dict 271 | result = {} 272 | result['name'] = name 273 | 274 | # retrieve pids matching the name pattern 275 | pids = get_pids(module) 276 | result['pids'] = pids 277 | 278 | # set number of running processes we found 279 | running = len(pids) 280 | result['running'] = running 281 | 282 | # set default changed false; is only true when we killed 283 | result['changed'] = False 284 | # default no fail 285 | fail = False 286 | 287 | # if count is set 288 | if count is not None: 289 | result['count'] = count 290 | (result, fail) = check_with_count(module, result, fail) 291 | 292 | # check with mincount and/or maxcount 293 | elif mincount is not None or maxcount is not None: 294 | result['mincount'] = mincount 295 | result['maxcount'] = maxcount 296 | (result, fail) = check_with_minmaxcount(module, result, fail) 297 | 298 | if fail: 299 | module.fail_json(**result) 300 | else: 301 | module.exit_json(**result) 302 | 303 | # this is magic, see lib/ansible/module_common.py 304 | #<> 305 | 306 | main() 307 | 308 | -------------------------------------------------------------------------------- /library/dpkg_reconfigure: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Ansible module for reconfiguring debian packages. 6 | (c) 2013, Sebastien Bocahu 7 | 8 | This file is part of Ansible 9 | 10 | Ansible is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | Ansible is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with Ansible. If not, see . 22 | """ 23 | 24 | DOCUMENTATION = ''' 25 | --- 26 | module: dpkg_reconfigure 27 | short_description: Reconfigure a Debian package. 28 | description: 29 | - Reconfigure a Debian package using dpkg-reconfigure. 30 | version_added: "1.2" 31 | notes: 32 | - A number of questions has to be answered (depending on the package). 33 | Use 'DEBIAN_FRONTED=editor dpkg-reconfigure $pkg' for finding them. 34 | options: 35 | pkg: 36 | description: 37 | - Package to configure. 38 | required: true 39 | default: null 40 | aliases: [] 41 | answers: 42 | description: 43 | - Debconf configuration answer(s)/question(s). 44 | required: true 45 | default: null 46 | aliases: [] 47 | author: Sebastien Bocahu 48 | ''' 49 | 50 | EXAMPLES = ''' 51 | # Set default locale to fr_FR.UTF-8, and generate en_US.UTF-8 as well: 52 | dpkg_reconfigure: 53 | pkg: locales 54 | answers: 55 | locales/default_environment_locale: fr_FR.UTF-8 56 | locales/locales_to_be_generated: en_US.UTF-8 UTF-8, fr_FR.UTF-8 UTF-8 57 | 58 | # Reconfigure roundcube, using configuration answers stored in a file: 59 | dpkg_reconfigure: pkg=roundcube answers='$FILE(/path/dpkg-reconfigure/roundcube)'" 60 | ''' 61 | 62 | import sys 63 | import os 64 | import pwd 65 | import os.path 66 | import re 67 | import tempfile 68 | 69 | def get_selections(module, pkg): 70 | cmd = [module.get_bin_path('debconf-show', True)] 71 | cmd.append(' %s' % pkg) 72 | rc, out, err = module.run_command(' '.join(cmd)) 73 | 74 | if rc == 0: 75 | selections = {} 76 | for answer in out.split('\n'): 77 | item = re.search('^[* ] ([^:]+): (.*)$', answer) 78 | if item: 79 | value = item.group(2).strip() 80 | if value == 'true': 81 | value = 'yes' 82 | elif value == 'false': 83 | value = 'no' 84 | selections[ item.group(1).strip() ] = value 85 | return selections 86 | else: 87 | module.fail_json(msg=err) 88 | 89 | 90 | def dpkg_reconfigure(module, pkg, wanted_config): 91 | editor_script = [ '#!/bin/sh', 'sed -i "$1" -f - < 1: 129 | wanted_config[ item[0].strip() ] = ' '.join(item[1:]) 130 | elif len(item) == 1: 131 | wanted_config[ item[0].strip() ] = '' 132 | 133 | for key in wanted_config: 134 | value = wanted_config[key] 135 | if isinstance(value, bool): 136 | if value: 137 | wanted_config[key] = 'yes' 138 | else: 139 | wanted_config[key] = 'no' 140 | 141 | current_config = get_selections(module, params["pkg"]) 142 | 143 | already_configured = 1 144 | for answer in wanted_config: 145 | if not answer in current_config or current_config[answer] != wanted_config[answer]: 146 | already_configured = 0 147 | 148 | if already_configured: 149 | module.exit_json(changed=False, msg="Already configured") 150 | else: 151 | rc, msg = dpkg_reconfigure(module, pkg, wanted_config) 152 | if not rc: 153 | module.fail_json(msg=msg) 154 | 155 | params['changed'] = True 156 | params['msg'] = msg 157 | return params 158 | 159 | 160 | def main(): 161 | 162 | module = AnsibleModule( 163 | argument_spec = dict( 164 | pkg = dict(required=True), 165 | answers = dict(required=True), 166 | ) 167 | ) 168 | 169 | results = enforce_state(module, module.params) 170 | module.exit_json(**results) 171 | 172 | # this is magic, see lib/ansible/module_common.py 173 | #<> 174 | main() 175 | -------------------------------------------------------------------------------- /library/jvspherecontrol: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2013, Serge van Ginderachter . 20 | 21 | DOCUMENTATION = ''' 22 | --- 23 | author: Serge van Ginderachter 24 | module: jvspherecontrol 25 | short_description: A wrapper module to Patrick Debois' jvspherecontrol 26 | description: 27 | - This module wraps (some) features of Patrick Debois' jvspherecontrol 28 | which is an java cli to the VMWare vsphere API 29 | (https://github.com/jedi4ever/jvspherecontrol) 30 | version_added: "1.2" 31 | options: 32 | path: 33 | required: yes 34 | description: full path to the jvsperecontrol jar 35 | url: 36 | required: true 37 | description: url to connect with to vSphere, including the sdk part 38 | user: 39 | required: true 40 | description: 41 | - username to connect to vSphere 42 | password: 43 | required: true 44 | description: 45 | - password to connect to vSphere 46 | command: 47 | required: true 48 | description: 49 | - the base command to pass to jvspherecontrol 50 | can be any of the jvsphere commands 51 | choices: ['list', 'createvm', 'omapiregister', 'activatevnc', 52 | 'deactivatevnc', 'sendvnctxt'] 53 | 54 | list: 55 | description: 56 | - option to the list command, which objects to retrieve a list from vSphere 57 | choices: ['hosts', 'datacenters', 'datastores', 'clusters', 'networks', 58 | 'users', vms, 'all', 'resourcepools'] 59 | bootorder: 60 | description: 61 | - I(order) to boot, better allow/deny boot devices, e.g. allow:cd,hd,net or deny:net,cd 62 | cdromdatastore: 63 | description: 64 | - cd/dvd datastorename 65 | cdromisopath: 66 | description: 67 | - path to dvd isofile 68 | cluster: 69 | description: 70 | - name of the cluster to store new Vm 71 | cpus: 72 | description: 73 | - number of cpu's to allocate 74 | datacenter: 75 | description: 76 | - name of the datacenter to store new Vm 77 | datastore: 78 | description: 79 | - name of the datastore to store new Vm 80 | disk: 81 | description: 82 | - a list of dictionaries setting the disk(s) 83 | - size in MB's 84 | - ({datastore: 'Datastore 01', mode: '', size: '1024'}, ... ) 85 | memory: 86 | description: 87 | - memory size to allocate 88 | - size in MB 89 | name: 90 | description: 91 | - name of vm to create 92 | nic: 93 | description: 94 | - a list of dictionaries setting the network interface(s): e.g. 95 | ({connected: True, name: 'VM Network', network: '', pxe: 'False', 96 | startconnected: True, type: 'e1000'}, ... ) 97 | omapihost: 98 | description: 99 | - omapi hostname 100 | omapikeyname: 101 | description: 102 | - omapi key to use 103 | omapikeyvalue: 104 | description: 105 | - omapi value 106 | omapioverwrite: 107 | description: 108 | - overwrite omapi entry 109 | omapiport: 110 | description: 111 | - omapi portname 112 | omapiregister: 113 | description: 114 | - register with omapi server 115 | ostype: 116 | description: 117 | - type of vm to create 118 | overwrite: 119 | description: 120 | - overwrite vm Flag 121 | pxeinterface: 122 | description: 123 | - name of the network interface to PXE from 124 | registermac: 125 | description: 126 | - command to execute, %s gets replaced with the MAC address 127 | hostname: 128 | description: 129 | - hostname to register 130 | macaddress: 131 | description: 132 | - mac address to register 133 | omapihost: 134 | description: 135 | - omapi hostname 136 | omapikeyname: 137 | description: 138 | - omapi key to use 139 | omapikeyvalue: 140 | description: 141 | - omapi value 142 | omapioverwrite: 143 | description: 144 | - overwrite omapi entry 145 | omapiport: 146 | description: 147 | - omapi portname 148 | omapiregister: 149 | description: 150 | - register with omapi server 151 | vmname: 152 | description: 153 | - name of vm to create 154 | vncpassword: 155 | description: 156 | - password to set on the VNC 157 | vncport: 158 | description: 159 | - port to enable VNC on 160 | vmname: 161 | description: 162 | - vmname to disable vnc 163 | host: 164 | description: 165 | - host to send it to 166 | port: 167 | description: 168 | - port to connect to 169 | text: 170 | description: 171 | - text to send 172 | wait: 173 | description: 174 | - seconds to wait in between sending different texts (default=1s) 175 | 176 | 177 | notes: 178 | - the system this module gets run on, must be a host with access to the vmware 179 | api; executee I(java -jar C($jvsphere-control-jar-with-dependencies.jar) --help) 180 | for more detailed information on which command takes which parameters 181 | requirements: 182 | - jvspherecontrol must be built from the upstream project (git checkout, then mvn package) 183 | and java must be installed and available in C($PATH) on the system where the module gets executed 184 | - the jvspherecontrol jar must be available on that system, its path defined 185 | ''' 186 | EXAMPLES = ''' 187 | - hosts: localhost 188 | tasks: 189 | - action: 190 | module: jvspherecontrol 191 | path: /home/serge/src/jvspherecontrol/target/jvspherecontrol-0.0.4-SNAPSHOT-jar-with-dependencies.jar 192 | url: vcserver.local 193 | cluster: myorg 194 | user: ansible 195 | password: t0ps3cr3t 196 | command: createvm 197 | name: test 198 | disk: 199 | - datastore: datastore1 200 | - datastore: datastore1 201 | size: 10485760 202 | nic: 203 | - connected: yes 204 | network: 3000 205 | name: prod 206 | pxe: yes 207 | - name: mgmt 208 | bootorder: 'allow:net,hd' 209 | registermac: '"cobbler system edit --name=${inventory_hostname} --mac=%s"' 210 | ''' 211 | 212 | COMMANDS = ['list', 'createvm', 'omapiregister', 'activatevnc', 213 | 'deactivatevnc', 'sendvnctxt'] 214 | NOACT_COMMANDS = ['list'] 215 | 216 | NEED_VSPHERE_CONN = ['list', 'createvm', 'activatevnc', 'deactivatevnc'] 217 | 218 | 219 | OPTIONS = {'list': ['hosts', 'datacenters', 'datastores', 'clusters', 'networks', 220 | 'users', 'vms', 'all', 'resourcepools'], 221 | 'createvm': ['bootorder', 'cdromdatastore', 'cdromisopath', 'cluster', 222 | 'cpus', 'datacenter', 'datastore', 'disk', 'memory', 'name', 223 | 'nic', 'omapihost', 'omapikeyname', 'omapikeyvalue', 224 | 'omapioverwrite', 'omapiport', 'omapiregister', 'ostype', 225 | 'overwrite', 'pxeinterface', 'registermac'], 226 | 'omapiregister': ['hostname', 'macaddress', 'omapihost', 227 | 'omapikeyname', 'omapikeyvalue', 'omapioverwrite', 228 | 'omapiport', 'omapiregister'], 229 | 'activatevnc': ['vmname', 'vncpassword', 'vncport'], 230 | 'deactivatevnc': ['vmname'], 231 | 'sendvnctext': ['host', 'password', 'port', 'text', 'wait']} 232 | 233 | REQUIRED = {'createvm': ['name', 'memory', 'ostype'], 234 | 'omapiregister': ['macaddress', 'hostname'], 235 | 'activatevnc': [OPTIONS['activatevnc']], 236 | 'deactivatevnc': ['vmname'], 237 | 'sendvnctext': ['password', 'host', 'port']} 238 | 239 | MULTI_OPTIONS = {'disk': ('datastore', 'mode', 'size'), 240 | 'nic': ('connected', 'name', 'network', 'pxe', 241 | 'startconnected', 'type')} 242 | DEFAULTS = {'memory': '1024', 243 | 'ostype': 'Ubuntu64Guest', 244 | 'cpus': '1', 245 | 'disk': {'mode': 'persistent', 246 | 'size': '110485760'}, 247 | 'nic': {'type': 'e1000', 248 | 'connected': 'True', 249 | 'startconnected': 'True', 250 | 'network': 'VM Network'} 251 | } 252 | 253 | import re 254 | 255 | E_VM_EXISTS = 1 256 | 257 | def safe_eval(str): 258 | ''' 259 | adapted from ansible_utils.safe_eval 260 | this is intended for allowing things like: 261 | multioption: "{{ a_complex_variable }}" 262 | ''' 263 | # do not allow method calls to modules 264 | if not isinstance(str, basestring): 265 | # already templated to a datastructure, perhaps? 266 | return str 267 | if re.search(r'\w\.\w+\(', str): 268 | return str 269 | # do not allow imports 270 | if re.search(r'import \w+', str): 271 | return str 272 | try: 273 | return eval(str) 274 | except Exception, e: 275 | return str 276 | 277 | 278 | def check_paths(module): 279 | p = module.params 280 | 281 | # check for java exec 282 | java = module.get_bin_path('java', True) 283 | 284 | # check jvspherecontrol jar path 285 | path = p['path'] 286 | if not os.path.isfile(path) or not os.access(path, os.R_OK): 287 | module.fail_json(msg="could not find or read the jvspherecontrol jar") 288 | 289 | return java 290 | 291 | def get_jvspherecontrol(module): 292 | 293 | # initial checks 294 | java = check_paths(module) 295 | 296 | p = module.params 297 | 298 | # set base command 299 | jvspherecontrol = '%s -jar %s' % (java, p['path']) 300 | 301 | return jvspherecontrol 302 | 303 | def get_connection_options(module): 304 | p = module.params 305 | url = p['url'] 306 | user = p['user'] 307 | password = p['password'] 308 | 309 | for opt in [ 'url', 'user', 'password' ]: 310 | if p[opt] is None: 311 | module.fail_json(msg="missing required option --%s for command %s" % 312 | (opt, p['command'])) 313 | 314 | # Try to failsafe if only hostname/ip is given 315 | if not url.startswith('http://'): 316 | url = 'https://' + url 317 | if not url.endswith('/sdk'): 318 | url = url + '/sdk' 319 | 320 | return '--url "%s" --user "%s" --password "%s"' % (url, user, password) 321 | 322 | def _get_command_options(module, options=[], required=[]): 323 | p = module.params 324 | opts = '' 325 | 326 | for opt in options: # options for given command 327 | # check if we have a default for a non multi options 328 | # there are no defaults for multi options 329 | # there can be defaults for multioptions-suboptions, but these are handled later 330 | if p[opt] is None and opt in required and opt in DEFAULTS and opt not in MULTI_OPTIONS: 331 | p[opt] = DEFAULTS[opt] 332 | if p[opt] is None and opt in required: 333 | # if still none, error 334 | module.fail_json(msg="missing required option --%s for command %s" % 335 | (opt, p['command'])) 336 | elif opt in MULTI_OPTIONS: 337 | # multi options have complex vars 338 | # can be a dict (1 disk/nic) or a list of dicts (multiple disks/nics) 339 | if isinstance(p[opt], basestring): 340 | p[opt] = safe_eval(p[opt]) 341 | if isinstance(p[opt], dict): 342 | # convert to a list of 1 dict 343 | # then we handle lists 344 | devopts = (p[opt]) 345 | elif isinstance(p[opt], list): 346 | devopts = p[opt] 347 | elif p[opt] is None: 348 | devopts = [] 349 | else: 350 | module.fail_json(msg="Error parsing complex variable %s == %s" % 351 | (opt, p[opt])) 352 | 353 | # devopts now is a list of dicts holding device multi-options parameters 354 | for devicenum in range(len(devopts)): 355 | 356 | # first replace missing suboptions by default value 357 | for defopt in DEFAULTS[opt]: 358 | if defopt not in devopts[devicenum]: 359 | devopts[devicenum][defopt] = DEFAULTS[opt][defopt] 360 | 361 | for devopt in devopts[devicenum]: # devopt = sub device dict key 362 | if devopt in MULTI_OPTIONS[opt]: 363 | # valid extension, device count starts at 1 364 | devoptvalue = devopts[devicenum][devopt] 365 | opts += ' --%s%s%s "%s"' % (opt, devopt, devicenum + 1, devoptvalue) 366 | else: 367 | module.fail_json(msg='--%s%s%s is not a valid option' 368 | % (opt, devopt, devicenum + 1)) 369 | 370 | elif p[opt] is not None: 371 | opts += ' --%s "%s"' % (opt, p[opt]) 372 | 373 | return opts 374 | 375 | def get_command_options(module, command): 376 | p = module.params 377 | opts = '' 378 | 379 | if command in NOACT_COMMANDS: 380 | if command == 'list': 381 | opts += '%s ' % p['list'] 382 | 383 | elif command in COMMANDS: 384 | opts += _get_command_options(module, options=OPTIONS[command], 385 | required=REQUIRED[command]) + ' ' 386 | 387 | if command in NEED_VSPHERE_CONN: 388 | opts += get_connection_options(module) 389 | 390 | return opts 391 | 392 | 393 | def main(): 394 | module = AnsibleModule( 395 | argument_spec = dict( 396 | # path to jvspherecontrol.jar 397 | path = dict(required=True), 398 | 399 | # common required args 400 | url = dict(), 401 | user = dict(), 402 | password = dict(), 403 | 404 | # base command 405 | command = dict(required=True, choices=COMMANDS), 406 | 407 | # list command option 408 | list = dict(default='vms', choice=OPTIONS['list']), 409 | 410 | # createvm vommand options 411 | bootorder = dict(), 412 | cdromdatastore = dict(), 413 | cdromisopath = dict(), 414 | cluster = dict(), 415 | cpus = dict(), 416 | datacenter = dict(), 417 | datastore = dict(), 418 | disk = dict(), 419 | memory = dict(), 420 | name = dict(), 421 | nic = dict(), 422 | omapihost = dict(), 423 | omapikeyname = dict(), 424 | omapikeyvalue = dict(), 425 | omapioverwrite = dict(), 426 | omapiport = dict(), 427 | omapiregister = dict(), 428 | ostype = dict(), 429 | overwrite=dict(type='bool', default='no'), 430 | pxeinterface = dict(), 431 | registermac = dict(), 432 | hostname = dict(), 433 | macaddress = dict(), 434 | vmname = dict(), 435 | vncpassword = dict(), 436 | vncport = dict(), 437 | host = dict(), 438 | port = dict(), 439 | text = dict(), 440 | wait = dict() 441 | ), 442 | supports_check_mode=True 443 | ) 444 | 445 | p = module.params 446 | check_mode = module.check_mode 447 | 448 | # get base command with required base args 449 | jvspherecontrol = get_jvspherecontrol(module) 450 | 451 | # get jvspherecontrol base command 452 | command = p['command'] 453 | # get jvspherecontrol options for command 454 | options = get_command_options(module, command) 455 | 456 | # assemble executable command 457 | cmd = '%s %s %s' % (jvspherecontrol, command, options) 458 | 459 | if command not in NOACT_COMMANDS and check_mode == True: 460 | module.exit_json(changed=True) 461 | 462 | (rc, out, err) = module.run_command(cmd) 463 | 464 | # jvspherecontrol exit codes suck, seems to be allways 0 when error 465 | if 'exception' in err.lower(): 466 | module.fail_json(msg='jvspherecontrol returned an error', stdout=out, 467 | stderr=err, cmd=cmd) 468 | 469 | if command == 'list': 470 | if rc == 0: 471 | list = [] 472 | for line in out.rstrip('\n').split('\n'): 473 | if line not in ['executing']: 474 | item = line.strip() 475 | list.extend([item]) 476 | module.exit_json(changed=False, list=list, stdout=out, stderr=err) 477 | elif rc != 0 or 'exception' in stderr: 478 | module.fail_json(stdout=out, stderr=err, cmd=cmd, msg='failed executing list command') 479 | 480 | elif command == 'createvm': 481 | if rc == 0: 482 | module.exit_json(changed=True, name=p['name'], stdout=out, 483 | stderr=err, cmd=cmd) 484 | elif rc == E_VM_EXISTS: 485 | # when return code == E_VM_EXISTS == 1 486 | # the vm already exists 487 | # only happens when force is false (default) 488 | module.exit_json(changed=False, name=p['name'], stdout=out, 489 | stderr=err, cmd=cmd) 490 | elif rc not in [0,1]: 491 | module.fail_json(name=p['name'], stdout=out, stderr=err, cmd=cmd, 492 | msg='Failed creating vm') 493 | 494 | elif command in COMMANDS: 495 | if rc == 0: 496 | module.exit_json(changed=False, stdout=out, stderr=err, cmd=cmd) 497 | else: 498 | module.fail_json(name=name, stdout=out, stderr=err, cmd=cmd, 499 | msg='failed executing %s command' % command) 500 | else: 501 | module.fail_json(msg="the %s command does not exist" % command) 502 | 503 | # this is magic, see lib/ansible/module_common.py 504 | #<> 505 | 506 | if __name__ == '__main__': 507 | main() 508 | 509 | -------------------------------------------------------------------------------- /library/known_host: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Ansible module to add host keys to ssh known_hosts. 6 | 7 | Modified from the authorized_keys module. 8 | 9 | (c) 2012, Jonathan Rudenberg 10 | (c) 2012, Brad Olson 11 | 12 | This file is part of Ansible. 13 | 14 | Ansible is free software: you can redistribute it and/or modify 15 | it under the terms of the GNU General Public License as published by 16 | the Free Software Foundation, either version 3 of the License, or 17 | (at your option) any later version. 18 | 19 | Ansible is distributed in the hope that it will be useful, 20 | but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | GNU General Public License for more details. 23 | 24 | You should have received a copy of the GNU General Public License 25 | along with Ansible. If not, see . 26 | """ 27 | 28 | # Makes sure the known_hosts line is present or absent in the user's .ssh/known_hosts. 29 | # 30 | # Arguments 31 | # ========= 32 | # user = username 33 | # host = line to add to known_hosts for user 34 | # state = absent|present (default: present) 35 | 36 | import sys 37 | import os 38 | import pwd 39 | import os.path 40 | 41 | def hostfile(user, write=False): 42 | """ 43 | Calculate name of known hosts file, optionally creating the 44 | directories and file, properly setting permissions. 45 | 46 | :param str user: name of user in passwd file 47 | :param bool write: if True, write changes to known_hosts file (creating directories if needed) 48 | :return: full path string to known_hosts for user 49 | """ 50 | 51 | user_entry = pwd.getpwnam(user) 52 | homedir = user_entry.pw_dir 53 | sshdir = os.path.join(homedir, ".ssh") 54 | hostsfile = os.path.join(sshdir, "known_hosts") 55 | 56 | if not write: 57 | return hostsfile 58 | 59 | uid = user_entry.pw_uid 60 | gid = user_entry.pw_gid 61 | 62 | if not os.path.exists(sshdir): 63 | os.mkdir(sshdir, 0700) 64 | os.chown(sshdir, uid, gid) 65 | os.chmod(sshdir, 0700) 66 | 67 | if not os.path.exists( hostsfile): 68 | try: 69 | f = open(hostsfile, "w") #touches file so we can set ownership and perms 70 | finally: 71 | f.close() 72 | 73 | os.chown(hostsfile, uid, gid) 74 | os.chmod(hostsfile, 0600) 75 | return hostsfile 76 | 77 | def readhosts(filename): 78 | 79 | if not os.path.isfile(filename): 80 | return [] 81 | f = open(filename) 82 | hosts = [line.rstrip() for line in f.readlines()] 83 | f.close() 84 | return hosts 85 | 86 | def writehosts(filename, hosts): 87 | 88 | f = open(filename,"w") 89 | f.writelines( (host + "\n" for host in hosts) ) 90 | f.close() 91 | 92 | def enforce_state(module, params): 93 | """ 94 | Add or remove host. 95 | """ 96 | 97 | user = params["user"] 98 | host = params["host"] 99 | state = params.get("state", "present") 100 | 101 | # check current state -- just get the filename, don't create file 102 | params["hostfile"] = hostfile(user, write=False) 103 | hosts = readhosts(params["hostfile"]) 104 | present = host in hosts 105 | 106 | # handle idempotent state=present 107 | if state=="present": 108 | if present: 109 | module.exit_json(changed=False) 110 | hosts.append(host) 111 | writehosts(hostfile(user,write=True), hosts) 112 | 113 | elif state=="absent": 114 | if not present: 115 | module.exit_json(changed=False) 116 | hosts.remove(host) 117 | writehosts(hostfile(user,write=True), hosts) 118 | 119 | params['changed'] = True 120 | return params 121 | 122 | def main(): 123 | 124 | module = AnsibleModule( 125 | argument_spec = dict( 126 | user = dict(required=True), 127 | host = dict(required=True), 128 | state = dict(default='present', choices=['absent','present']) 129 | ) 130 | ) 131 | 132 | params = module.params 133 | results = enforce_state(module, module.params) 134 | module.exit_json(**results) 135 | 136 | # this is magic, see lib/ansible/module_common.py 137 | #<> 138 | main() 139 | -------------------------------------------------------------------------------- /library/seds: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | 3 | # Ansible module to sed a file with an 's' script. 4 | # (c) 2013, Serge van Ginderachter 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | 21 | DOCUMENTATION=" 22 | --- 23 | module: seds 24 | short_description: module to sed a file with an 's' script 25 | description: 26 | - to run 's/${regexp/{replace}/' on a file 27 | - optionally add the 'thirsty' g option at the end of the sed script 28 | version_added: null 29 | author: Serge van Ginderachter 30 | notes: This is a Bash module 31 | requirements: null 32 | options: 33 | dest: 34 | description: the file that will be modified by sed 35 | required: true 36 | default: null 37 | regexp: 38 | description: the regular expression by which sed will select the part to change 39 | required: true 40 | default: null 41 | replace: 42 | description: the expression by which sed will replace the regexp, can be an empty string 43 | required: false 44 | default: empty string 45 | thirsty: 46 | description: 47 | - a boolean, whether sed will replace multiple instances of the regexp on the same line 48 | - sets the 'g' modiier at the end of the sed s script 49 | choices: [true, false] 50 | " 51 | 52 | EXAMPLES=" 53 | --- 54 | examples: 55 | - code: seds dest=somefile regexp=deletethistring thirsty=yes 56 | description: delete all occureences of 'deletethisstring' on every line in 'somefile' 57 | " 58 | 59 | # Generic functions 60 | 61 | fail() { 62 | local msg="$1" 63 | echo "failed=True msg=\"$msg\"" 64 | # ansible doesn't care (yet) about module exit code 65 | exit 1 66 | } 67 | 68 | changed() { 69 | local msg="$1" 70 | local lines="$2" 71 | echo "changed=True msg=\"$msg\" lines=\"$lines\"" 72 | exit 0 73 | } 74 | 75 | ok() { 76 | local msg="$1" 77 | echo "changed=False" 78 | exit 0 79 | } 80 | 81 | booleanify() { 82 | local bool=$1 83 | 84 | # list of possible boolean equivalent strings 85 | BOOLEANS_TRUE="y Y yes Yes YES true True TRUE 0 on On ON" 86 | 87 | rc=1 88 | for b in $BOOLEANS_TRUE 89 | do 90 | if [ "$bool" = "$b" ] 91 | then # found boolean_true 92 | rc=0 93 | fi 94 | 95 | # stop looping if we found one 96 | [ $rc = 0 ] && break 97 | done 98 | 99 | return $rc 100 | } 101 | 102 | src_readable() { 103 | if [ -r "${src}" ] 104 | then return 0 105 | else return 1 106 | fi 107 | } 108 | 109 | dest_writeable() { 110 | if [ -w "${dest}" ] 111 | then return 0 112 | else return 1 113 | fi 114 | } 115 | 116 | # start here to customize your own module 117 | 118 | parse_args() { 119 | 120 | # evaluate the parameter file prepared by ansible 121 | local paramfile=$1 122 | [ -r "$paramfile" ] && eval $(sed -e "s/\s?\([^=]+\)\s?=\s?\(\x22\([^\x22]+\)\x22|\x27\([^\x27]+\)\x27|\(\S+\)\)\s?/\1='\2'/p" $paramfile) 123 | 124 | # destination aka file to be modified must exist 125 | [ -z "${dest}" ] && fail "destination file must be defined at dest=" 126 | 127 | # regexp must exist or we have nothing to do 128 | [ -z "${regexp}" ] && fail "pattern must be defined at regexp=" 129 | 130 | # replace may be empty; yes, this is redundant 131 | replace=${replace:=} 132 | 133 | # thirsty is boolean and default false 134 | # if thirsty then set to 'g' else '' as sed s script extension 135 | thirsty=${thirsty:=1} 136 | if booleanify ${thirsty} 137 | then thirsty='g' 138 | else thirsty='' 139 | fi 140 | 141 | } 142 | 143 | find_delimiter() { 144 | local delimiters="/ @ # % + = _ & [ ] 0 1 2 3 4 5 6 7 8 9" 145 | found=1 146 | for del in ${delimiters} 147 | do if [ "${regexp}" = "${regexp/${del}}" \ 148 | -a "${replace}" = "${replace/${del}}" ] 149 | then # found one! 150 | found=0 151 | break 152 | fi 153 | done 154 | [ $found -eq 1 ] && fail "could not find a unique delimiter for sed; expand \$delimiters" 155 | } 156 | 157 | build_script() { 158 | SCRIPT="s${del}${regexp}${del}${replace}${del}${thirsty}" 159 | } 160 | 161 | wont_change() { 162 | # if sed prints zero lines, then $SCRIPT changes nothing 163 | chlines=$(sed -n -e "${SCRIPT}p" "$dest" | wc -l) 2>/dev/null 164 | [ $? -ne 0 ] && fail "error in \"$SCRIPT\" ?" 165 | 166 | # $chlines is also global 167 | return $chlines 168 | } 169 | 170 | modify_dest() { 171 | sed -i -e "${SCRIPT}" "$dest" 2>/dev/null 172 | [ $? -ne 0 ] && fail "failed modifying $dest with \"$SCRIPT\"" 173 | } 174 | 175 | main() { 176 | parse_args $args 177 | dest_writeable || fail "dest must be exist and be writeable" 178 | 179 | find_delimiter 180 | build_script 181 | 182 | if wont_change 183 | then ok "no changes" 184 | elif [ "$CHECKMODE" != "True" ] 185 | then 186 | modify_dest 187 | changed "$chlines lines(s) changed in $dest" $chlines 188 | else 189 | changed "$chlines lines(s) changed in $dest" $chlines 190 | fi 191 | } 192 | 193 | trap "fail 'We got killed!'" SIGINT SIGTERM 194 | args="$*" 195 | 196 | main 197 | 198 | -------------------------------------------------------------------------------- /lookup_plugins/hash_items.py: -------------------------------------------------------------------------------- 1 | # (c) 2013, Ali-Akber Saifee 2 | 3 | import ansible.errors as errors 4 | from ansible.utils import safe_eval 5 | 6 | 7 | class LookupModule(object): 8 | 9 | def __init__(self, basedir=None, **kwargs): 10 | self.basedir = basedir 11 | 12 | def run(self, terms, inject=None, **kwargs): 13 | terms = safe_eval(terms) 14 | 15 | if not isinstance(terms, dict): 16 | print terms 17 | raise errors.AnsibleError("with_hash_items expects a dict") 18 | return [{"key": term[0], "value": term[1]} for term in terms.items()] 19 | -------------------------------------------------------------------------------- /lookup_plugins/hashrays.py: -------------------------------------------------------------------------------- 1 | # Ansible lookup plugin for using hashes of arrays structures in loops 2 | # (c) 2014, Sebastien Bocahu 3 | 4 | # For each item of the value (array) of each hash item, returns the hash 5 | # key and the array item. Example: 6 | # 7 | # - authorized_keys: 8 | # root: 9 | # - admin1 10 | # www-data: 11 | # - dev1 12 | # - dev2 13 | # 14 | # - ssh_keys: 15 | # admin1: ssh-rsa ... 16 | # dev1: ssh-rsa ... 17 | # dev2: ssh-rsa ... 18 | # 19 | # - name: set authorized_keys 20 | # authorized_key: user="{{ item.key }}" key="{{ ssh_keys[item.value] }}" 21 | # with_hashrays: authorized_keys 22 | 23 | import ansible.utils as utils 24 | import ansible.errors as errors 25 | 26 | class LookupModule(object): 27 | 28 | def __init__(self, basedir=None, **kwargs): 29 | self.basedir = basedir 30 | 31 | def run(self, terms, inject=None, **kwargs): 32 | terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject) 33 | 34 | if not isinstance(terms, dict): 35 | raise errors.AnsibleError("with_hashrays expects a dict of arrays") 36 | 37 | ret = [] 38 | for k,v in terms.iteritems(): 39 | if isinstance(v, list): 40 | for vv in v: 41 | ret.append({'key': k, 'value': vv}) 42 | 43 | return ret 44 | -------------------------------------------------------------------------------- /vars_plugins/README.md: -------------------------------------------------------------------------------- 1 | ansible-plugins 2 | =============== 3 | 4 | My custom modules for ansible (https://github.com/ansible/ansible) configuration management: 5 | a multi-node deployment/orchestration, and remote task execution system. 6 | 7 | This repo is ordered by the subdirectories as mentioned in ansible's lib/ansible/utils/plugins.py 8 | ```python 9 | vars_loader = PluginLoader('VarsModule', 'ansible.inventory.vars_plugins', C.DEFAULT_VARS_PLUGIN_PATH, 'vars_plugins') 10 | ``` 11 | + `vars_plugins/` 12 | + group_vars_dirs.py: customized version of the original `lib/ansible/inventory/vars_plugins/group_vars.py` 13 | - With this plugin you can organise group and host vars files in various subdirectories, as you like. 14 | - Files directly beneath host_vars or group_vars are ignored, as those are handled by group_vars.py in core. 15 | - Files are named to the group name, and can haven optionally an extension .yml or .yaml 16 | - There can be only one (1) file per group or host, otherwise an error is thrown. 17 | 18 | -------------------------------------------------------------------------------- /vars_plugins/group_vars_dirs.py: -------------------------------------------------------------------------------- 1 | # (c) 2012, Michael DeHaan 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 . 17 | 18 | import os 19 | import glob 20 | from ansible import errors 21 | from ansible import utils 22 | import ansible.constants as C 23 | 24 | 25 | def vars_file_matches(f, name): 26 | # A vars file matches if either: 27 | # - the basename of the file equals the value of 'name' 28 | # - the basename of the file, stripped its extension, equals 'name' 29 | if os.path.basename(f) == name: 30 | return True 31 | elif os.path.basename(f) == '.'.join([name, 'yml']): 32 | return True 33 | elif os.path.basename(f) == '.'.join([name, 'yaml']): 34 | return True 35 | else: 36 | return False 37 | 38 | def vars_files(vars_dir, name): 39 | files = [] 40 | try: 41 | candidates = [os.path.join(vars_dir, f) for f in os.listdir(vars_dir)] 42 | except OSError: 43 | return files 44 | for f in candidates: 45 | if os.path.isfile(f) and (os.path.split(os.path.dirname(f))[1] 46 | not in ['group_vars', 'host_vars']) and vars_file_matches(f, name): 47 | files.append(f) 48 | elif os.path.isdir(f): 49 | files.extend(vars_files(f, name)) 50 | return sorted(files) 51 | 52 | 53 | class VarsModule(object): 54 | 55 | def __init__(self, inventory): 56 | self.inventory = inventory 57 | self.group_cache = {} 58 | 59 | def run(self, host, vault_password=None): 60 | # return the inventory variables for the host 61 | 62 | inventory = self.inventory 63 | #hostrec = inventory.get_host(host) 64 | 65 | groupz = sorted(inventory.groups_for_host(host.name), key=lambda g: g.depth) 66 | groups = [ g.name for g in groupz ] 67 | basedir = inventory.basedir() 68 | 69 | if basedir is None: 70 | # could happen when inventory is passed in via the API 71 | return 72 | 73 | results = {} 74 | 75 | # load vars in inventory_dir/group_vars/name_of_group 76 | for group in groups: 77 | if group in self.group_cache: 78 | results = self.group_cache[group] 79 | else: 80 | group_vars_dir = os.path.join(basedir, "group_vars") 81 | group_vars_files = vars_files(group_vars_dir, group) 82 | #if len(group_vars_files) > 1: 83 | # raise errors.AnsibleError("Found more than one file for group '%s': %s" 84 | # % (group, group_vars_files)) 85 | for path in group_vars_files: 86 | data = utils.parse_yaml_from_file(path, vault_password=vault_password) 87 | if type(data) != dict: 88 | raise errors.AnsibleError("%s must be stored as a dictionary/hash" % path) 89 | if C.DEFAULT_HASH_BEHAVIOUR == "merge": 90 | # let data content override results if needed 91 | results = utils.merge_hash(results, data) 92 | else: 93 | results.update(data) 94 | self.group_cache[group] = results 95 | 96 | # load vars in inventory_dir/hosts_vars/name_of_host 97 | host_vars_dir = os.path.join(basedir, "host_vars") 98 | host_vars_files = vars_files(host_vars_dir, host.name) 99 | if len(host_vars_files) > 1: 100 | raise errors.AnsibleError("Found more than one file for host '%s': %s" 101 | % (host.name, host_vars_files)) 102 | for path in host_vars_files: 103 | data = utils.parse_yaml_from_file(path, vault_password=vault_password) 104 | if type(data) != dict: 105 | raise errors.AnsibleError("%s must be stored as a dictionary/hash" % path) 106 | if C.DEFAULT_HASH_BEHAVIOUR == "merge": 107 | # let data content override results if needed 108 | results = utils.merge_hash(results, data) 109 | else: 110 | results.update(data) 111 | return results 112 | 113 | --------------------------------------------------------------------------------