scripts/debops

181 lines
6.2 KiB
Python
Executable File

#!/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')