248 lines
8.5 KiB
Python
Executable File
248 lines
8.5 KiB
Python
Executable File
#!/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')
|