Remove debops* bin…

This commit is contained in:
Jeremy Gardais 2018-01-15 16:43:05 +01:00
parent b0d36ad389
commit 2eafc4907e
6 changed files with 0 additions and 1004 deletions

180
debops
View File

@ -1,180 +0,0 @@
#!/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')

View File

@ -1,111 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
debops-defaults: aggregate all defaults from Ansible roles into one stream
"""
# 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 os
import sys
import codecs
import subprocess
import glob
import argparse
import errno
reload(sys)
sys.setdefaultencoding('utf-8')
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"
def cat(filename, outstream):
try:
fh = codecs.open(filename, encoding=sys.getdefaultencoding())
except IOError, e:
# This should only happen if the user listed a unknown role.
outstream.write('%s: %s\n' % (e.strerror, e.filename))
return
try:
# Read input file as Unicode object and pass it to outstream.
outstream.write(fh.read())
finally:
fh.close()
def aggregate_defaults(playbooks_path, role_list, outstream):
# Aggregate role/defaults/main.yml files from all roles into one stream
roles_path = os.path.normpath(os.path.join(playbooks_path, '..', 'roles'))
if role_list:
for role in role_list:
if not '.' in role:
role = ROLE_PREFIX + '.' + role
fn = os.path.join(roles_path, role, 'defaults', 'main.yml')
cat(fn, outstream=outstream)
else:
for fn in glob.glob(os.path.join(roles_path,
'*', 'defaults', 'main.yml')):
cat(fn, outstream=outstream)
# ---- DebOps environment setup ----
def main(role_list):
project_root = find_debops_project(required=False)
config = read_config(project_root)
playbooks_path = find_playbookpath(config, project_root, required=True)
# Make sure required commands are present
require_commands('view')
if sys.stdout.isatty():
# if script is run as standalone, redirect to view
view = subprocess.Popen(['view', '+set ft=yaml', '-'],
stdin=subprocess.PIPE)
try:
aggregate_defaults(playbooks_path, role_list, view.stdin)
except IOError, e:
if e.errno not in (errno.EPIPE, errno.EINVAL):
# "Invalid pipe" or "Invalid argument"
raise
finally:
view.communicate()
else:
# else, send everything to stdout
aggregate_defaults(playbooks_path, role_list, sys.stdout)
parser = argparse.ArgumentParser()
parser.add_argument('role', nargs='*')
args = parser.parse_args()
try:
main(args.role)
except KeyboardInterrupt:
raise SystemExit('... aborted')

View File

@ -1,205 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
debops-init: create a new DebOps project directory
"""
# 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 os
import codecs
import subprocess
import glob
import argparse
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"
SKEL_DIRS = (
os.path.join("ansible", INVENTORY, "group_vars", "all"),
os.path.join("ansible", INVENTORY, "host_vars"),
os.path.join("ansible", "playbooks"),
os.path.join("ansible", "roles"),
)
DEFAULT_DEBOPS_CONFIG = """
# -*- conf -*-
[paths]
;data-home: /opt/debops
[ansible defaults]
display_skipped_hosts = False
retry_files_enabled = False
;callback_plugins = /my/plugins/callback
;roles_path = /my/roles
[ansible paramiko]
;record_host_keys=True
[ansible ssh_connection]
;ssh_args = -o ControlMaster=auto -o ControlPersist=60s
"""
DEFAULT_GITIGNORE = """\
ansible/{SECRET_NAME}
{SECRET_NAME}
{ENCFS_PREFIX}{SECRET_NAME}
ansible.cfg
#-- python
*.py[co]
#-- vim
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
*.un~
Session.vim
.netrwhist
*~
#-- Emacs
\#*\#
/.emacs.desktop
/.emacs.desktop.lock
*.elc
auto-save-list
tramp
.\#*
#-- SublimeText
*.sublime-workspace
#*.sublime-project
#-- sftp configuration file
sftp-config.json
"""
HOSTS_FILE_HEADER = """\
# This is an Ansible inventory file in INI format. You can define a list of
# hosts and groups to be managed by this particular inventory.
# Hosts listed under [debops_all_hosts] will have common DebOps plays
# ran against them. It will include services such as iptables, DNS, Postfix,
# sshd configuration and more.
#
# View the list here:
# https://github.com/debops/debops-playbooks/blob/master/playbooks/common.yml
#
# You should check Getting Started guide for useful suggestions:
# https://docs.debops.org/en/latest/debops-playbooks/docs/guides/getting-started.html
"""
HOSTS_FILE_CONTENT_CONTROLER = """
# Your host is eligible to be managed by DebOps' common playbook. If you want
# that functionality and more, then uncomment your hostname below.
[debops_all_hosts]
#%s ansible_connection=local
""" % platform.node()
HOSTS_FILE_CONTENT_NO_CONTROLER = """
# Your host was not detected as compatible with DebOps playbooks, so you will
# not be able to leverage the above features on your current operating system.
# You can however use a virtual machine as the Ansible Controller.
[debops_all_hosts]
"""
def write_file(filename, *content):
"""
If file:`filename` does not exist, create it and write
var:`content` into it.
"""
if not os.path.exists(filename):
with open(filename, "w") as fh:
fh.writelines(content)
def write_config_files(project_root):
"""
Create the default debops-config files in the dir:`project_root`
directory.
"""
# Create .debops.cfg
write_file(os.path.join(project_root, DEBOPS_CONFIG), DEFAULT_DEBOPS_CONFIG)
# Create .gitignore
write_file(os.path.join(project_root, '.gitignore'),
DEFAULT_GITIGNORE.format(SECRET_NAME=SECRET_NAME, ENCFS_PREFIX=ENCFS_PREFIX))
hosts_filename = os.path.join(project_root, "ansible", INVENTORY, "hosts")
# Swap in different hosts file content depending on the host's OS/distro
if (platform.system() == "Linux" and
platform.linux_distribution()[0].lower() in ("debian", "ubuntu")):
write_file(hosts_filename,
HOSTS_FILE_HEADER, HOSTS_FILE_CONTENT_CONTROLER)
else:
write_file(hosts_filename,
HOSTS_FILE_HEADER, HOSTS_FILE_CONTENT_NO_CONTROLER)
def main(project_root):
orig_project_root = project_root
project_root = os.path.abspath(project_root)
#-- Check for existing debops project directory
debops_project_root = find_debops_project(project_root, required=False)
# Exit if DebOps configuration file has been found in project_dir
if os.path.exists(os.path.join(project_root, DEBOPS_CONFIG)):
error_msg("%s is already a DebOps project directory" % project_root)
# Exit if we are in a DebOps project dir and nested project would be created
if debops_project_root:
error_msg("You are inside %s project already" % debops_project_root)
#-- Main script
print("Creating new DebOps project directory in", orig_project_root, "...")
# Create base project directories
for skel_dir in SKEL_DIRS:
skel_dir = os.path.join(project_root, skel_dir)
if not os.path.isdir(skel_dir):
os.makedirs(skel_dir)
# Write default config files
write_config_files(project_root)
parser = argparse.ArgumentParser()
parser.add_argument('project_dir', default=os.curdir)
args = parser.parse_args()
try:
main(args.project_dir)
except KeyboardInterrupt:
raise SystemExit('... aborted')

View File

@ -1,187 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
debops-padlock: encrypt secret directory with EncFS and GPG
"""
# 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 os
import shutil
import argparse
import itertools
import stat
import sys
import time
from pkg_resources import resource_filename
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"
def gen_pwd():
from string import ascii_letters, digits, punctuation
import random
ALLCHARS = digits + ascii_letters + punctuation
ALLCHARS = digits + ascii_letters + '-_!@#$%^&*()_+{}|:<>?='
pwd = ''.join(random.choice(ALLCHARS) for i in range(ENCFS_KEYFILE_LENGTH))
return pwd
# Randomness source for EncFS keyfile generation
devrandom = os.environ.get('DEVRANDOM', "/dev/urandom")
SCRIPT_FILENAME = 'padlock-script'
# ---- DebOps environment setup ----
def main(subcommand_func, **kwargs):
project_root = find_debops_project(required=True)
# :todo: Source DebOps configuration file
#[ -r ${debops_config} ] && source ${debops_config}
# ---- Main script ----
# Make sure required commands are present
require_commands('encfs', 'find', 'fusermount', 'gpg')
inventory_path = find_inventorypath(project_root, required=False)
# If inventory hasn't been found automatically, assume it's the default
if not inventory_path:
inventory_path = os.path.join(project_root, 'ansible', INVENTORY)
# Create names of EncFS encrypted and decrypted directories, based on
# inventory name (absolute paths are specified)
encfs_encrypted = os.path.join(os.path.dirname(inventory_path),
ENCFS_PREFIX + SECRET_NAME)
encfs_decrypted = os.path.join(os.path.dirname(inventory_path),
SECRET_NAME)
subcommand_func(encfs_decrypted, encfs_encrypted, **kwargs)
def init(encfs_decrypted, encfs_encrypted, recipients):
# EncFS cannot create encrypted directory if directory with
# decrypted data is not empty
if not os.path.exists(encfs_decrypted):
os.makedirs(encfs_decrypted)
elif os.listdir(encfs_decrypted):
error_msg("secret directory not empty")
# Quit if encrypted directory already exists.
if os.path.isdir(encfs_encrypted):
error_msg("EncFS directory already exists")
os.makedirs(encfs_encrypted)
encfs_keyfile = os.path.join(encfs_encrypted, ENCFS_KEYFILE)
encfs_configfile = os.path.join(encfs_encrypted, ENCFS_CONFIGFILE)
# put a `-r` in front of each recipient for passing as args to gpg
recipients = list(itertools.chain.from_iterable(['-r', r]
for r in recipients))
# Generate a random password and encrypt it with GPG keys of recipients.
print("Generating a random", ENCFS_KEYFILE_LENGTH, "char password")
pwd = gen_pwd()
gpg = subprocess.Popen(['gpg', '--encrypt', '--armor',
'--output', encfs_keyfile] + recipients,
stdin=subprocess.PIPE)
gpg.communicate(pwd)
# Mount the encfs to the config file will be written. Tell encfs
# it to ask gpg for the password.
# NB1: Alternativly we could use --stdinpass, but using --extpass makes
# the user check if she has the correct passphrase early.
# NB2: We can not use padlock_unlock here, because the config file
# does not yet exist.
encfs = subprocess.Popen([
'encfs', encfs_encrypted, encfs_decrypted,
'--extpass', 'gpg --no-mdc-warning --output - '+shquote(encfs_keyfile)],
stdin=subprocess.PIPE)
encfs.communicate('p\n'+pwd)
# Create padlock-script
padlock_script = os.path.join(encfs_encrypted, PADLOCK_CMD)
# :todo: use resource_stream
shutil.copy(resource_filename('debops', SCRIPT_FILENAME), padlock_script)
os.chmod(padlock_script,
os.stat(padlock_script).st_mode|stat.S_IXUSR|stat.S_IXGRP|stat.S_IXOTH)
# Lock the EncFS directory after creation
time.sleep(0.5) # :fixme: why sleeping here?
padlock_lock(encfs_encrypted)
# Protect the EncFS configuration file by also encrypting it with
# the GPG keys of recipients.
subprocess.call(['gpg', '--encrypt', '--armor',
'--output', encfs_configfile+'.asc']
+ recipients + [encfs_configfile])
os.remove(encfs_configfile)
def lock(encfs_decrypted, encfs_encrypted, verbose):
# Unmount the directory if it is mounted
if padlock_lock(encfs_encrypted):
if verbose: print("Locked!")
else:
if verbose: print("Is already locked.")
def unlock(encfs_decrypted, encfs_encrypted, verbose):
# Mount the directory it if it is unmounted
if padlock_unlock(encfs_encrypted):
if verbose: print("Unlocked!")
else:
if verbose: print("Is already unlocked.")
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(
help='action to perform. Use `%(prog)s --help <action>` for further help.')
p = subparsers.add_parser('init')
p.add_argument('recipients', nargs='*',
help=("GPG recipients for which the secret key should be "
"encrypted for (name, e-mail or key-id)"))
p.set_defaults(subcommand_func=init)
p = subparsers.add_parser('unlock')
p.add_argument('-v', '--verbose', action='store_true', help="be verbose")
p.set_defaults(subcommand_func=unlock)
p = subparsers.add_parser('lock')
p.add_argument('-v', '--verbose', action='store_true', help="be verbose")
p.set_defaults(subcommand_func=lock)
args = parser.parse_args()
try:
main(**vars(args))
except KeyboardInterrupt:
raise SystemExit('... aborted')

View File

@ -1,74 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
debops-task: run ansible 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
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"
DEBOPS_RESERVED_NAMES = ["task", "init", "update", "defaults", "padlock"]
# ---- DebOps environment setup ----
# Find DebOps configuration file
project_root = find_debops_project(required=True)
# Source DebOps configuration file
###----- todo: need to decide on semantics!
#config = read_config(project_root)
# ---- Main script ----
# Make sure required commands are present
require_commands('ansible')
ansible_inventory = find_inventorypath(project_root)
# Get module name from the script name if script is named 'debops-*'
module_name = SCRIPT_NAME.rsplit('-', 1)[-1]
if module_name not in DEBOPS_RESERVED_NAMES:
module = ["-m", module_name]
else:
module = []
os.environ['ANSIBLE_HOSTS'] = os.path.abspath(ansible_inventory)
# Allow insecure SSH connections if requested
if INSECURE:
os.environ['ANSIBLE_HOST_KEY_CHECKING'] = 'False'
# Run ansible with custom environment
cmd = ['ansible'] + module + sys.argv[1:]
subprocess.call(cmd)

View File

@ -1,247 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
("""
debops-update: install or update DebOps playbooks and roles
"""
# 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
"""
This script can be used to install or update installed DebOps playbooks and
roles to current or specified version. By default it works on the installed
playbook in users $HOME/.local/share/debops directory, but it can also be
used on locally installed playbooks and roles in current directory.
Short usage guide:
- 'debops-update' will check if we are in DebOps project directory
('.debops.cfg' exists)
* if yes, it will check if 'debops-playbooks/playbooks/site.yml' exists
* if yes, update playbooks and roles in $PWD
* if no, check if DebOps playbooks are installed in known places,
like ~/.local/share/debops
* if yes, update playbooks in a place that they are installed at
* if no, install DebOps playbooks in
~/.local/share/debops/debops-playbooks
- 'debops-update path/to/dir' will check if specified directory exists
* if no, create it
* if yes, check if DebOps playbooks are installed at $path/debops-playbooks
* if yes, update them
* if no, install DebOps playbooks at $path/debops-playbooks
""")
from __future__ import print_function
import os
import subprocess
import argparse
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"
# ---- Configuration variables ----
# Default URI of DebOps (user https for server authentication)
GIT_URI = "https://github.com/debops"
# Default git sources for debops-playbooks
PLAYBOOKS_GIT_URI = GIT_URI + "/debops-playbooks"
# Default slug prefix for roles
GIT_ROLE_PREFIX = "ansible-"
# Ansible Galaxy requirements file to use by default to download or update
GALAXY_REQUIREMENTS = "galaxy/requirements.txt"
# Default Ansible Galaxy user account name
GALAXY_ACCOUNT = "debops"
# ---- Functions ----
def fetch_or_clone_roles(roles_path, requirements_file, dry_run=False):
"""
Efficiently fetch or clone a role
"""
with open(requirements_file) as fh:
requirements = [r.strip().split() for r in fh.readlines()]
num_roles = len(requirements)
for cnt, role_name in enumerate(requirements, 1):
# Parse the requirements.txt file to extract the role name and version
try:
role_name, role_version = role_name[:2]
except:
role_name = role_name[0]
role_version = 'master'
# :todo: rethink if we really want this
if role_name.startswith(GALAXY_ACCOUNT + '.'):
galaxy_name = role_name
role_name = role_name.split('.', 1)[1]
else:
galaxy_name = GALAXY_ACCOUNT + '.' + role_name
remote_uri = GIT_URI + '/' + GIT_ROLE_PREFIX + role_name
destination_dir = os.path.join(roles_path, galaxy_name)
progress_label="[{role_version}] ({cnt}/{num_roles})".format(**locals())
# Either update or clone the role
if os.path.exists(destination_dir):
print("Updating", remote_uri, progress_label)
update_git_repository(destination_dir, dry_run, remote_uri)
else:
print()
print("Installing", remote_uri, progress_label)
clone_git_repository(remote_uri, role_version, destination_dir, dry_run)
print()
def clone_git_repository(repo_uri, branch, destination, dry_run=False):
if dry_run:
print("Cloning '%s' to %s..." % (repo_uri, destination))
else:
subprocess.call(['git', 'clone', '--quiet', '--branch', branch,
repo_uri, destination])
def update_git_repository(path, dry_run=False, remote_uri=False):
"""
Update an exiting git repository.
To get nice output, merge only of origin as updates.
"""
# Move into the role's directory
old_pwd = os.getcwd()
os.chdir(path)
if dry_run:
subprocess.call(['git', 'fetch'])
subprocess.call(['git', 'diff', 'HEAD', 'origin', '--stat'])
else:
# Get the current sha of the head branch
current_sha = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip()
# Fetch it silently and store the new sha
subprocess.call(['git', 'fetch', '--quiet'])
fetch_sha = subprocess.check_output(['git', 'rev-parse', 'FETCH_HEAD']).strip()
if current_sha != fetch_sha:
print()
print('--')
subprocess.call(['git', 'merge', fetch_sha])
if remote_uri:
compare_uri = remote_uri + '/compare/' + current_sha[:7] + '...' + fetch_sha[:7]
print()
print("Compare:", compare_uri)
print('--')
print()
# Move back to the initial directory
os.chdir(old_pwd)
# ---- Main script ----
def main(project_dir=None, dry_run=False):
# Check if user specified a directory as a parameter, if yes, use it as
# a project directory and clone DebOps playbooks inside
if project_dir:
# If it's a new project, create the directory for it
if not os.path.exists(project_dir):
print ("Creating project directory in", project_dir)
if not dry_run:
os.makedirs(project_dir)
# Make sure that playbooks and roles will be installed in project
# directory if it's specified
install_path = os.path.join(project_dir, "debops-playbooks")
# If playbooks already are installed in specified location, set them as
# currently used for eventual update
if os.path.isfile(os.path.join(install_path, DEBOPS_SITE_PLAYBOOK)):
playbooks_path = install_path
else:
playbooks_path = None
else:
# If there's no project specified, look for playbooks in known locations
project_root = find_debops_project(required=False)
config = read_config(project_root)
playbooks_path = find_playbookpath(config, project_root, required=False)
if playbooks_path:
install_path = os.path.dirname(playbooks_path)
else:
install_path = config['paths']['install-path']
roles_path = os.path.join(install_path, 'roles')
# ---- Create or update the playbooks and roles ----
# Playbooks have not been found, at this point assume playbooks are not
# installed. Install them in user home directory
if not playbooks_path:
if dry_run:
raise SystemExit("--dry-run requires DebOps playbooks.\n" \
"Run debops-update without --dry-run first.")
# Download/clone main debops-playbooks repository
print("DebOps playbooks have not been found, installing into",
install_path)
print()
clone_git_repository(PLAYBOOKS_GIT_URI, 'master', install_path, dry_run)
os.chdir(install_path)
os.makedirs(roles_path)
else:
# Update found debops-playbooks repository
print("DebOps playbooks have been found in", install_path)
update_git_repository(install_path, dry_run)
print()
os.chdir(install_path)
# Now install or update the roles into roles_path
fetch_or_clone_roles(roles_path, GALAXY_REQUIREMENTS, dry_run)
parser = argparse.ArgumentParser()
parser.add_argument('--dry-run', action='store_true',
help='perform a trial run with no changes made')
parser.add_argument('project_dir', nargs='?')
args = parser.parse_args()
try:
main(args.project_dir, args.dry_run)
except KeyboardInterrupt:
raise SystemExit('... aborted')