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')
 |