#!/usr/bin/python # -*- coding: utf-8 -*- """ debops: run ansible-playbook with some customization """ # Copyright (C) 2014-2015 Hartmut Goebel # Part of the DebOps - https://debops.org/ # This program is free software; you can redistribute # it and/or modify it under the terms of the # GNU General Public License as published by the Free # Software Foundation; either version 3 of the License, # or (at your option) any later version. # # This program is distributed in the hope that it will # be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. See the GNU General Public # License for more details. # # You should have received a copy of the GNU General # Public License along with this program; if not, # write to the Free Software Foundation, Inc., 59 # Temple Place, Suite 330, Boston, MA 02111-1307 USA # # An on-line copy of the GNU General Public License can # be downloaded from the FSF web page at: # https://www.gnu.org/copyleft/gpl.html from __future__ import print_function import sys import os import ConfigParser import ansible from debops import * from debops.cmds import * __author__ = "Hartmut Goebel " __copyright__ = "Copyright 2014-2015 by Hartmut Goebel " __licence__ = "GNU General Public License version 3 (GPL v3) or later" PATHSEP = ':' ConfigFileHeader = """\ # Ansible configuration file generated by DebOps, all changes will be lost. # You can manipulate the contents of this file via `.debops.cfg`. """ def write_config(filename, config): cfgparser = ConfigParser.ConfigParser() for section, pairs in config.items(): cfgparser.add_section(section) for option, value in pairs.items(): cfgparser.set(section, option, value) with open(filename, "w") as fh: print(ConfigFileHeader, file=fh) cfgparser.write(fh) def gen_ansible_cfg(filename, config, project_root, playbooks_path, inventory_path): # Generate Ansible configuration file def custom_paths(type): if type in defaults: # prepend value from .debops.cfg yield defaults[type] yield os.path.join(project_root, "ansible", type) yield os.path.join(playbooks_path, type) yield os.path.join("/usr/share/ansible/", type) # Add custom configuration options to ansible.cfg: Take values # from [ansible ...] sections of the .debops.cfg file # Note: To set debops default values, use debops.config.DEFAULTS cfg = dict((sect.split(None, 1)[1], pairs) for sect, pairs in config.items() if sect.startswith('ansible ')) defaults = cfg.setdefault('defaults', {}) defaults['inventory'] = inventory_path defaults['roles_path'] = PATHSEP.join(filter(None, ( defaults.get('roles_path'), # value from .debops.cfg or None os.path.join(project_root, "roles"), os.path.join(project_root, "ansible", "roles"), os.path.join(playbooks_path, "..", "roles"), os.path.join(playbooks_path, "roles"), "/etc/ansible/roles"))) for plugin_type in ('action', 'callback', 'connection', 'filter', 'lookup', 'vars'): plugin_type = plugin_type+"_plugins" defaults[plugin_type] = PATHSEP.join(custom_paths(plugin_type)) if ansible.__version__ >= "1.7": # work around a bug obviously introduced in 1.7, see # https://github.com/ansible/ansible/issues/8555 if ' ' in defaults[plugin_type]: defaults[plugin_type] = PATHSEP.join( '"%s"' % p for p in defaults[plugin_type].split(PATHSEP)) defaults['library'] = PATHSEP.join(custom_paths('library')) write_config(filename, cfg) def main(cmd_args): project_root = find_debops_project(required=True) config = read_config(project_root) playbooks_path = find_playbookpath(config, project_root, required=True) # Make sure required commands are present require_commands('ansible-playbook') def find_playbook(playbook): tries = [ (project_root, "playbooks", playbook), (project_root, "ansible", "playbooks", playbook), (playbooks_path, playbook), ] if 'playbooks_path' in config['paths']: tries += [(custom_path, playbook) for custom_path in config['paths']['playbooks_path'].split(PATHSEP)] for parts in tries: play = os.path.join(*parts) if os.path.isfile(play): return play # Check if user specified a potential playbook name as the first # argument. If yes, use it as the playbook name and remove it from # the argument list play = None if len(cmd_args) > 0: maybe_play = cmd_args[0] if os.path.isfile(maybe_play): play = maybe_play else: play = find_playbook(maybe_play + ".yml") if play: cmd_args.pop(0) del maybe_play if not play: play = find_playbook("site.yml") inventory_path = find_inventorypath(project_root) os.environ['ANSIBLE_HOSTS'] = inventory_path ansible_config_file = os.path.join(project_root, ANSIBLE_CONFIG_FILE) os.environ['ANSIBLE_CONFIG'] = os.path.abspath(ansible_config_file) gen_ansible_cfg(ansible_config_file, config, project_root, playbooks_path, inventory_path) # Allow insecure SSH connections if requested if INSECURE: os.environ['ANSIBLE_HOST_KEY_CHECKING'] = 'False' # Create path to EncFS encrypted directory, based on inventory name encfs_encrypted = os.path.join(os.path.dirname(inventory_path), ENCFS_PREFIX + SECRET_NAME) # Check if encrypted secret directory exists and use it revert_unlock = padlock_unlock(encfs_encrypted) try: # Run ansible-playbook with custom environment print("Running Ansible playbook from:") print(play, "...") return subprocess.call(['ansible-playbook', play] + cmd_args) finally: if revert_unlock: padlock_lock(encfs_encrypted) try: sys.exit(main(sys.argv[1:])) except KeyboardInterrupt: raise SystemExit('... aborted')