def paasta_rollback(args): """Call mark_for_deployment with rollback parameters :param args: contains all the arguments passed onto the script: service, deploy groups and sha. These arguments will be verified and passed onto mark_for_deployment. """ service = figure_out_service_name(args) git_url = get_git_url(service) commit = args.commit given_deploy_groups = [deploy_group for deploy_group in args.deploy_groups.split(",") if deploy_group] service_deploy_groups = set(config.get_deploy_group() for config in get_instance_config_for_service( soa_dir=DEFAULT_SOA_DIR, service=service, )) deploy_groups, invalid = validate_given_deploy_groups(service_deploy_groups, given_deploy_groups) if len(invalid) > 0: print PaastaColors.yellow("These deploy groups are not valid and will be skipped: %s.\n" % (",").join(invalid)) if len(deploy_groups) == 0: print PaastaColors.red("ERROR: No valid deploy groups specified for %s.\n" % (service)) returncode = 1 for deploy_group in deploy_groups: returncode = mark_for_deployment( git_url=git_url, service=service, deploy_group=deploy_group, commit=commit, ) sys.exit(returncode)
def get_service_info(service): service_configuration = read_service_configuration(service) description = service_configuration.get('description', NO_DESCRIPTION_MESSAGE) external_link = service_configuration.get('external_link', NO_EXTERNAL_LINK_MESSAGE) pipeline_url = get_pipeline_url(service) smartstack_endpoints = get_smartstack_endpoints(service) git_url = get_git_url(service) output = [] output.append('Service Name: %s' % service) output.append('Description: %s' % description) output.append('External Link: %s' % PaastaColors.cyan(external_link)) output.append('Monitored By: team %s' % get_team(service=service, overrides={})) output.append('Runbook: %s' % PaastaColors.cyan(get_runbook(service=service, overrides={}))) output.append('Git Repo: %s' % git_url) output.append('Jenkins Pipeline: %s' % pipeline_url) output.append('Deployed to the following clusters:') output.extend(get_deployments_strings(service)) if smartstack_endpoints: output.append('Smartstack endpoint(s):') for endpoint in smartstack_endpoints: output.append(' - %s' % endpoint) output.append('Dashboard(s):') output.extend(get_dashboard_urls(service)) return '\n'.join(output)
def issue_state_change_for_service(service_config, force_bounce, desired_state): ref_mutator = make_mutate_refs_func( service_config=service_config, force_bounce=force_bounce, desired_state=desired_state, ) remote_git.create_remote_refs(utils.get_git_url(service_config.get_service()), ref_mutator) log_event( service_config=service_config, desired_state=desired_state, ) if isinstance(service_config, MarathonServiceConfig): if desired_state == 'start': extra_message = ( "A 'start' command will signal to Marathon that the service should have the normal " "instance count. A restart will cause new tasks to replace the old ones gracefully." ) elif desired_state == 'stop': extra_message = ( "A 'stop' command will signal to Marathon that the service should be in Marathon, " "but scaled down to 0 instances gracefully. Use 'paasta start' or make a new deployment to " "make the service start back up." ) elif isinstance(service_config, ChronosJobConfig): if desired_state == 'start': extra_message = ( "'Start' will tell Chronos to start scheduling the job. " "If you need the job to start regardless of the schedule, use 'paasta emergency-start'." ) elif desired_state == 'stop': extra_message = ( "'Stop' for a Chronos job will cause the job to be disabled until the " "next deploy or a 'start' command is issued." ) print extra_message
def paasta_start_or_stop(args, desired_state): """Requests a change of state to start or stop given branches of a service.""" instance = args.instance cluster = args.cluster soa_dir = args.soa_dir service = figure_out_service_name(args=args, soa_dir=soa_dir) service_config = get_instance_config( service=service, cluster=cluster, instance=instance, soa_dir=soa_dir, load_deployments=False, ) remote_refs = remote_git.list_remote_refs(utils.get_git_url(service)) if 'refs/heads/paasta-%s' % service_config.get_deploy_group() not in remote_refs: print "No branches found for %s in %s." % \ (service_config.get_deploy_group(), remote_refs) print "Has it been deployed there yet?" sys.exit(1) force_bounce = utils.format_timestamp(datetime.datetime.utcnow()) issue_state_change_for_service( service_config=service_config, force_bounce=force_bounce, desired_state=desired_state, )
def get_branches(service): paasta_branches = set(get_branches_for_service(SOA_DIR, service)) remote_refs = remote_git.list_remote_refs(utils.get_git_url(service)) for branch in paasta_branches: if 'refs/heads/%s' % branch in remote_refs: yield branch
def git_repo_check(service): git_url = get_git_url(service) cmd = 'git ls-remote %s' % git_url returncode, _ = _run(cmd, timeout=5) if returncode == 0: print PaastaCheckMessages.GIT_REPO_FOUND else: print PaastaCheckMessages.git_repo_missing(git_url)
def get_branches(service, soa_dir): paasta_control_branches = set(('paasta-%s' % config.get_branch() for config in get_instance_config_for_service(soa_dir, service))) remote_refs = remote_git.list_remote_refs(utils.get_git_url(service)) for branch in paasta_control_branches: if 'refs/heads/%s' % branch in remote_refs: yield branch
def get_git_repo_for_fab_repo(service): """Returns the 'repo' in fab_repo terms. fab_repo just wants the trailing section of the git_url, after the colon. """ git_url = get_git_url(service) validate_git_url_for_fab_repo(git_url) repo = git_url.split(':')[1] return repo
def issue_state_change_for_branches(service, instance, cluster, branches, force_bounce, desired_state): ref_mutator = make_mutate_refs_func( branches=branches, force_bounce=force_bounce, desired_state=desired_state ) remote_git.create_remote_refs(utils.get_git_url(service), ref_mutator) log_event(service, instance, cluster, desired_state)
def test_get_git_url_provided_by_serviceyaml(): service = 'giiiiiiiiiiit' expected = 'git@some_random_host:foobar' with ( mock.patch('service_configuration_lib.read_service_configuration', autospec=True) ) as mock_read_service_configuration: mock_read_service_configuration.return_value = {'git_url': expected} assert utils.get_git_url(service) == expected mock_read_service_configuration.assert_called_once_with(service, soa_dir=utils.DEFAULT_SOA_DIR)
def test_get_git_url_default(): service = 'giiiiiiiiiiit' expected = '[email protected]:services/%s.git' % service with ( mock.patch('service_configuration_lib.read_service_configuration', autospec=True) ) as mock_read_service_configuration: mock_read_service_configuration.return_value = {} assert utils.get_git_url(service) == expected mock_read_service_configuration.assert_called_once_with(service, soa_dir=utils.DEFAULT_SOA_DIR)
def paasta_rollback(args): """Call mark_for_deployment with rollback parameters :param args: contains all the arguments passed onto the script: service, deploy groups and sha. These arguments will be verified and passed onto mark_for_deployment. """ soa_dir = args.soa_dir service = figure_out_service_name(args, soa_dir) git_url = get_git_url(service, soa_dir) given_deploy_groups = { deploy_group for deploy_group in args.deploy_groups.split(",") if deploy_group } service_deploy_groups = { config.get_deploy_group() for config in get_instance_config_for_service( service=service, soa_dir=soa_dir, ) } deploy_groups, invalid = validate_given_deploy_groups( service_deploy_groups, given_deploy_groups) if len(invalid) > 0: print PaastaColors.yellow( "These deploy groups are not valid and will be skipped: %s.\n" % (",").join(invalid)) if len(deploy_groups) == 0: print PaastaColors.red( "ERROR: No valid deploy groups specified for %s.\n" % (service)) return 1 commit = args.commit if not commit: list_previous_commits(service, deploy_groups, bool(given_deploy_groups), soa_dir) return 1 returncode = 0 for deploy_group in deploy_groups: returncode = max( mark_for_deployment( git_url=git_url, service=service, deploy_group=deploy_group, commit=commit, ), returncode, ) return returncode
def paasta_start_or_stop(args, desired_state): """Requests a change of state to start or stop given branches of a service.""" instance = args.instance clusters = args.clusters soa_dir = args.soa_dir service = figure_out_service_name(args=args, soa_dir=soa_dir) if args.clusters is not None: clusters = args.clusters.split(",") else: clusters = list_clusters(service) try: remote_refs = remote_git.list_remote_refs( utils.get_git_url(service, soa_dir)) except remote_git.LSRemoteException as e: msg = ( "Error talking to the git server: %s\n" "This PaaSTA command requires access to the git server to operate.\n" "The git server may be down or not reachable from here.\n" "Try again from somewhere where the git server can be reached, " "like your developer environment.") % str(e) print msg return 1 invalid_deploy_groups = [] for cluster in clusters: service_config = get_instance_config( service=service, cluster=cluster, instance=instance, soa_dir=soa_dir, load_deployments=False, ) deploy_group = service_config.get_deploy_group() (deploy_tag, _) = get_latest_deployment_tag(remote_refs, deploy_group) if deploy_tag not in remote_refs: invalid_deploy_groups.append(deploy_group) else: force_bounce = utils.format_timestamp(datetime.datetime.utcnow()) issue_state_change_for_service( service_config=service_config, force_bounce=force_bounce, desired_state=desired_state, ) return_val = 0 if invalid_deploy_groups: print "No branches found for %s in %s." % \ (", ".join(invalid_deploy_groups), remote_refs) print "Has %s been deployed there yet?" % service return_val = 1 return return_val
def paasta_start_or_stop(args, desired_state): """Requests a change of state to start or stop given branches of a service.""" instance = args.instance clusters = args.clusters soa_dir = args.soa_dir service = figure_out_service_name(args=args, soa_dir=soa_dir) if args.clusters is not None: clusters = args.clusters.split(",") else: clusters = list_clusters(service) try: remote_refs = remote_git.list_remote_refs(utils.get_git_url(service, soa_dir)) except remote_git.LSRemoteException as e: msg = ( "Error talking to the git server: %s\n" "This PaaSTA command requires access to the git server to operate.\n" "The git server may be down or not reachable from here.\n" "Try again from somewhere where the git server can be reached, " "like your developer environment." ) % str(e) print msg return 1 invalid_deploy_groups = [] for cluster in clusters: service_config = get_instance_config( service=service, cluster=cluster, instance=instance, soa_dir=soa_dir, load_deployments=False, ) deploy_group = service_config.get_deploy_group() (deploy_tag, _) = get_latest_deployment_tag(remote_refs, deploy_group) if deploy_tag not in remote_refs: invalid_deploy_groups.append(deploy_group) else: force_bounce = utils.format_timestamp(datetime.datetime.utcnow()) issue_state_change_for_service( service_config=service_config, force_bounce=force_bounce, desired_state=desired_state, ) return_val = 0 if invalid_deploy_groups: print "No branches found for %s in %s." % \ (", ".join(invalid_deploy_groups), remote_refs) print "Has %s been deployed there yet?" % service return_val = 1 return return_val
def get_deploy_group_mappings(soa_dir, service, old_mappings): """Gets mappings from service:deploy_group to services-service:paasta-hash, where hash is the current SHA at the HEAD of branch_name. This is done for all services in soa_dir. :param soa_dir: The SOA configuration directory to read from :param old_mappings: A dictionary like the return dictionary. Used for fallback if there is a problem with a new mapping. :returns: A dictionary mapping service:deploy_group to a dictionary containing: - 'docker_image': something like "services-service:paasta-hash". This is relative to the paasta docker registry. - 'desired_state': either 'start' or 'stop'. Says whether this branch should be running. - 'force_bounce': An arbitrary value, which may be None. A change in this value should trigger a bounce, even if the other properties of this app have not changed. """ mappings = {} service_configs = get_instance_config_for_service( soa_dir=soa_dir, service=service, ) deploy_group_branch_mappings = dict((config.get_branch(), config.get_deploy_group()) for config in service_configs) if not deploy_group_branch_mappings: log.info('Service %s has no valid deploy groups. Skipping.', service) return {} git_url = get_git_url( service=service, soa_dir=soa_dir, ) remote_refs = remote_git.list_remote_refs(git_url) for control_branch, deploy_group in deploy_group_branch_mappings.items(): deploy_ref_name = 'refs/heads/paasta-%s' % deploy_group if deploy_ref_name in remote_refs: commit_sha = remote_refs[deploy_ref_name] control_branch_alias = '%s:paasta-%s' % (service, control_branch) deploy_group_branch_alias = '%s:paasta-%s' % (service, deploy_group) docker_image = build_docker_image_name(service, commit_sha) log.info('Mapping deploy_group %s to docker image %s', deploy_group_branch_alias, docker_image) mapping = mappings.setdefault(control_branch_alias, {}) mapping['docker_image'] = docker_image desired_state, force_bounce = get_desired_state( service=service, branch=control_branch, remote_refs=remote_refs, deploy_group=deploy_group, ) mapping['desired_state'] = desired_state mapping['force_bounce'] = force_bounce return mappings
def issue_state_change_for_service(service_config, force_bounce, desired_state): ref_mutator = make_mutate_refs_func( service_config=service_config, force_bounce=force_bounce, desired_state=desired_state, ) remote_git.create_remote_refs(utils.get_git_url(service_config.get_service()), ref_mutator) log_event( service_config=service_config, desired_state=desired_state, )
def paasta_wait_for_deployment(args): """Wrapping wait_for_deployment""" if args.verbose: log.setLevel(level=logging.DEBUG) else: log.setLevel(level=logging.INFO) service = args.service if service and service.startswith('services-'): service = service.split('services-', 1)[1] if args.git_url is None: args.git_url = get_git_url(service=service, soa_dir=args.soa_dir) try: validate_full_git_sha(args.commit) except ArgumentTypeError: refs = remote_git.list_remote_refs(args.git_url) commits = short_to_full_git_sha(short=args.commit, refs=refs) if len(commits) != 1: raise ValueError( "%s matched %d git shas (with refs pointing at them). Must match exactly 1." % (args.commit, len(commits))) args.commit = commits[0] try: validate_service_name(service, soa_dir=args.soa_dir) validate_deploy_group(args.deploy_group, service, args.soa_dir) validate_git_sha(args.commit, args.git_url, args.deploy_group, service) except (GitShaError, DeployGroupError, NoSuchService) as e: paasta_print(PaastaColors.red('{}'.format(e))) return 1 try: wait_for_deployment(service=service, deploy_group=args.deploy_group, git_sha=args.commit, soa_dir=args.soa_dir, timeout=args.timeout) _log(service=service, component='deploy', line=("Deployment of {} for {} complete". format(args.commit, args.deploy_group)), level='event') except (KeyboardInterrupt, TimeoutError): paasta_print("Waiting for deployment aborted.") return 1 except NoInstancesFound: return 1 return 0
def paasta_wait_for_deployment(args): """Wrapping wait_for_deployment""" if args.verbose: log.setLevel(level=logging.DEBUG) else: log.setLevel(level=logging.INFO) service = args.service if service and service.startswith("services-"): service = service.split("services-", 1)[1] if args.git_url is None: args.git_url = get_git_url(service=service, soa_dir=args.soa_dir) args.commit = validate_git_sha(sha=args.commit, git_url=args.git_url) version = DeploymentVersion(sha=args.commit, image_version=args.image_version) try: validate_service_name(service, soa_dir=args.soa_dir) validate_deploy_group(args.deploy_group, service, args.soa_dir) validate_version_is_latest(version, args.git_url, args.deploy_group, service) except (VersionError, DeployGroupError, NoSuchService) as e: print(PaastaColors.red(f"{e}")) return 1 try: asyncio.run( wait_for_deployment( service=service, deploy_group=args.deploy_group, git_sha=args.commit, image_version=args.image_version, soa_dir=args.soa_dir, timeout=args.timeout, polling_interval=args.polling_interval, diagnosis_interval=args.diagnosis_interval, time_before_first_diagnosis=args.time_before_first_diagnosis, )) _log( service=service, component="deploy", line=(f"Deployment of {version} for {args.deploy_group} complete"), level="event", ) except (KeyboardInterrupt, TimeoutError, NoSuchCluster): report_waiting_aborted(service, args.deploy_group) return 1 return 0
def issue_state_change_for_service(service_config, force_bounce, desired_state): ref_mutator = make_mutate_refs_func( service_config=service_config, force_bounce=force_bounce, desired_state=desired_state, ) git_url = utils.get_git_url(service_config.get_service()) remote_git.create_remote_refs(git_url, ref_mutator) if "yelpcorp.com" in git_url: trigger_deploys(service_config.get_service()) log_event(service_config=service_config, desired_state=desired_state)
def get_deploy_group_mappings(soa_dir, service, old_mappings): """Gets mappings from service:deploy_group to services-service:paasta-hash, where hash is the current SHA at the HEAD of branch_name. This is done for all services in soa_dir. :param soa_dir: The SOA configuration directory to read from :param old_mappings: A dictionary like the return dictionary. Used for fallback if there is a problem with a new mapping. :returns: A dictionary mapping service:deploy_group to a dictionary containing: - 'docker_image': something like "services-service:paasta-hash". This is relative to the paasta docker registry. - 'desired_state': either 'start' or 'stop'. Says whether this branch should be running. - 'force_bounce': An arbitrary value, which may be None. A change in this value should trigger a bounce, even if the other properties of this app have not changed. """ mappings = {} service_configs = get_instance_config_for_service( soa_dir=soa_dir, service=service, ) deploy_group_branch_mappings = dict((config.get_branch(), config.get_deploy_group()) for config in service_configs) if not deploy_group_branch_mappings: log.info('Service %s has no valid deploy groups. Skipping.', service) return {} git_url = get_git_url( service=service, soa_dir=soa_dir, ) remote_refs = remote_git.list_remote_refs(git_url) for control_branch, deploy_group in deploy_group_branch_mappings.items(): deploy_ref_name = 'refs/heads/paasta-%s' % deploy_group if deploy_ref_name in remote_refs: commit_sha = remote_refs[deploy_ref_name] control_branch_alias = '%s:paasta-%s' % (service, control_branch) deploy_group_branch_alias = '%s:paasta-%s' % (service, deploy_group) docker_image = build_docker_image_name(service, commit_sha) log.info('Mapping deploy_group %s to docker image %s', deploy_group_branch_alias, docker_image) mapping = mappings.setdefault(control_branch_alias, {}) mapping['docker_image'] = docker_image desired_state, force_bounce = get_desired_state( branch=control_branch, remote_refs=remote_refs, deploy_group=deploy_group, ) mapping['desired_state'] = desired_state mapping['force_bounce'] = force_bounce return mappings
def paasta_rollback(args): """Call mark_for_deployment with rollback parameters :param args: contains all the arguments passed onto the script: service, deploy groups and sha. These arguments will be verified and passed onto mark_for_deployment. """ soa_dir = args.soa_dir service = figure_out_service_name(args, soa_dir) git_url = get_git_url(service, soa_dir) given_deploy_groups = {deploy_group for deploy_group in args.deploy_groups.split(",") if deploy_group} all_deploy_groups = list_deploy_groups(service=service, soa_dir=soa_dir) deploy_groups, invalid = validate_given_deploy_groups(all_deploy_groups, given_deploy_groups) if len(invalid) > 0: paasta_print( PaastaColors.yellow( "These deploy groups are not valid and will be skipped: %s.\n" % (",").join(invalid), ), ) if len(deploy_groups) == 0: paasta_print(PaastaColors.red("ERROR: No valid deploy groups specified for %s.\n" % (service))) return 1 git_shas = get_git_shas_for_service(service, deploy_groups, soa_dir) commit = args.commit if not commit: paasta_print("Please specify a commit to mark for rollback (-k, --commit).") list_previous_commits(service, deploy_groups, bool(given_deploy_groups), git_shas) return 1 elif commit not in git_shas and not args.force: paasta_print(PaastaColors.red("This Git SHA has never been deployed before.")) paasta_print("Please double check it or use --force to skip this verification.\n") list_previous_commits(service, deploy_groups, bool(given_deploy_groups), git_shas) return 1 returncode = 0 for deploy_group in deploy_groups: returncode = max( mark_for_deployment( git_url=git_url, service=service, deploy_group=deploy_group, commit=commit, ), returncode, ) return returncode
def paasta_wait_for_deployment(args): """Wrapping wait_for_deployment""" if args.verbose: log.setLevel(level=logging.DEBUG) else: log.setLevel(level=logging.INFO) service = args.service if service and service.startswith('services-'): service = service.split('services-', 1)[1] if args.git_url is None: args.git_url = get_git_url(service=service, soa_dir=args.soa_dir) args.commit = validate_git_sha(sha=args.commit, git_url=args.git_url) try: validate_service_name(service, soa_dir=args.soa_dir) validate_deploy_group(args.deploy_group, service, args.soa_dir) validate_git_sha_is_latest( args.commit, args.git_url, args.deploy_group, service, ) except (GitShaError, DeployGroupError, NoSuchService) as e: paasta_print(PaastaColors.red(f'{e}')) return 1 try: wait_for_deployment( service=service, deploy_group=args.deploy_group, git_sha=args.commit, soa_dir=args.soa_dir, timeout=args.timeout, ) _log( service=service, component='deploy', line=("Deployment of {} for {} complete".format( args.commit, args.deploy_group)), level='event', ) except (KeyboardInterrupt, TimeoutError, NoSuchCluster): report_waiting_aborted(service, args.deploy_group) return 1 return 0
def paasta_rollback(args): """Call mark_for_deployment with rollback parameters :param args: contains all the arguments passed onto the script: service, deploy groups and sha. These arguments will be verified and passed onto mark_for_deployment. """ soa_dir = args.soa_dir service = figure_out_service_name(args, soa_dir) git_url = get_git_url(service, soa_dir) given_deploy_groups = {deploy_group for deploy_group in args.deploy_groups.split(",") if deploy_group} service_deploy_groups = {config.get_deploy_group() for config in get_instance_config_for_service( service=service, soa_dir=soa_dir, )} deploy_groups, invalid = validate_given_deploy_groups(service_deploy_groups, given_deploy_groups) if len(invalid) > 0: print PaastaColors.yellow("These deploy groups are not valid and will be skipped: %s.\n" % (",").join(invalid)) if len(deploy_groups) == 0: print PaastaColors.red("ERROR: No valid deploy groups specified for %s.\n" % (service)) return 1 commit = args.commit if not commit: list_previous_commits(service, deploy_groups, bool(given_deploy_groups), soa_dir) return 1 returncode = 0 for deploy_group in deploy_groups: returncode = max( mark_for_deployment( git_url=git_url, service=service, deploy_group=deploy_group, commit=commit, ), returncode, ) return returncode
def paasta_wait_for_deployment(args): """Wrapping wait_for_deployment""" if args.verbose: log.setLevel(level=logging.DEBUG) else: log.setLevel(level=logging.INFO) service = args.service if service and service.startswith('services-'): service = service.split('services-', 1)[1] if args.git_url is None: args.git_url = get_git_url(service=service, soa_dir=args.soa_dir) try: validate_service_name(service, soa_dir=args.soa_dir) validate_deploy_group(args.deploy_group, service, args.soa_dir) validate_git_sha(args.commit, args.git_url, args.deploy_group, service) except (GitShaError, DeployGroupError, NoSuchService) as e: paasta_print(PaastaColors.red('{}'.format(e))) return 1 try: wait_for_deployment( service=service, deploy_group=args.deploy_group, git_sha=args.commit, soa_dir=args.soa_dir, timeout=args.timeout) _log( service=service, component='deploy', line=("Deployment of {0} for {1} complete".format( args.commit, args.deploy_group)), level='event') except (KeyboardInterrupt, TimeoutError): paasta_print("Waiting for deployment aborted.") return 1 except NoInstancesFound: return 1 return 0
def paasta_get_latest_deployment(args): service = args.service deploy_group = args.deploy_group soa_dir = args.soa_dir validate_service_name(service, soa_dir) git_url = get_git_url( service=service, soa_dir=soa_dir, ) remote_refs = list_remote_refs(git_url) _, git_sha = get_latest_deployment_tag(remote_refs, deploy_group) if not git_sha: print PaastaColors.red("A deployment could not be found for %s in %s" % (deploy_group, service)) return 1 else: print git_sha return 0
def paasta_wait_for_deployment(args): """Wrapping wait_for_deployment""" if args.verbose: log.setLevel(level=logging.DEBUG) else: log.setLevel(level=logging.INFO) service = args.service if service and service.startswith('services-'): service = service.split('services-', 1)[1] if args.git_url is None: args.git_url = get_git_url(service=service, soa_dir=args.soa_dir) try: validate_service_name(service, soa_dir=args.soa_dir) validate_deploy_group(args.deploy_group, service, args.soa_dir) validate_git_sha(args.commit, args.git_url, args.deploy_group, service) except (GitShaError, DeployGroupError, NoSuchService) as e: paasta_print(PaastaColors.red('{}'.format(e))) return 1 try: wait_for_deployment(service=service, deploy_group=args.deploy_group, git_sha=args.commit, soa_dir=args.soa_dir, timeout=args.timeout) _log(service=service, component='deploy', line=("Deployment of {0} for {1} complete". format(args.commit, args.deploy_group)), level='event') except (KeyboardInterrupt, TimeoutError): paasta_print("Waiting for deployment aborted.") return 1 except NoInstancesFound: return 1 return 0
def paasta_rollback(args): """Call mark_for_deployment with rollback parameters :param args: contains all the arguments passed onto the script: service, cluster, instance and sha. These arguments will be verified and passed onto mark_for_deployment. """ service = figure_out_service_name(args) cluster = args.cluster git_url = get_git_url(service) commit = args.commit given_instances = args.instances.split(",") if cluster in list_clusters(service): service_instances = list_all_instances_for_service(service) instances, invalid = validate_given_instances(service_instances, given_instances) if len(invalid) > 0: print PaastaColors.yellow( "These instances are not valid and will be skipped: %s.\n" % (",").join(invalid)) if len(instances) is 0: print PaastaColors.red( "ERROR: No valid instances specified for %s.\n" % (service)) returncode = 1 for instance in instances: returncode = mark_for_deployment( git_url=git_url, cluster=cluster, instance=instance, service=service, commit=commit, ) else: print PaastaColors.red( "ERROR: The service %s is not deployed into cluster %s.\n" % (service, cluster)) returncode = 1 sys.exit(returncode)
def paasta_start_or_stop(args, desired_state): """Requests a change of state to start or stop given branches of a service.""" instance = args.instance cluster = args.cluster soa_dir = args.soa_dir service = figure_out_service_name(args=args, soa_dir=soa_dir) service_config = get_instance_config( service=service, cluster=cluster, instance=instance, soa_dir=soa_dir, load_deployments=False, ) try: remote_refs = remote_git.list_remote_refs(utils.get_git_url(service)) except remote_git.LSRemoteException as e: msg = ( "Error talking to the git server: %s\n" "This PaaSTA command requires access to the git server to operate.\n" "The git server may be down or not reachable from here.\n" "Try again from somewhere where the git server can be reached, " "like your developer environment." ) % str(e) print msg return 1 if 'refs/heads/paasta-%s' % service_config.get_deploy_group() not in remote_refs: print "No branches found for %s in %s." % \ (service_config.get_deploy_group(), remote_refs) print "Has it been deployed there yet?" return 1 force_bounce = utils.format_timestamp(datetime.datetime.utcnow()) issue_state_change_for_service( service_config=service_config, force_bounce=force_bounce, desired_state=desired_state, )
def paasta_rollback(args): """Call mark_for_deployment with rollback parameters :param args: contains all the arguments passed onto the script: service, deploy groups and sha. These arguments will be verified and passed onto mark_for_deployment. """ service = figure_out_service_name(args) git_url = get_git_url(service) commit = args.commit given_deploy_groups = [ deploy_group for deploy_group in args.deploy_groups.split(",") if deploy_group ] service_deploy_groups = set(config.get_deploy_group() for config in get_instance_config_for_service( soa_dir=DEFAULT_SOA_DIR, service=service, )) deploy_groups, invalid = validate_given_deploy_groups( service_deploy_groups, given_deploy_groups) if len(invalid) > 0: print PaastaColors.yellow( "These deploy groups are not valid and will be skipped: %s.\n" % (",").join(invalid)) if len(deploy_groups) == 0: print PaastaColors.red( "ERROR: No valid deploy groups specified for %s.\n" % (service)) returncode = 1 for deploy_group in deploy_groups: returncode = mark_for_deployment( git_url=git_url, service=service, deploy_group=deploy_group, commit=commit, ) sys.exit(returncode)
def get_branch_mappings(soa_dir, service, old_mappings): """Gets mappings from service:branch_name to services-service:paasta-hash, where hash is the current SHA at the HEAD of branch_name. This is done for all services in soa_dir. :param soa_dir: The SOA configuration directory to read from :param old_mappings: A dictionary like the return dictionary. Used for fallback if there is a problem with a new mapping. :returns: A dictionary mapping service:branch_name to a dictionary containing: - 'docker_image': something like "services-service:paasta-hash". This is relative to the paasta docker registry. - 'desired_state': either 'start' or 'stop'. Says whether this branch should be running. - 'force_bounce': An arbitrary value, which may be None. A change in this value should trigger a bounce, even if the other properties of this app have not changed. """ mappings = {} valid_branches = get_branches_for_service(soa_dir, service) if not valid_branches: log.info("Service %s has no valid branches. Skipping.", service) return {} git_url = get_git_url(service, soa_dir=soa_dir) remote_refs = remote_git.list_remote_refs(git_url) for branch in valid_branches: ref_name = "refs/heads/%s" % branch if ref_name in remote_refs: commit_sha = remote_refs[ref_name] branch_alias = "%s:%s" % (service, branch) docker_image = build_docker_image_name(service, commit_sha) log.info("Mapping branch %s to docker image %s", branch_alias, docker_image) mapping = mappings.setdefault(branch_alias, {}) mapping["docker_image"] = docker_image desired_state, force_bounce = get_desired_state(service, branch, remote_refs) mapping["desired_state"] = desired_state mapping["force_bounce"] = force_bounce return mappings
def get_git_shas_for_service(service, deploy_groups, soa_dir): """Returns a dictionary of 2-tuples of the form (timestamp, deploy_group) for each deploy sha""" if service is None: return [] git_url = get_git_url(service=service, soa_dir=soa_dir) all_deploy_groups = list_deploy_groups(service=service, soa_dir=soa_dir) deploy_groups, _ = validate_given_deploy_groups(all_deploy_groups, deploy_groups) previously_deployed_shas = {} for ref, sha in list_remote_refs(git_url).items(): regex_match = extract_tags(ref) try: deploy_group = regex_match["deploy_group"] tstamp = regex_match["tstamp"] except KeyError: pass else: # Now we filter and dedup by picking the most recent sha for a deploy group # Note that all strings are greater than '' if deploy_group in deploy_groups: tstamp_so_far = previously_deployed_shas.get(sha, ("all", ""))[1] if tstamp > tstamp_so_far: previously_deployed_shas[sha] = (tstamp, deploy_group) return previously_deployed_shas
def get_git_shas_for_service(service, deploy_groups, soa_dir): """Returns a dictionary of 2-tuples of the form (timestamp, deploy_group) for each deploy sha""" if service is None: return [] git_url = get_git_url(service=service, soa_dir=soa_dir) all_deploy_groups = list_deploy_groups( service=service, soa_dir=soa_dir, ) deploy_groups, _ = validate_given_deploy_groups(all_deploy_groups, deploy_groups) previously_deployed_shas = {} for ref, sha in list_remote_refs(git_url).items(): regex_match = extract_tags(ref) try: deploy_group = regex_match['deploy_group'] tstamp = regex_match['tstamp'] except KeyError: pass else: # note that all strings are greater than '' if deploy_group in deploy_groups and tstamp > previously_deployed_shas.get(sha, ''): previously_deployed_shas[sha] = (tstamp, deploy_group) return previously_deployed_shas.items()
def get_service_info(service, soa_dir): service_configuration = read_service_configuration(service, soa_dir) description = service_configuration.get('description', NO_DESCRIPTION_MESSAGE) external_link = service_configuration.get('external_link', NO_EXTERNAL_LINK_MESSAGE) smartstack_endpoints = get_smartstack_endpoints(service, soa_dir) git_url = get_git_url(service, soa_dir) output = [] output.append('Service Name: %s' % service) output.append('Description: %s' % description) output.append('External Link: %s' % PaastaColors.cyan(external_link)) output.append('Monitored By: team %s' % get_team(service=service, overrides={})) output.append('Runbook: %s' % PaastaColors.cyan(get_runbook(service=service, overrides={}))) output.append('Git Repo: %s' % git_url) output.append('Deployed to the following clusters:') output.extend(get_deployments_strings(service, soa_dir)) if smartstack_endpoints: output.append('Smartstack endpoint(s):') for endpoint in smartstack_endpoints: output.append(' - %s' % endpoint) output.append('Dashboard(s):') output.extend(get_dashboard_urls(service)) return '\n'.join(output)
def paasta_rollback(args): """Call mark_for_deployment with rollback parameters :param args: contains all the arguments passed onto the script: service, cluster, instance and sha. These arguments will be verified and passed onto mark_for_deployment. """ service = figure_out_service_name(args) cluster = args.cluster git_url = get_git_url(service) commit = args.commit given_instances = args.instances.split(",") if cluster in list_clusters(service): service_instances = list_all_instances_for_service(service) instances, invalid = validate_given_instances(service_instances, given_instances) if len(invalid) > 0: print PaastaColors.yellow("These instances are not valid and will be skipped: %s.\n" % (",").join(invalid)) if len(instances) is 0: print PaastaColors.red("ERROR: No valid instances specified for %s.\n" % (service)) returncode = 1 for instance in instances: returncode = mark_for_deployment( git_url=git_url, cluster=cluster, instance=instance, service=service, commit=commit, ) else: print PaastaColors.red("ERROR: The service %s is not deployed into cluster %s.\n" % (service, cluster)) returncode = 1 sys.exit(returncode)
def get_git_shas_for_service(service, deploy_groups, soa_dir): """Returns a list of 2-tuples of the form (sha, timestamp) for each deploy tag in a service's git repository""" if service is None: return [] git_url = get_git_url(service=service, soa_dir=soa_dir) all_deploy_groups = {config.get_deploy_group() for config in get_instance_config_for_service( service=service, soa_dir=soa_dir, )} deploy_groups, _ = validate_given_deploy_groups(all_deploy_groups, deploy_groups) previously_deployed_shas = {} for ref, sha in list_remote_refs(git_url).items(): regex_match = extract_tags(ref) try: deploy_group = regex_match['deploy_group'] tstamp = regex_match['tstamp'] except KeyError: pass else: # note that all strings are greater than '' if deploy_group in deploy_groups and tstamp > previously_deployed_shas.get(sha, ''): previously_deployed_shas[sha] = tstamp return previously_deployed_shas.items()
def paasta_rollback(args): """Call mark_for_deployment with rollback parameters :param args: contains all the arguments passed onto the script: service, deploy groups and sha. These arguments will be verified and passed onto mark_for_deployment. """ soa_dir = args.soa_dir service = figure_out_service_name(args, soa_dir) deploy_info = get_deploy_info(service=service, soa_dir=args.soa_dir) deploy_authz_check(deploy_info, service) git_url = get_git_url(service, soa_dir) given_deploy_groups = { deploy_group for deploy_group in args.deploy_groups.split(",") if deploy_group } all_deploy_groups = list_deploy_groups(service=service, soa_dir=soa_dir) deploy_groups, invalid = validate_given_deploy_groups( all_deploy_groups, given_deploy_groups ) if len(invalid) > 0: print( PaastaColors.yellow( "These deploy groups are not valid and will be skipped: %s.\n" % (",").join(invalid) ) ) if len(deploy_groups) == 0: print( PaastaColors.red( "ERROR: No valid deploy groups specified for %s.\n" % (service) ) ) return 1 git_shas = get_git_shas_for_service(service, deploy_groups, soa_dir) commit = args.commit if not commit: print("Please specify a commit to mark for rollback (-k, --commit).") list_previous_commits( service, deploy_groups, bool(given_deploy_groups), git_shas ) return 1 elif commit not in git_shas and not args.force: print(PaastaColors.red("This Git SHA has never been deployed before.")) print("Please double check it or use --force to skip this verification.\n") list_previous_commits( service, deploy_groups, bool(given_deploy_groups), git_shas ) return 1 returncode = 0 for deploy_group in deploy_groups: rolled_back_from = get_currently_deployed_sha(service, deploy_group) returncode |= mark_for_deployment( git_url=git_url, service=service, deploy_group=deploy_group, commit=commit ) # we could also gate this by the return code from m-f-d, but we probably care more about someone wanting to # rollback than we care about if the underlying machinery was successfully able to complete the request if rolled_back_from != commit: audit_action_details = { "rolled_back_from": rolled_back_from, "rolled_back_to": commit, "rollback_type": RollbackTypes.USER_INITIATED_ROLLBACK.value, "deploy_group": deploy_group, } _log_audit( action="rollback", action_details=audit_action_details, service=service ) return returncode
def get_deploy_group_mappings( soa_dir: str, service: str ) -> Tuple[Dict[str, V1_Mapping], V2_Mappings]: """Gets mappings from service:deploy_group to services-service:paasta-hash, where hash is the current SHA at the HEAD of branch_name. This is done for all services in soa_dir. :param soa_dir: The SOA configuration directory to read from :returns: A dictionary mapping service:deploy_group to a dictionary containing: - 'docker_image': something like "services-service:paasta-hash". This is relative to the paasta docker registry. - 'desired_state': either 'start' or 'stop'. Says whether this branch should be running. - 'force_bounce': An arbitrary value, which may be None. A change in this value should trigger a bounce, even if the other properties of this app have not changed. """ mappings: Dict[str, V1_Mapping] = {} v2_mappings: V2_Mappings = {"deployments": {}, "controls": {}} git_url = get_git_url(service=service, soa_dir=soa_dir) # Most of the time of this function is in two parts: # 1. getting remote refs from git. (Mostly IO, just waiting for git to get back to us.) # 2. loading instance configs. (Mostly CPU, copy.deepcopying yaml over and over again) # Let's do these two things in parallel. executor = concurrent.futures.ThreadPoolExecutor(max_workers=1) remote_refs_future = executor.submit(remote_git.list_remote_refs, git_url) service_configs = get_instance_configs_for_service(soa_dir=soa_dir, service=service) deploy_group_branch_mappings = { config.get_branch(): config.get_deploy_group() for config in service_configs } if not deploy_group_branch_mappings: log.info("Service %s has no valid deploy groups. Skipping.", service) return mappings, v2_mappings remote_refs = remote_refs_future.result() tag_by_deploy_group = { dg: get_latest_deployment_tag(remote_refs, dg) for dg in set(deploy_group_branch_mappings.values()) } state_by_branch_and_sha = get_desired_state_by_branch_and_sha(remote_refs) for control_branch, deploy_group in deploy_group_branch_mappings.items(): (deploy_ref_name, deploy_ref_sha) = tag_by_deploy_group[deploy_group] if deploy_ref_name in remote_refs: commit_sha = remote_refs[deploy_ref_name] control_branch_alias = f"{service}:paasta-{control_branch}" control_branch_alias_v2 = f"{service}:{control_branch}" docker_image = build_docker_image_name(service, commit_sha) desired_state, force_bounce = state_by_branch_and_sha.get( (control_branch, deploy_ref_sha), ("start", None) ) log.info("Mapping %s to docker image %s", control_branch, docker_image) v2_mappings["deployments"][deploy_group] = { "docker_image": docker_image, "git_sha": commit_sha, } mappings[control_branch_alias] = { "docker_image": docker_image, "desired_state": desired_state, "force_bounce": force_bounce, } v2_mappings["controls"][control_branch_alias_v2] = { "desired_state": desired_state, "force_bounce": force_bounce, } return mappings, v2_mappings
def paasta_start_or_stop(args, desired_state): """Requests a change of state to start or stop given branches of a service.""" soa_dir = args.soa_dir service = figure_out_service_name(args=args, soa_dir=soa_dir) if args.clusters is not None: clusters = args.clusters.split(",") else: clusters = list_clusters(service) if args.instances is not None: instances = args.instances.split(",") else: instances = None try: remote_refs = remote_git.list_remote_refs(utils.get_git_url(service, soa_dir)) except remote_git.LSRemoteException as e: msg = ( "Error talking to the git server: %s\n" "This PaaSTA command requires access to the git server to operate.\n" "The git server may be down or not reachable from here.\n" "Try again from somewhere where the git server can be reached, " "like your developer environment." ) % str(e) print msg return 1 invalid_deploy_groups = [] marathon_message_printed, chronos_message_printed = False, False for cluster in clusters: # If they haven't specified what instances to act on, do it for all of them. # If they have specified what instances, only iterate over them if they're # actually within this cluster. if instances is None: cluster_instances = list_all_instances_for_service(service, clusters=[cluster], soa_dir=soa_dir) else: all_cluster_instances = list_all_instances_for_service(service, clusters=[cluster], soa_dir=soa_dir) cluster_instances = all_cluster_instances.intersection(set(instances)) for instance in cluster_instances: service_config = get_instance_config( service=service, cluster=cluster, instance=instance, soa_dir=soa_dir, load_deployments=False, ) deploy_group = service_config.get_deploy_group() (deploy_tag, _) = get_latest_deployment_tag(remote_refs, deploy_group) if deploy_tag not in remote_refs: invalid_deploy_groups.append(deploy_group) else: force_bounce = utils.format_timestamp(datetime.datetime.utcnow()) if isinstance(service_config, MarathonServiceConfig) and not marathon_message_printed: print_marathon_message(desired_state) marathon_message_printed = True elif isinstance(service_config, ChronosJobConfig) and not chronos_message_printed: print_chronos_message(desired_state) chronos_message_printed = True issue_state_change_for_service( service_config=service_config, force_bounce=force_bounce, desired_state=desired_state, ) return_val = 0 if invalid_deploy_groups: print "No branches found for %s in %s." % \ (", ".join(invalid_deploy_groups), remote_refs) print "Has %s been deployed there yet?" % service return_val = 1 return return_val
def paasta_start_or_stop(args, desired_state): """Requests a change of state to start or stop given branches of a service.""" soa_dir = args.soa_dir service = figure_out_service_name(args=args, soa_dir=soa_dir) instances = args.instances.split(",") if args.instances else None # assert that each of the clusters that the user specifies are 'valid' # for the instance list provided; that is, assert that at least one of the instances # provided in the -i argument is deployed there. # if there are no instances defined in the args, then assert that the service # is deployed to that cluster. # If args.clusters is falsey, then default to *all* clusters that a service is deployed to, # and we figure out which ones are needed for each service later. if instances: instance_clusters = [list_clusters(service, soa_dir, instance) for instance in args.instances] valid_clusters = sorted(list(set([cluster for cluster_list in instance_clusters for cluster in cluster_list]))) else: valid_clusters = list_clusters(service, soa_dir) if args.clusters: clusters = args.clusters.split(",") invalid_clusters = [cluster for cluster in clusters if cluster not in valid_clusters] if invalid_clusters: print ("Invalid cluster name(s) specified: %s." "Valid options: %s") % ( " ".join(invalid_clusters), " ".join(valid_clusters), ) return 1 else: clusters = valid_clusters try: remote_refs = remote_git.list_remote_refs(utils.get_git_url(service, soa_dir)) except remote_git.LSRemoteException as e: msg = ( "Error talking to the git server: %s\n" "This PaaSTA command requires access to the git server to operate.\n" "The git server may be down or not reachable from here.\n" "Try again from somewhere where the git server can be reached, " "like your developer environment." ) % str(e) print msg return 1 invalid_deploy_groups = [] marathon_message_printed, chronos_message_printed = False, False for cluster in clusters: # If they haven't specified what instances to act on, do it for all of them. # If they have specified what instances, only iterate over them if they're # actually within this cluster. if instances is None: cluster_instances = list_all_instances_for_service(service, clusters=[cluster], soa_dir=soa_dir) print ("no instances specified; restarting all instances for service") else: all_cluster_instances = list_all_instances_for_service(service, clusters=[cluster], soa_dir=soa_dir) cluster_instances = all_cluster_instances.intersection(set(instances)) for instance in cluster_instances: service_config = get_instance_config( service=service, cluster=cluster, instance=instance, soa_dir=soa_dir, load_deployments=False ) deploy_group = service_config.get_deploy_group() (deploy_tag, _) = get_latest_deployment_tag(remote_refs, deploy_group) if deploy_tag not in remote_refs: invalid_deploy_groups.append(deploy_group) else: force_bounce = utils.format_timestamp(datetime.datetime.utcnow()) if isinstance(service_config, MarathonServiceConfig) and not marathon_message_printed: print_marathon_message(desired_state) marathon_message_printed = True elif isinstance(service_config, ChronosJobConfig) and not chronos_message_printed: print_chronos_message(desired_state) chronos_message_printed = True issue_state_change_for_service( service_config=service_config, force_bounce=force_bounce, desired_state=desired_state ) return_val = 0 if invalid_deploy_groups: print "No branches found for %s in %s." % (", ".join(invalid_deploy_groups), remote_refs) print "Has %s been deployed there yet?" % service return_val = 1 return return_val
def paasta_mark_for_deployment(args): """Wrapping mark_for_deployment""" if args.verbose: log.setLevel(level=logging.DEBUG) else: log.setLevel(level=logging.INFO) service = args.service if service and service.startswith('services-'): service = service.split('services-', 1)[1] validate_service_name(service, soa_dir=args.soa_dir) in_use_deploy_groups = list_deploy_groups( service=service, soa_dir=args.soa_dir, ) _, invalid_deploy_groups = validate_given_deploy_groups( in_use_deploy_groups, [args.deploy_group]) if len(invalid_deploy_groups) == 1: paasta_print( PaastaColors.red( "ERROR: These deploy groups are not currently used anywhere: %s.\n" % (",").join(invalid_deploy_groups))) paasta_print( PaastaColors.red( "This isn't technically wrong because you can mark-for-deployment before deploying there" )) paasta_print( PaastaColors.red( "but this is probably a typo. Did you mean one of these in-use deploy groups?:" )) paasta_print( PaastaColors.red(" %s" % (",").join(in_use_deploy_groups))) paasta_print() paasta_print(PaastaColors.red("Continuing regardless...")) if args.git_url is None: args.git_url = get_git_url(service=service, soa_dir=args.soa_dir) old_git_sha = get_currently_deployed_sha(service=service, deploy_group=args.deploy_group) if old_git_sha == args.commit: paasta_print( "Warning: The sha asked to be deployed already matches what is set to be deployed:" ) paasta_print(old_git_sha) paasta_print("Continuing anyway.") ret = mark_for_deployment( git_url=args.git_url, deploy_group=args.deploy_group, service=service, commit=args.commit, ) if args.block: try: wait_for_deployment(service=service, deploy_group=args.deploy_group, git_sha=args.commit, soa_dir=args.soa_dir, timeout=args.timeout) line = "Deployment of {} for {} complete".format( args.commit, args.deploy_group) _log(service=service, component='deploy', line=line, level='event') except (KeyboardInterrupt, TimeoutError): if args.auto_rollback is True: if old_git_sha == args.commit: paasta_print( "Error: --auto-rollback was requested, but the previous sha" ) paasta_print( "is the same that was requested with --commit. Can't rollback" ) paasta_print("automatically.") else: paasta_print( "Auto-Rollback requested. Marking the previous sha") paasta_print("(%s) for %s as desired." % (args.deploy_group, old_git_sha)) mark_for_deployment( git_url=args.git_url, deploy_group=args.deploy_group, service=service, commit=old_git_sha, ) else: paasta_print( "Waiting for deployment aborted. PaaSTA will continue to try to deploy this code." ) paasta_print("If you wish to see the status, run:") paasta_print() paasta_print(" paasta status -s %s -v" % service) paasta_print() ret = 1 except NoInstancesFound: return 1 if old_git_sha is not None and old_git_sha != args.commit and not args.auto_rollback: paasta_print() paasta_print("If you wish to roll back, you can run:") paasta_print() paasta_print( PaastaColors.bold( " paasta rollback --service %s --deploy-group %s --commit %s " % (service, args.deploy_group, old_git_sha))) return ret
def paasta_mark_for_deployment(args): """Wrapping mark_for_deployment""" if args.verbose: log.setLevel(level=logging.DEBUG) else: log.setLevel(level=logging.INFO) service = args.service if service and service.startswith('services-'): service = service.split('services-', 1)[1] validate_service_name(service, soa_dir=args.soa_dir) in_use_deploy_groups = list_deploy_groups( service=service, soa_dir=args.soa_dir, ) _, invalid_deploy_groups = validate_given_deploy_groups(in_use_deploy_groups, [args.deploy_group]) if len(invalid_deploy_groups) == 1: paasta_print(PaastaColors.red( "ERROR: These deploy groups are not currently used anywhere: %s.\n" % (",").join(invalid_deploy_groups))) paasta_print(PaastaColors.red( "This isn't technically wrong because you can mark-for-deployment before deploying there")) paasta_print(PaastaColors.red("but this is probably a typo. Did you mean one of these in-use deploy groups?:")) paasta_print(PaastaColors.red(" %s" % (",").join(in_use_deploy_groups))) paasta_print() paasta_print(PaastaColors.red("Continuing regardless...")) if args.git_url is None: args.git_url = get_git_url(service=service, soa_dir=args.soa_dir) old_git_sha = get_currently_deployed_sha(service=service, deploy_group=args.deploy_group) if old_git_sha == args.commit: paasta_print("Warning: The sha asked to be deployed already matches what is set to be deployed:") paasta_print(old_git_sha) paasta_print("Continuing anyway.") ret = mark_for_deployment( git_url=args.git_url, deploy_group=args.deploy_group, service=service, commit=args.commit, ) if args.block: try: wait_for_deployment(service=service, deploy_group=args.deploy_group, git_sha=args.commit, soa_dir=args.soa_dir, timeout=args.timeout) line = "Deployment of {0} for {1} complete".format(args.commit, args.deploy_group) _log( service=service, component='deploy', line=line, level='event' ) except (KeyboardInterrupt, TimeoutError): if args.auto_rollback is True: if old_git_sha == args.commit: paasta_print("Error: --auto-rollback was requested, but the previous sha") paasta_print("is the same that was requested with --commit. Can't rollback") paasta_print("automatically.") else: paasta_print("Auto-Rollback requested. Marking the previous sha") paasta_print("(%s) for %s as desired." % (args.deploy_group, old_git_sha)) mark_for_deployment( git_url=args.git_url, deploy_group=args.deploy_group, service=service, commit=old_git_sha, ) else: paasta_print("Waiting for deployment aborted. PaaSTA will continue to try to deploy this code.") paasta_print("If you wish to see the status, run:") paasta_print() paasta_print(" paasta status -s %s -v" % service) paasta_print() ret = 1 except NoInstancesFound: return 1 if old_git_sha is not None and old_git_sha != args.commit and not args.auto_rollback: paasta_print() paasta_print("If you wish to roll back, you can run:") paasta_print() paasta_print(PaastaColors.bold(" paasta rollback --service %s --deploy-group %s --commit %s " % ( service, args.deploy_group, old_git_sha)) ) return ret
def paasta_start_or_stop(args, desired_state): """Requests a change of state to start or stop given branches of a service.""" soa_dir = args.soa_dir service = figure_out_service_name(args=args, soa_dir=soa_dir) instances = args.instances.split(",") if args.instances else None # assert that each of the clusters that the user specifies are 'valid' # for the instance list provided; that is, assert that at least one of the instances # provided in the -i argument is deployed there. # if there are no instances defined in the args, then assert that the service # is deployed to that cluster. # If args.clusters is falsey, then default to *all* clusters that a service is deployed to, # and we figure out which ones are needed for each service later. if instances: instance_clusters = [ list_clusters(service, soa_dir, instance) for instance in args.instances ] valid_clusters = sorted( list( set([ cluster for cluster_list in instance_clusters for cluster in cluster_list ]))) else: valid_clusters = list_clusters(service, soa_dir) if args.clusters: clusters = args.clusters.split(",") invalid_clusters = [ cluster for cluster in clusters if cluster not in valid_clusters ] if invalid_clusters: print("Invalid cluster name(s) specified: %s." "Valid options: %s") % (" ".join(invalid_clusters), " ".join(valid_clusters)) return 1 else: clusters = valid_clusters try: remote_refs = remote_git.list_remote_refs( utils.get_git_url(service, soa_dir)) except remote_git.LSRemoteException as e: msg = ( "Error talking to the git server: %s\n" "This PaaSTA command requires access to the git server to operate.\n" "The git server may be down or not reachable from here.\n" "Try again from somewhere where the git server can be reached, " "like your developer environment.") % str(e) print msg return 1 invalid_deploy_groups = [] marathon_message_printed, chronos_message_printed = False, False for cluster in clusters: # If they haven't specified what instances to act on, do it for all of them. # If they have specified what instances, only iterate over them if they're # actually within this cluster. if instances is None: cluster_instances = list_all_instances_for_service( service, clusters=[cluster], soa_dir=soa_dir) print( "no instances specified; restarting all instances for service") else: all_cluster_instances = list_all_instances_for_service( service, clusters=[cluster], soa_dir=soa_dir) cluster_instances = all_cluster_instances.intersection( set(instances)) for instance in cluster_instances: service_config = get_instance_config( service=service, cluster=cluster, instance=instance, soa_dir=soa_dir, load_deployments=False, ) deploy_group = service_config.get_deploy_group() (deploy_tag, _) = get_latest_deployment_tag(remote_refs, deploy_group) if deploy_tag not in remote_refs: invalid_deploy_groups.append(deploy_group) else: force_bounce = utils.format_timestamp( datetime.datetime.utcnow()) if isinstance(service_config, MarathonServiceConfig ) and not marathon_message_printed: print_marathon_message(desired_state) marathon_message_printed = True elif isinstance( service_config, ChronosJobConfig) and not chronos_message_printed: print_chronos_message(desired_state) chronos_message_printed = True issue_state_change_for_service( service_config=service_config, force_bounce=force_bounce, desired_state=desired_state, ) return_val = 0 if invalid_deploy_groups: print "No branches found for %s in %s." % \ (", ".join(invalid_deploy_groups), remote_refs) print "Has %s been deployed there yet?" % service return_val = 1 return return_val
def paasta_rollback(args: argparse.Namespace) -> int: """Call mark_for_deployment with rollback parameters :param args: contains all the arguments passed onto the script: service, deploy groups and sha. These arguments will be verified and passed onto mark_for_deployment. """ soa_dir = args.soa_dir service = figure_out_service_name(args, soa_dir) deploy_info = get_deploy_info(service=service, soa_dir=args.soa_dir) if not can_user_deploy_service(deploy_info, service): return 1 git_url = get_git_url(service, soa_dir) if args.all_deploy_groups: given_deploy_groups = list_deploy_groups(service=service, soa_dir=soa_dir) else: given_deploy_groups = { deploy_group for deploy_group in args.deploy_groups.split(",") if deploy_group } all_deploy_groups = list_deploy_groups(service=service, soa_dir=soa_dir) deploy_groups, invalid = validate_given_deploy_groups( all_deploy_groups, given_deploy_groups) if len(invalid) > 0: print( PaastaColors.yellow( "These deploy groups are not valid and will be skipped: %s.\n" % (",").join(invalid))) if len(deploy_groups) == 0 and not args.all_deploy_groups: print( PaastaColors.red( "ERROR: No valid deploy groups specified for %s.\n Use the flag -a to rollback all valid deploy groups for this service" % (service))) return 1 versions = get_versions_for_service(service, deploy_groups, soa_dir) commit = args.commit image_version = args.image_version new_version = DeploymentVersion(sha=commit, image_version=image_version) if not commit: print("Please specify a commit to mark for rollback (-k, --commit).") list_previous_versions(service, deploy_groups, bool(given_deploy_groups), versions) return 1 elif new_version not in versions and not args.force: print( PaastaColors.red( f"This version {new_version} has never been deployed before.")) print( "Please double check it or use --force to skip this verification.\n" ) list_previous_versions(service, deploy_groups, bool(given_deploy_groups), versions) return 1 # TODO: Add similar check for when image_version is empty and no-commit redeploys is enforced for requested deploy_group returncode = 0 for deploy_group in deploy_groups: rolled_back_from = get_currently_deployed_version( service, deploy_group) returncode |= mark_for_deployment( git_url=git_url, service=service, deploy_group=deploy_group, commit=commit, image_version=image_version, ) # we could also gate this by the return code from m-f-d, but we probably care more about someone wanting to # rollback than we care about if the underlying machinery was successfully able to complete the request if rolled_back_from != new_version: audit_action_details = { "rolled_back_from": str(rolled_back_from), "rolled_back_to": str(new_version), "rollback_type": RollbackTypes.USER_INITIATED_ROLLBACK.value, "deploy_group": deploy_group, } _log_audit(action="rollback", action_details=audit_action_details, service=service) return returncode
def paasta_start_or_stop(args, desired_state): """Requests a change of state to start or stop given branches of a service.""" soa_dir = args.soa_dir pargs = apply_args_filters(args) if len(pargs) == 0: return 1 affected_services = {s for service_list in pargs.values() for s in service_list.keys()} if len(affected_services) > 1: paasta_print(PaastaColors.red("Warning: trying to start/stop/restart multiple services:")) for cluster, services_instances in pargs.items(): paasta_print("Cluster %s:" % cluster) for service, instances in services_instances.items(): paasta_print(" Service %s:" % service) paasta_print(" Instances %s" % ",".join(instances)) if sys.stdin.isatty(): confirm = choice.Binary('Are you sure you want to continue?', False).ask() else: confirm = False if not confirm: paasta_print() paasta_print("exiting") return 1 invalid_deploy_groups = [] marathon_message_printed, chronos_message_printed = False, False for cluster, services_instances in pargs.items(): for service, instances in services_instances.items(): try: remote_refs = remote_git.list_remote_refs(utils.get_git_url(service, soa_dir)) except remote_git.LSRemoteException as e: msg = ( "Error talking to the git server: %s\n" "This PaaSTA command requires access to the git server to operate.\n" "The git server may be down or not reachable from here.\n" "Try again from somewhere where the git server can be reached, " "like your developer environment." ) % str(e) paasta_print(msg) return 1 for instance in instances: service_config = get_instance_config( service=service, cluster=cluster, instance=instance, soa_dir=soa_dir, load_deployments=False, ) deploy_group = service_config.get_deploy_group() (deploy_tag, _) = get_latest_deployment_tag(remote_refs, deploy_group) if deploy_tag not in remote_refs: invalid_deploy_groups.append(deploy_group) else: force_bounce = utils.format_timestamp(datetime.datetime.utcnow()) if isinstance(service_config, MarathonServiceConfig) and not marathon_message_printed: print_marathon_message(desired_state) marathon_message_printed = True elif isinstance(service_config, ChronosJobConfig) and not chronos_message_printed: print_chronos_message(desired_state) chronos_message_printed = True issue_state_change_for_service( service_config=service_config, force_bounce=force_bounce, desired_state=desired_state, ) return_val = 0 if invalid_deploy_groups: paasta_print("No branches found for %s in %s." % (", ".join(invalid_deploy_groups), remote_refs)) paasta_print("Has %s been deployed there yet?" % service) return_val = 1 return return_val
def paasta_mark_for_deployment(args): """Wrapping mark_for_deployment""" if args.verbose: log.setLevel(level=logging.DEBUG) else: log.setLevel(level=logging.INFO) service = args.service if service and service.startswith('services-'): service = service.split('services-', 1)[1] validate_service_name(service, soa_dir=args.soa_dir) in_use_deploy_groups = list_deploy_groups( service=service, soa_dir=args.soa_dir, ) _, invalid_deploy_groups = validate_given_deploy_groups( in_use_deploy_groups, [args.deploy_group]) if len(invalid_deploy_groups) == 1: print PaastaColors.red( "ERROR: These deploy groups are not currently used anywhere: %s.\n" % (",").join(invalid_deploy_groups)) print PaastaColors.red( "This isn't technically wrong because you can mark-for-deployment before deploying there" ) print PaastaColors.red( "but this is probably a typo. Did you mean one of these in-use deploy groups?:" ) print PaastaColors.red(" %s" % (",").join(in_use_deploy_groups)) print "" print PaastaColors.red("Continuing regardless...") if args.git_url is None: args.git_url = get_git_url(service=service, soa_dir=args.soa_dir) old_git_sha = get_currently_deployed_sha(service=service, deploy_group=args.deploy_group) if old_git_sha == args.commit: print "Warning: The sha asked to be deployed already matches what is set to be deployed:" print old_git_sha print "Continuing anyway." ret = mark_for_deployment( git_url=args.git_url, deploy_group=args.deploy_group, service=service, commit=args.commit, ) if args.block: try: print "Waiting for deployment of {0} for '{1}' complete...".format( args.commit, args.deploy_group) wait_for_deployment(service=service, deploy_group=args.deploy_group, git_sha=args.commit, soa_dir=args.soa_dir, timeout=args.timeout) line = "Deployment of {0} for {1} complete".format( args.commit, args.deploy_group) _log(service=service, component='deploy', line=line, level='event') except (KeyboardInterrupt, TimeoutError): print "Waiting for deployment aborted. PaaSTA will continue to try to deploy this code." print "If you wish to see the status, run:" print "" print " paasta status -s %s -v" % service print "" ret = 1 if old_git_sha is not None and old_git_sha != args.commit: print "" print "If you wish to roll back, you can run:" print "" print PaastaColors.bold( " paasta rollback --service %s --deploy-group %s --commit %s " % (service, args.deploy_group, old_git_sha)) return ret
def get_remote_refs(service, soa_dir): if service not in REMOTE_REFS: REMOTE_REFS[service] = remote_git.list_remote_refs( utils.get_git_url(service, soa_dir)) return REMOTE_REFS[service]
def paasta_mark_for_deployment(args): """Wrapping mark_for_deployment""" if args.verbose: log.setLevel(level=logging.DEBUG) else: log.setLevel(level=logging.INFO) service = args.service if service and service.startswith('services-'): service = service.split('services-', 1)[1] validate_service_name(service, soa_dir=args.soa_dir) in_use_deploy_groups = list_deploy_groups( service=service, soa_dir=args.soa_dir, ) _, invalid_deploy_groups = validate_given_deploy_groups( in_use_deploy_groups, [args.deploy_group]) if len(invalid_deploy_groups) == 1: paasta_print( PaastaColors.red( "ERROR: These deploy groups are not currently used anywhere: %s.\n" % (",").join(invalid_deploy_groups), )) paasta_print( PaastaColors.red( "This isn't technically wrong because you can mark-for-deployment before deploying there", )) paasta_print( PaastaColors.red( "but this is probably a typo. Did you mean one of these in-use deploy groups?:" )) paasta_print( PaastaColors.red(" %s" % (",").join(in_use_deploy_groups))) paasta_print() paasta_print(PaastaColors.red("Continuing regardless...")) if args.git_url is None: args.git_url = get_git_url(service=service, soa_dir=args.soa_dir) try: validate_full_git_sha(args.commit) except ArgumentTypeError: refs = remote_git.list_remote_refs(args.git_url) commits = short_to_full_git_sha(short=args.commit, refs=refs) if len(commits) != 1: raise ValueError( "%s matched %d git shas (with refs pointing at them). Must match exactly 1." % (args.commit, len(commits)), ) args.commit = commits[0] old_git_sha = get_currently_deployed_sha(service=service, deploy_group=args.deploy_group) if old_git_sha == args.commit: paasta_print( "Warning: The sha asked to be deployed already matches what is set to be deployed:" ) paasta_print(old_git_sha) paasta_print("Continuing anyway.") if args.verify_image: if not is_docker_image_already_in_registry(service, args.soa_dir, args.commit): raise ValueError( 'Failed to find image in the registry for the following sha %s' % args.commit) ret = mark_for_deployment( git_url=args.git_url, deploy_group=args.deploy_group, service=service, commit=args.commit, ) if args.block and ret == 0: try: wait_for_deployment( service=service, deploy_group=args.deploy_group, git_sha=args.commit, soa_dir=args.soa_dir, timeout=args.timeout, ) line = "Deployment of {} for {} complete".format( args.commit, args.deploy_group) _log( service=service, component='deploy', line=line, level='event', ) except (KeyboardInterrupt, TimeoutError): if args.auto_rollback is True: if old_git_sha == args.commit: paasta_print( "Error: --auto-rollback was requested, but the previous sha" ) paasta_print( "is the same that was requested with --commit. Can't rollback" ) paasta_print("automatically.") else: paasta_print( "Auto-Rollback requested. Marking the previous sha") paasta_print("(%s) for %s as desired." % (args.deploy_group, old_git_sha)) mark_for_deployment( git_url=args.git_url, deploy_group=args.deploy_group, service=service, commit=old_git_sha, ) else: report_waiting_aborted(service, args.deploy_group) ret = 1 except NoSuchCluster: report_waiting_aborted(service, args.deploy_group) if old_git_sha is not None and old_git_sha != args.commit and not args.auto_rollback: paasta_print() paasta_print("If you wish to roll back, you can run:") paasta_print() paasta_print( PaastaColors.bold( " paasta rollback --service %s --deploy-group %s --commit %s " % ( service, args.deploy_group, old_git_sha, )), ) return ret
def get_deploy_group_mappings( soa_dir: str, service: str, ) -> Tuple[Dict[str, V1_Mapping], V2_Mappings]: """Gets mappings from service:deploy_group to services-service:paasta-hash, where hash is the current SHA at the HEAD of branch_name. This is done for all services in soa_dir. :param soa_dir: The SOA configuration directory to read from :returns: A dictionary mapping service:deploy_group to a dictionary containing: - 'docker_image': something like "services-service:paasta-hash". This is relative to the paasta docker registry. - 'desired_state': either 'start' or 'stop'. Says whether this branch should be running. - 'force_bounce': An arbitrary value, which may be None. A change in this value should trigger a bounce, even if the other properties of this app have not changed. """ mappings: Dict[str, V1_Mapping] = {} v2_mappings: V2_Mappings = {'deployments': {}, 'controls': {}} service_configs = get_instance_configs_for_service( soa_dir=soa_dir, service=service, ) deploy_group_branch_mappings = { config.get_branch(): config.get_deploy_group() for config in service_configs } if not deploy_group_branch_mappings: log.info('Service %s has no valid deploy groups. Skipping.', service) return mappings, v2_mappings git_url = get_git_url( service=service, soa_dir=soa_dir, ) remote_refs = remote_git.list_remote_refs(git_url) for control_branch, deploy_group in deploy_group_branch_mappings.items(): (deploy_ref_name, _) = get_latest_deployment_tag(remote_refs, deploy_group) if deploy_ref_name in remote_refs: commit_sha = remote_refs[deploy_ref_name] control_branch_alias = f'{service}:paasta-{control_branch}' control_branch_alias_v2 = f'{service}:{control_branch}' docker_image = build_docker_image_name(service, commit_sha) desired_state, force_bounce = get_desired_state( branch=control_branch, remote_refs=remote_refs, deploy_group=deploy_group, ) log.info('Mapping %s to docker image %s', control_branch, docker_image) v2_mappings['deployments'][deploy_group] = { 'docker_image': docker_image, 'git_sha': commit_sha, } mappings[control_branch_alias] = { 'docker_image': docker_image, 'desired_state': desired_state, 'force_bounce': force_bounce, } v2_mappings['controls'][control_branch_alias_v2] = { 'desired_state': desired_state, 'force_bounce': force_bounce, } return mappings, v2_mappings
def paasta_mark_for_deployment(args): """Wrapping mark_for_deployment""" if args.verbose: log.setLevel(level=logging.DEBUG) else: log.setLevel(level=logging.INFO) service = args.service if service and service.startswith("services-"): service = service.split("services-", 1)[1] validate_service_name(service, soa_dir=args.soa_dir) deploy_group = args.deploy_group in_use_deploy_groups = list_deploy_groups(service=service, soa_dir=args.soa_dir) _, invalid_deploy_groups = validate_given_deploy_groups( in_use_deploy_groups, [deploy_group] ) if len(invalid_deploy_groups) == 1: paasta_print( PaastaColors.red( "ERROR: These deploy groups are not currently used anywhere: %s.\n" % (",").join(invalid_deploy_groups) ) ) paasta_print( PaastaColors.red( "This isn't technically wrong because you can mark-for-deployment before deploying there" ) ) paasta_print( PaastaColors.red( "but this is probably a typo. Did you mean one of these in-use deploy groups?:" ) ) paasta_print(PaastaColors.red(" %s" % (",").join(in_use_deploy_groups))) paasta_print() paasta_print(PaastaColors.red("Continuing regardless...")) if args.git_url is None: args.git_url = get_git_url(service=service, soa_dir=args.soa_dir) commit = validate_git_sha(sha=args.commit, git_url=args.git_url) old_git_sha = get_currently_deployed_sha(service=service, deploy_group=deploy_group) if old_git_sha == commit: paasta_print( "Warning: The sha asked to be deployed already matches what is set to be deployed:" ) paasta_print(old_git_sha) paasta_print("Continuing anyway.") if args.verify_image: if not is_docker_image_already_in_registry(service, args.soa_dir, commit): raise ValueError( "Failed to find image in the registry for the following sha %s" % commit ) deploy_info = get_deploy_info(service=service, soa_dir=args.soa_dir) deploy_process = MarkForDeploymentProcess( service=service, deploy_info=deploy_info, deploy_group=deploy_group, commit=commit, old_git_sha=old_git_sha, git_url=args.git_url, auto_rollback=args.auto_rollback, block=args.block, soa_dir=args.soa_dir, timeout=args.timeout, auto_certify_delay=args.auto_certify_delay, auto_abandon_delay=args.auto_abandon_delay, auto_rollback_delay=args.auto_rollback_delay, ) ret = deploy_process.run() return ret
def paasta_mark_for_deployment(args): """Wrapping mark_for_deployment""" if args.verbose: log.setLevel(level=logging.DEBUG) else: log.setLevel(level=logging.INFO) service = args.service if service and service.startswith('services-'): service = service.split('services-', 1)[1] validate_service_name(service, soa_dir=args.soa_dir) deploy_group = args.deploy_group in_use_deploy_groups = list_deploy_groups( service=service, soa_dir=args.soa_dir, ) _, invalid_deploy_groups = validate_given_deploy_groups( in_use_deploy_groups, [deploy_group]) if len(invalid_deploy_groups) == 1: paasta_print( PaastaColors.red( "ERROR: These deploy groups are not currently used anywhere: %s.\n" % (",").join(invalid_deploy_groups), )) paasta_print( PaastaColors.red( "This isn't technically wrong because you can mark-for-deployment before deploying there", )) paasta_print( PaastaColors.red( "but this is probably a typo. Did you mean one of these in-use deploy groups?:" )) paasta_print( PaastaColors.red(" %s" % (",").join(in_use_deploy_groups))) paasta_print() paasta_print(PaastaColors.red("Continuing regardless...")) if args.git_url is None: args.git_url = get_git_url(service=service, soa_dir=args.soa_dir) commit = validate_git_sha(sha=args.commit, git_url=args.git_url) old_git_sha = get_currently_deployed_sha(service=service, deploy_group=deploy_group) if old_git_sha == commit: paasta_print( "Warning: The sha asked to be deployed already matches what is set to be deployed:" ) paasta_print(old_git_sha) paasta_print("Continuing anyway.") if args.verify_image: if not is_docker_image_already_in_registry(service, args.soa_dir, commit): raise ValueError( 'Failed to find image in the registry for the following sha %s' % commit) deploy_info = get_deploy_info(service=service, soa_dir=args.soa_dir) slack_notifier = SlackDeployNotifier( deploy_info=deploy_info, service=service, deploy_group=deploy_group, commit=commit, old_commit=old_git_sha, git_url=args.git_url, ) ret = mark_for_deployment( git_url=args.git_url, deploy_group=deploy_group, service=service, commit=commit, ) slack_notifier.notify_after_mark(ret=ret) if args.block and ret == 0: try: wait_for_deployment( service=service, deploy_group=deploy_group, git_sha=commit, soa_dir=args.soa_dir, timeout=args.timeout, ) line = f"Deployment of {commit} for {deploy_group} complete" _log( service=service, component='deploy', line=line, level='event', ) slack_notifier.notify_after_good_deploy() except (KeyboardInterrupt, TimeoutError): if args.auto_rollback is True: if old_git_sha == commit: paasta_print( "Error: --auto-rollback was requested, but the previous sha" ) paasta_print( "is the same that was requested with --commit. Can't rollback" ) paasta_print("automatically.") else: paasta_print( "Auto-Rollback requested. Marking the previous sha") paasta_print( f"({deploy_group}) for {old_git_sha} as desired.") mark_for_deployment( git_url=args.git_url, deploy_group=deploy_group, service=service, commit=old_git_sha, ) slack_notifier.notify_after_auto_rollback() else: report_waiting_aborted(service, deploy_group) slack_notifier.notify_after_abort() ret = 1 except NoSuchCluster: report_waiting_aborted(service, deploy_group) slack_notifier.notify_after_abort() if old_git_sha is not None and old_git_sha != commit and not args.auto_rollback: paasta_print() paasta_print("If you wish to roll back, you can run:") paasta_print() paasta_print( PaastaColors.bold( " paasta rollback --service {} --deploy-group {} --commit {} " .format( service, deploy_group, old_git_sha, )), ) return ret