def create_entity(entity_path, entity_type='file', protected=False): """ Creates directory/file and sets proper owner/mode. """ def change_owner(owner_triple): """ Changes unix owner/mode """ run('chmod u+rwx,g+rx,o-rwx ' + entity_path) run('chown -R %s:%s %s' % owner_triple) # 1) Create file/directory if entity_type == 'file': run('touch %s' % entity_path) else: run('mkdir -p %s' % entity_path) # 2) Change owner/mode if protected: protected_owner_user = fetch('protected_owner_user') protected_owner_group = fetch('protected_owner_group') change_owner( (protected_owner_user, protected_owner_group, entity_path)) else: owner_user = fetch('owner_user', default_value=False) owner_group = fetch('owner_group', default_value=False) if owner_user != False and owner_group != False: change_owner((owner_user, owner_group, entity_path)) else: run('chmod o-rwx ' + entity_path)
def rollback_release(): """ Rollbacks latest release """ ensure('current_path') releases_path = fetch('releases_path') with (settings(show('debug'))): with cd(releases_path): echo_subtask('Finding previous release for rollback') rollback_release_number = run( 'ls -1A | sort -n | tail -n 2 | head -n 1') if int(rollback_release_number) > 0: echo_subtask('Linking previous release to current') run('ln -nfs %s/%s %s' % (releases_path, rollback_release_number, fetch('current_path'))) echo_subtask('Finding latest release') current_release = run('ls -1A | sort -n | tail -n 1') if int(current_release) > 0: echo_subtask('Removing latest release') run('rm -rf %s/%s' % (releases_path, current_release)) else: abort('Can\'t find latest release for remove.') else: abort('Can\'t find previous release for rollback.')
def move_build_to_releases(): """ Moves current build folder to releases path """ ensure('build_to') ensure('release_to') echo_subtask('Moving from build path to release path') run('mv %s %s' % (fetch('build_to'), fetch('release_to')))
def link_release_to_current(): """ Creates current release symlink in `deploy_to` path """ ensure('current_path') release_to = fetch('release_to') echo_subtask("Linking release to current") with cd(release_to): run('ln -nfs %s %s' % (release_to, fetch('current_path')))
def fetch_new_commits(): """ Fetches new git commits """ ensure('scm') ensure('repository') ensure('branch') echo_subtask("Fetching new commits") with cd(fetch('scm')): run('git fetch {0} "{1}:{1}" --force'.format(fetch('repository'), fetch('branch')))
def use_git_branch(): """ Clones repository to build dir """ ensure('scm') ensure('branch') build_to = fetch('build_to') echo_subtask("Copying code from repository to build folder") run('git clone {0} {1} --recursive --branch {2}'.format( fetch('scm'), build_to, fetch('branch'))) run('rm -rf %s' % os.path.join(build_to, '.git'))
def add_repo_to_known_hosts(): """ Adds repository host to known hosts if possible """ maybe_repository = fetch('repository', default_value='') if maybe_repository: # Parse repo host repo_host_array = re.compile('(@|://)').split(maybe_repository) repo_host_part = repo_host_array[-1] if repo_host_array else '' repo_host_match = re.compile(':|\/').split(repo_host_part) repo_host = repo_host_match[0] if repo_host_match else '' # Exit if no host if repo_host == '': return # Parse port repo_port_match = re.search(r':([0-9]+)', maybe_repository) repo_port = repo_port_match.group(1) if bool(repo_port_match) else 22 echo_subtask('Adding repository to known hosts') add_to_known_hosts(repo_host, repo_port)
def discover_latest_release(): """ Connects to remote server and discovers next release number """ releases_path = fetch('releases_path') echo_subtask("Discovering latest release number") # Get latest release number with settings(hide('warnings'), warn_only=True): if run('test -d %s' % releases_path).failed: last_release_number = 0 create_entity(releases_path, entity_type='directory', protected=False) else: releases_respond = run('ls -1p %s | sed "s/\///g"' % releases_path) releases_dirs = list( filter(lambda x: len(x) != 0, releases_respond.split('\r\n'))) if len(releases_dirs) > 0: last_release_number = max(map(lambda x: int(x), releases_dirs)) or 0 else: last_release_number = 0 release_number = last_release_number + 1 # Set release info to config set('release_number', release_number) set('release_to', '/'.join([releases_path, str(release_number)]))
def create_shared_paths(): """ Creates shared (+protected) files/dirs and sets unix owner/mode """ global shared_path shared_path = fetch('shared_path') def create_dirs(directories, protected=False): global shared_path for sdir in directories: create_entity(os.path.join(shared_path, sdir), entity_type='directory', protected=protected) def create_files(files, protected=False): global shared_path for sfile in files: directory, filename_ = os.path.split(sfile) if directory: create_entity(os.path.join(shared_path, directory), entity_type='directory', protected=protected) filepath = os.path.join(shared_path, sfile) create_entity(filepath, entity_type='file', protected=protected) recommendation_tuple = (env.host_string, filepath) echo_status( '\n=====> Don\'t forget to update shared file:\n[%s] %s\n' % recommendation_tuple, error=True) echo_subtask('Creating shared paths') # Shared create_dirs(fetch('shared_dirs', default_value=[]), protected=False) create_files(fetch('shared_files', default_value=[]), protected=False) # Protected create_dirs(fetch('protected_shared_dirs', default_value=[]), protected=True) create_files(fetch('protected_shared_files', default_value=[]), protected=True)
def use_git_branch(): """ Clones repository to build dir """ ensure('scm') ensure('branch') build_to = fetch('build_to') echo_subtask("Copying code from repository to build folder") run('git clone {0} {1} --recursive --branch {2}'.format( fetch('scm'), build_to, fetch('branch'))) with settings(warn_only=True): run('rm -rf %s' % '/'.join([build_to, '.git']))
def force_unlock(): """ Forces a deploy unlock """ echo_subtask("Removing `deploy.lock` file") run('rm -f %s' % '/'.join([fetch('deploy_to'), 'deploy.lock']))
def check_lock(): """ Aborts deploy if lock file is found """ def run_abort(): abort("Another deploy in progress") echo_subtask("Checking `deploy.lock` file presence") with settings(hide('warnings'), warn_only=True): if run('test -f %s' % '/'.join([fetch('deploy_to'), 'deploy.lock'])).succeeded: if fetch('ask_unlock_if_locked', default_value=False): if not confirm('Deploy lockfile exists. Continue?'): run_abort() else: run_abort()
def print_deploy_stats(task_name, start_time): if fetch('verbose') == True: print_verbose() error_states = get_error_states() if error_states: echo_task('Deploy errors', error=True) for error_state in error_states: echo_comment(('\n[ERROR]\n%s' % state.get(error_state)), error=True) if state.get('success'): print(yellow('\n-----> Release number: %s' % fetch('release_number'))) print_stats_args = [task_name, start_time] if state.get('success') != True: print_stats_args.append(True) print_task_stats(*print_stats_args)
def cleanup_releases(): """ Cleans up old releases """ ensure('releases_path') releases_count = str(fetch('keep_releases')) echo_subtask("Cleaning up old realeses. Keeping latest %s" % releases_count) with cd(fetch('releases_path')), show('debug'): cmd = ''' count=$(ls -A1 | sort -rn | wc -l) remove=$((count > %s ? count - %s : 0)) ls -A1 | sort -rn | tail -n $remove | xargs rm -rf {}''' run(cmd % (releases_count, releases_count))
def remove_build_path(): """ Removes a temporary build dir """ echo_subtask("Removing build path") with settings(hide('stdout', 'warnings'), warn_only=True): builds_path = '/'.join([fetch('deploy_to'), 'tmp', 'build-*']) run('rm -rf %s' % builds_path)
def lock(): """ Locks deploy Parallel deploys are not allowed """ ensure('build_to') echo_subtask("Creating `deploy.lock` file") run('touch %s' % os.path.join(fetch('deploy_to'), 'deploy.lock'))
def maybe_clone_git_repository(): """ Clones bare git repository on first deploy """ ensure('repository') scm_path = fetch('scm') with settings(hide('warnings'), warn_only=True): echo_subtask('Ensuring git repository presence') if run('test -d %s' % scm_path).failed: create_entity(scm_path, entity_type='directory', protected=False) if run('test -f %s' % '/'.join([scm_path, 'HEAD'])).failed: echo_subtask("Cloning bare git repository") with settings(hide('output')): run('git clone {0} {1} --bare'.format(fetch('repository'), scm_path))
def lock(): """ Locks deploy Parallel deploys are not allowed """ ensure('build_to') echo_subtask("Creating `deploy.lock` file") create_entity('/'.join([fetch('deploy_to'), 'deploy.lock']), entity_type='file', protected=False)
def cleanup_releases(): """ Cleans up old releases """ ensure('releases_path') ensure('keep_releases') releases_count = str(fetch('keep_releases')) if (releases_count == "-1"): return echo_subtask("Cleaning up old realeses. Keeping latest %s" % releases_count) with cd(fetch('releases_path')), show('debug'): use_sudo = 'sudo' if fetch('sudo_on_cleanup_releases') else '' cmd = ''' count=$(ls -A1 | sort -rn | wc -l) remove=$((count > %s ? count - %s : 0)) ls -A1 | sort -rn | tail -n $remove | xargs %s rm -rf {}''' run(cmd % (releases_count, releases_count, use_sudo))
def create_required_structure(): """ Creates required folders (tmp, releases, shared) in `deploy_to` path """ deploy_to = fetch('deploy_to') echo_subtask('Creating required structure') create_entity(deploy_to, entity_type='directory', protected=False) for required_path in ['shared', 'releases', 'tmp']: create_entity(os.path.join(deploy_to, required_path), entity_type='directory', protected=False)
def create_build_path(): """ Creates tmp dir for building app. Folder will be: * deleted -> if build fails * moved to releases -> if build succeeds """ ensure('build_to') echo_subtask("Creating build path") with settings(hide('warnings'), warn_only=True): build_path = fetch('build_to') if run('test -d %s' % build_path).failed: create_entity(build_path, entity_type='directory', protected=False)
def create_entity(entity_path, entity_type='file', protected=False): """ Creates directory/file and sets proper owner/mode. """ chmod_sudo = 'sudo ' if fetch('sudo_on_chmod') else '' chown_sudo = 'sudo ' if fetch('sudo_on_chown') else '' def change_owner(owner_triple): """ Changes unix owner/mode """ run(chmod_sudo + 'chmod 0755 ' + entity_path) run(chown_sudo + 'chown %s:%s %s' % owner_triple) # 1) Create file/directory if entity_type == 'file': run('touch %s' % entity_path) else: run('mkdir -p %s' % entity_path) # 2) Change owner/mode if protected: protected_owner_user = fetch('protected_owner_user') protected_owner_group = fetch('protected_owner_group') change_owner( (protected_owner_user, protected_owner_group, entity_path)) else: owner_user = fetch('owner_user', default_value=False) owner_group = fetch('owner_group', default_value=False) if owner_user != False and owner_group != False: change_owner((owner_user, owner_group, entity_path)) else: run(chmod_sudo + 'chmod 0755 ' + entity_path)
def link_shared_paths(): """ Links shared paths to build folder """ global build_to build_to = fetch('build_to') global shared shared = fetch('shared_path') def link_dirs(dirs): global shared global build_to for sdir in dirs: relative_path = '/'.join(['.', sdir]) directory, filename_ = os.path.split(relative_path) shared_path = '/'.join([shared, sdir]) with cd(build_to): create_entity(directory, entity_type='directory', protected=False) # create parent directory run('rm -rf %s' % relative_path ) # remove directory if it conficts with shared run('ln -s %s %s' % (shared_path, relative_path)) # link shared to current folder def link_files(files): global shared global build_to for sfile in files: relative_path = '/'.join(['.', sfile]) directory, filename_ = os.path.split(relative_path) shared_path = '/'.join([shared, sfile]) with cd(build_to): create_entity(directory, entity_type='directory', protected=False) # create parent directory run('ln -sf %s %s' % (shared_path, relative_path)) # link shared to current folder shared_dirs = fetch('shared_dirs', default_value=[]) shared_files = fetch('shared_files', default_value=[]) any_shared_dir = len(shared_dirs) > 0 any_shared_file = len(shared_files) > 0 protected_shared_dirs = fetch('protected_shared_dirs', default_value=[]) protected_shared_files = fetch('protected_shared_files', default_value=[]) any_protected_shared_dir = len(protected_shared_dirs) > 0 any_protected_shared_file = len(protected_shared_files) > 0 if any_shared_dir or any_shared_file or any_protected_shared_dir or any_protected_shared_file: echo_subtask("Linking shared paths") if any_shared_dir: link_dirs(shared_dirs) if any_shared_file: link_files(shared_files) if any_protected_shared_dir: link_dirs(protected_shared_dirs) if any_protected_shared_file: link_files(protected_shared_files)