def deploy(branch=None): ''' Deploy to remote source. ''' stage = shell.get_stage() deployer_user = shell.get_user() branch = branch or get_stage_config(stage)['branch'] commit = git.last_commit(short=True) notif.send(notification_types.DEPLOYMENT_STARTED, { 'user': deployer_user, 'branch': branch, 'commit': commit, 'stage': stage }) # Get the latest code from the repository sync(branch) install_dependencies() # Building the app build(stage) reload_service() notif.send(notification_types.DEPLOYMENT_FINISHED, { 'user': deployer_user, 'branch': branch, 'commit': commit, 'stage': stage }) remote_info('Deployment Completed')
def install_remote_dependencies(): ''' Install dependencies on the remote host. ''' remote_info('Installing dependencies on the remote') if runner.is_script_defined(known_scripts.INSTALL_REMOTE): runner.run_script(known_scripts.INSTALL_REMOTE) else: runner.run_script(known_scripts.INSTALL)
def deploy(branch=None): ''' Deploy to remote source. ''' stage = shell.get_stage() deployer_user = shell.get_user() branch = branch or resolve_deployment_branch(stage) commit = git.last_commit(remote=False, short=True) notif.send(notification_types.DEPLOYMENT_STARTED, { 'user': deployer_user, 'branch': branch, 'commit': commit, 'stage': stage }) with cd(get_repo_path()): runner.run_script_safely(known_scripts.PRE_DEPLOY) # Get the latest code from the repository sync(branch) install_dependencies() # Building the app build(stage) reload_service() runner.run_script_safely(known_scripts.POST_DEPLOY) notif.send(notification_types.DEPLOYMENT_FINISHED, { 'user': deployer_user, 'branch': branch, 'commit': commit, 'stage': stage }) remote_info('Deployment Completed')
def get_build_info(history, id): ''' Get the build information by build id. ''' if not history['builds']: remote_info('No build history recorded yet.') return None return get_build_by_id(history, id)
def deploy(branch=None): ''' Deploy to remote source. ''' stage = shell.get_stage() branch = branch or resolve_deployment_branch(stage) params = dict(user=shell.get_user(), stage=stage, branch=branch) notif.send(notification_types.DEPLOYMENT_STARTED, params) run_deploy_script(stage, branch) notif.send(notification_types.DEPLOYMENT_FINISHED, params) remote_info('Deployment Completed')
def get_current_build_index(history): ''' Get the current build index. ''' if not history['current']: remote_info('No current build found.') return None # Return the build index for the current build. current = history['current'] for i, build in enumerate(history['builds']): if build['id'] == current: return i return None
def rollback(id=None): ''' Deployment rollback to the previous build, or the build identified by the given id. ''' # TODO: Send rollback started notification (_, current_path) = setup_remote() history = load_history() # If the current build in the history is not set yet or # there aren't any previous builds on the history # rollback is not possible. if not history['current'] or not history['builds']: remote_info('Could not get the previous build to rollback.') return # If the rollback build id is not explicitly provided, # rollback to the previous build. if not id: current_index = get_current_build_index(history) # If current_index is None, or there are no builds before the current build # print the error since there are no previous builds to rollback. build_count = len(history['builds']) has_prev_build = 0 < current_index + 1 < build_count if current_index is None or not has_prev_build: remote_info('No previous builds found to rollback.') return # Get the previous build information. prev_build = history['builds'][current_index + 1] else: # Otherwise, if the id is provided then, get the build with that id prev_build = get_build_by_id(history, id) if not prev_build: remote_info('Build with id "{}" not found.'.format(id)) return remote_info('Rolling back to build {}'.format(prev_build['id'])) fs.update_symlink(prev_build['path'], current_path) # Save history and display it. history['current'] = prev_build['id'] save_history(history) display_list(history) # TODO: Send rollback completed notification. remote_info('Rollback successful')
def setup_remote(quiet=True): ''' Setup remote environment before we can proceed with the deployment process. ''' base_dir = get_deploy_dir() release_dir = get_release_dir() current_path = get_current_path() build_history_path = get_builds_file() preset = get_config()['deployment']['preset'] did_setup = False stage = shell.get_stage() # If the release directory does not exist, create it. if not fs.exists(release_dir): remote_info('Setting up {} server for {} deployment'.format( stage, preset)) remote_info('Creating the releases directory {}'.format( cyan(release_dir))) fs.mkdir(release_dir, nested=True) # Add build history file. remote_info('Creating new build meta file {}'.format( cyan(build_history_path))) save_history(merge(INITIAL_BUILD_HISTORY, {'preset': preset})) # Setup a default web page for web deployment. if preset == presets.WEB: setup_default_html(base_dir) did_setup = True if not did_setup and not quiet: remote_info('Remote already setup for deployment') return (release_dir, current_path)
def record_history(build_info): ''' Record a new build in the history. ''' config = get_config() keep_builds = int(config['deployment']['keep_builds']) build_history = load_history() build_history['current'] = build_info['id'] build_history['builds'].insert(0, build_info) build_history['builds'] = build_history['builds'][0:keep_builds] remote_info('Saving the build history') # Update build history json file save_history(build_history) # Delete the previous builds more than the value of `keep_builds`. delete_old_builds(build_history)
def delete_old_builds(history): ''' Auto delete unnecessary build directories from the filesystem. ''' build_path = get_release_dir() kept_builds = map(lambda x: get_build_name(x['id']), history['builds']) found_builds = fs.glob(build_path) to_be_deleted_builds = [x for x in found_builds if x not in kept_builds] deletion_count = len(to_be_deleted_builds) # Skip, if there are no builds to be deleted. if deletion_count == 0: return # Remove directories to be deleted. with cd(build_path): fs.rm_rf(to_be_deleted_builds) remote_info( 'Deleted {} old build(s) from the remote'.format(deletion_count))
def setup_default_html(base_dir): ''' Setup default html web page on the remote host. ''' current_path = base_dir + CURRENT_BUILD_LINK html_path = BASE_PATH + '/misc/default_html' remote_html_path = base_dir + DEFAULT_HTML_PATH remote_info('Setting up the default web page') fs.upload_dir(html_path, base_dir) # Point the current sym link to the default web page. fs.update_symlink(remote_html_path, current_path) remote_info('Remote is setup and is ready for deployment.') remote_print(('Deployed build will point to {0}.\n' + 'For serving the latest build, ' + 'please set your web server document root to {0}.').format( cyan(current_path)))
def display_list(history): ''' Display build history. ''' if not history['builds']: remote_info('No builds have been deployed yet.') return remote_info('Showing recent builds') # Map build data into tabular format data = map(row_mapper_wrt(history['current']), history['builds']) # Prepend heading rows data.insert(0, [' ', 'ID', 'Commit', 'Branch', 'Created By', 'Timestamp']) table = SingleTable(data) print('') print(table.table)
def deploy(branch=None): ''' Deploy to remote source. ''' stage = shell.get_stage() branch = branch or fallback_branch(stage) notif.send(notif.DEPLOYMENT_STARTED, { 'user': shell.get_user(), 'branch': branch, 'stage': stage }) # Get the latest code from the repository sync(branch) install_dependencies() # Building the app build(stage) reload_service() notif.send(notif.DEPLOYMENT_FINISHED, {'branch': branch, 'stage': stage}) remote_info('Deployment Completed')
def sync(branch=None): ''' Sync the changes on the branch with the remote (origin). ''' remote_info('Fetching the latest changes.') git.fetch() branch = branch or git.current_branch() remote_info('Checking out to {}.'.format(cyan(branch))) git.checkout(branch, True) remote_info('Synchronizing with the latest changes.') git.sync(branch)
def install_remote_dependencies(commit, current_path, smart_install): ''' Install dependencies on the remote host. ''' history = buildman.load_history() prev_build = buildman.get_prev_build_info(history) # Check if the installation could be skipped (smart_install). can_skip_installation = ( smart_install and prev_build and not has_updated_dependencies(prev_build['commit'], commit) ) # Smart install - copy the node_modules directory from the previous deployment # if it's usable (no dependencies or package manager files have changed). if can_skip_installation: runner.run( 'cp -R {src} {dest}'.format( src=os.path.join(prev_build['path'], 'node_modules'), dest=os.path.join(current_path, 'node_modules') ) ) remote_info('Skipping installation - No change in dependencies.') return # Install dependencies on the remote. with cd(current_path): remote_info('Installing dependencies on the remote') runner.run_script_safely(known_scripts.PRE_INSTALL) if runner.is_script_defined(known_scripts.INSTALL_REMOTE): runner.run_script_safely(known_scripts.PRE_INSTALL_REMOTE) runner.run_script(known_scripts.INSTALL_REMOTE) runner.run_script_safely(known_scripts.POST_INSTALL_REMOTE) else: runner.run_script_safely(known_scripts.INSTALL) runner.run_script_safely(known_scripts.POST_INSTALL)
def start_or_reload_service(has_started=False): ''' Start or reload the application service. ''' with cd(buildman.get_deploy_dir()): if runner.is_script_defined(known_scripts.START_OR_RELOAD): remote_info('Starting/Reloading the service.') runner.run_script(known_scripts.START_OR_RELOAD) elif has_started and runner.is_script_defined(known_scripts.RELOAD): remote_info('Reloading the service.') runner.run_script_safely(known_scripts.RELOAD) elif runner.is_script_defined(known_scripts.START): remote_info('Starting the service.') runner.run_script(known_scripts.START)
def deploy(): ''' Zero-Downtime deployment for the backend. ''' config = get_config() stage = shell.get_stage() is_remote_setup = buildman.is_remote_setup() is_first_deployment = not is_remote_setup if is_remote_setup and buildman.is_remote_up_to_date(): echo('Remote build is already up to date.') return branch = git.current_branch(remote=False) commit = git.last_commit(remote=False, short=True) info('Deploying <{branch}:{commit}> to the {stage} server'.format( branch=branch, commit=commit, stage=stage )) build_dir = os.path.abspath(buildman.resolve_local_build_dir()) included_files = config['deployment']['include_files'] deployer_user = shell.get_user() notif_params = dict( user=deployer_user, commit=commit, branch=branch, stage=stage ) notif.send(notification_types.DEPLOYMENT_STARTED, notif_params) runner.run_script_safely(known_scripts.PRE_DEPLOY) (release_dir, current_path) = buildman.setup_remote() timestamp = datetime.utcnow() build_id = timestamp.strftime('%Y%m%d%H%M%S') build_name = buildman.get_build_name(build_id) release_path = os.path.join(release_dir + '/' + build_name) dist_path = os.path.join(release_dir, build_name + '/dist') buildman.build(stage, config) uploader = BulkUploader() uploader.add(build_dir, dist_path) # Upload the files to be included eg: package.json file # to the remote build location. for filename in included_files: path = os.path.abspath(filename) # Add for upload if the file exist. if exists_local(path): uploader.add(path, release_path) uploader.upload() remote_info('Updating the current symlink') fs.update_symlink(release_path, current_path) # Once, the build is uploaded to the remote, # set things up in the remote server. # Change directory to the release path. install_remote_dependencies( commit=commit, current_path=current_path, smart_install=get_stage_config(stage)['deployment']['smart_install'] ) # Start or restart the application service. start_or_reload_service(is_first_deployment) # Save build history buildman.record_history({ 'id': build_id, 'path': release_path, 'branch': branch, 'commit': commit, 'stage': stage, 'createdBy': deployer_user, 'timestamp': timestamp.strftime(buildman.TS_FORMAT) }) runner.run_script_safely(known_scripts.POST_DEPLOY) # Send deployment finished notification. notif.send(notification_types.DEPLOYMENT_FINISHED, notif_params) info('Deployment Completed')
def deploy(): ''' Zero-Downtime deployment for the backend. ''' config = get_config() stage = shell.get_stage() is_first_deployment = not buildman.is_remote_setup() branch = git.current_branch(remote=False) commit = git.last_commit(remote=False, short=True) info('Deploying <{branch}:{commit}> to the {stage} server'.format( branch=branch, commit=commit, stage=stage)) tmp_path = fs.get_temp_filename() build_dir = buildman.resolve_local_build_dir() included_files = config['deployment']['include_files'] deployer_user = shell.get_user() notif.send(notification_types.DEPLOYMENT_STARTED, { 'user': deployer_user, 'commit': commit, 'branch': branch, 'stage': stage }) (release_dir, current_path) = buildman.setup_remote() timestamp = datetime.utcnow() build_id = timestamp.strftime('%Y%m%d%H%M%S') build_name = buildman.get_build_name(build_id) build_compressed = build_name + '.tar.gz' release_path = release_dir + '/' + build_name dist_path = build_name + '/dist' buildman.build(stage, config) info('Compressing the build') fs.tar_archive(build_compressed, build_dir, remote=False) info('Uploading the build {} to {}'.format(build_compressed, tmp_path)) fs.upload(build_compressed, tmp_path) # Remove the compressed build from the local directory. fs.rm(build_compressed, remote=False) # Once, the build is uploaded to the remote, # set things up in the remote server. with cd(release_dir): remote_info('Extracting the build {}'.format(build_compressed)) # Create a new directory for the build in the remote. fs.mkdir(dist_path, nested=True) # Extract the build. fs.tar_extract(tmp_path, dist_path) # Remove the uploaded archived from the temp path. fs.rm_rf(tmp_path) # Upload the files to be included eg: package.json file # to the remote build location. upload_included_files(included_files, release_path) remote_info('Pointing the current symlink to the latest build') fs.update_symlink(release_path, current_path) # Change directory to the release path. with cd(current_path): install_remote_dependencies() # Start or restart the application service. start_or_reload_service(is_first_deployment) # Save build history buildman.record_history({ 'id': build_id, 'path': release_path, 'branch': branch, 'commit': commit, 'stage': stage, 'createdBy': deployer_user, 'timestamp': timestamp.strftime(buildman.TS_FORMAT) }) # Send deployment finished notification. notif.send(notification_types.DEPLOYMENT_FINISHED, { 'user': deployer_user, 'branch': branch, 'commit': commit, 'stage': stage }) remote_info('Deployment Completed')
def stop_service(): ''' Stop the application service. ''' with cd(buildman.get_deploy_dir()): remote_info('Stopping the service.') runner.run_script_safely(known_scripts.STOP)
def reload_service(): ''' Restart the application service. ''' with cd(buildman.get_deploy_dir()): remote_info('Reloading the service.') runner.run_script_safely(known_scripts.RELOAD)
def deploy(): ''' Zero-Downtime deployment for the web. ''' config = get_config() stage = shell.get_stage() user = get_stage_config(stage)['user'] # Get the current branch and commit (locally). branch = git.current_branch(remote=False) commit = git.last_commit(remote=False, short=True) info('Deploying <{branch}:{commit}> to the {stage} server'.format( branch=branch, commit=commit, stage=stage)) tmp_path = fs.get_temp_filename() build_dir = buildman.resolve_local_build_dir() deploy_dir = buildman.get_deploy_dir() deployer_user = shell.get_user() notif.send(notification_types.DEPLOYMENT_STARTED, { 'user': deployer_user, 'branch': branch, 'commit': commit, 'stage': stage }) (release_dir, current_path) = buildman.setup_remote() timestamp = datetime.utcnow() build_id = timestamp.strftime('%Y%m%d%H%M%S') build_name = buildman.get_build_name(build_id) build_compressed = build_name + '.tar.gz' release_path = release_dir + '/' + build_name buildman.build(stage, config) info('Compressing the build') fs.tar_archive(build_compressed, build_dir, remote=False) info('Uploading the build {} to {}'.format(build_compressed, tmp_path)) fs.upload(build_compressed, tmp_path) # Remove the compressed build from the local directory. fs.rm(build_compressed, remote=False) # Once, the build is uploaded to the remote, # set things up in the remote server. with cd(release_dir): remote_info('Extracting the build {}'.format(build_compressed)) # Create a new directory for the build in the remote. fs.mkdir(build_name) # Extract the build. fs.tar_extract(tmp_path, build_name) # Remove the uploaded archived from the temp path. fs.rm_rf(tmp_path) remote_info('Changing ownership of {} to user {}'.format( deploy_dir, user)) fs.chown(release_path, user, user) remote_info('Pointing the current symlink to the latest build') fs.update_symlink(release_path, current_path) # Save build history buildman.record_history({ 'id': build_id, 'path': release_path, 'branch': branch, 'commit': commit, 'stage': stage, 'createdBy': deployer_user, 'timestamp': timestamp.strftime(buildman.TS_FORMAT) }) # Send deployment finished notification. notif.send(notification_types.DEPLOYMENT_FINISHED, { 'user': deployer_user, 'branch': branch, 'commit': commit, 'stage': stage }) remote_info('Deployment Completed')