181 lines
6.2 KiB
Plaintext
181 lines
6.2 KiB
Plaintext
|
#!/usr/bin/python
|
||
|
# -*- coding: utf-8 -*-
|
||
|
"""
|
||
|
debops: run ansible-playbook with some customization
|
||
|
"""
|
||
|
# Copyright (C) 2014-2015 Hartmut Goebel <h.goebel@crazy-compilers.com>
|
||
|
# 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 <h.goebel@crazy-compilers.com>"
|
||
|
__copyright__ = "Copyright 2014-2015 by Hartmut Goebel <h.goebel@crazy-compilers.com>"
|
||
|
__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')
|