def get_diff_context(self): context = { "new_version_details": self.new_version_details, "user": get_default_username(), "LABELS_TO_EXPAND": LABELS_TO_EXPAND, "errors": [], "warnings": [] } if self.deployed_commit_matches_latest_commit: context["errors"].append( "Versions are identical. No changes since last deploy.") return context if not (self.current_commit and self.deploy_commit): context["warnings"].append("Insufficient info to get deploy diff.") return context context["compare_url"] = self.url if not self.generate_diff: disabled_msg = "Deploy diffs disabled for this environment." print(color_warning(disabled_msg)) context["warnings"].append(disabled_msg) return context if not self.repo.permissions: # using unauthenticated API calls, skip diff creation to avoid hitting rate limits print( color_warning( "Diff generation skipped. Supply a Github token to see deploy diffs." )) context["warnings"].append("Diff omitted.") return context try: pr_numbers = self._get_pr_numbers() except GithubException as e: print(color_error(f"Error getting diff commits: {e}")) context["warnings"].append( "There was an error fetching the PRs since the last deploy.") return context if len(pr_numbers) > 500: context["warnings"].append("There are too many PRs to display.") return context elif not pr_numbers: context["warnings"].append("No PRs merged since last release.") return context pool = Pool(5) pr_infos = [_f for _f in pool.map(self._get_pr_info, pr_numbers) if _f] context["pr_infos"] = pr_infos prs_by_label = self._get_prs_by_label(pr_infos) context["prs_by_label"] = prs_by_label return context
def run(self, args, unknown_args): if 'destroy' in unknown_args: puts(color_error("Refusing to run a terraform command containing the argument 'destroy'.")) puts(color_error("It's simply not worth the risk.")) exit(-1) environment = get_environment(args.env_name) run_dir = environment.paths.get_env_file_path('.generated-terraform') modules_dir = os.path.join(TERRAFORM_DIR, 'modules') modules_dest = os.path.join(run_dir, 'modules') if not os.path.isdir(run_dir): os.mkdir(run_dir) if not os.path.isdir(run_dir): os.mkdir(run_dir) if not (os.path.exists(modules_dest) and os.readlink(modules_dest) == modules_dir): os.symlink(modules_dir, modules_dest) if args.username != get_default_username(): print_help_message_about_the_commcare_cloud_default_username_env_var(args.username) key_name = args.username try: generate_terraform_entrypoint(environment, key_name, run_dir, apply_immediately=args.apply_immediately) except UnauthorizedUser as e: allowed_users = environment.users_config.dev_users.present puts(color_error( "Unauthorized user {}.\n\n" "Use COMMCARE_CLOUD_DEFAULT_USERNAME or --username to pass in one of the allowed ssh users:{}" .format(e.username, '\n - '.join([''] + allowed_users)))) return -1 if not args.skip_secrets and unknown_args and unknown_args[0] in ('plan', 'apply'): rds_password = ( environment.get_secret('POSTGRES_USERS.root.password') if environment.terraform_config.rds_instances else '' ) with open(os.path.join(run_dir, 'secrets.auto.tfvars'), 'w', encoding='utf-8') as f: print('rds_password = {}'.format(json.dumps(rds_password)), file=f) env_vars = {'AWS_PROFILE': aws_sign_in(environment)} all_env_vars = os.environ.copy() all_env_vars.update(env_vars) cmd_parts = ['terraform'] + unknown_args cmd = ' '.join(shlex_quote(arg) for arg in cmd_parts) print_command('cd {}; {} {}; cd -'.format( run_dir, ' '.join('{}={}'.format(key, value) for key, value in env_vars.items()), cmd, )) return subprocess.call(cmd, shell=True, env=all_env_vars, cwd=run_dir)
def call_record_deploy_success(environment, diff, start_time, end_time): delta = end_time - start_time args = [ '--user', get_default_username(), '--environment', environment.meta_config.deploy_env, '--url', diff.url, '--minutes', str(int(delta.total_seconds() // 60)), '--commit', diff.deploy_commit, ] commcare_cloud(environment.name, 'django-manage', 'record_deploy_success', *args)
def record_deploy_in_datadog(environment, diff, tdelta): if environment.public_vars.get('DATADOG_ENABLED', False): print(color_summary(f">> Recording deploy in Datadog")) diff_url = f"\nDiff link: [Git Diff]({diff.url})" deploy_notification_text = ( "Formplayer has been successfully deployed to " "*{}* by *{}* in *{}* minutes.\nRelease Name: {}{}".format( environment.name, get_default_username(), int(tdelta.total_seconds() / 60) or '?', environment.new_release_name(), diff_url)) commcare_cloud(environment.name, 'send-datadog-event', 'Formplayer Deploy Success', deploy_notification_text, '--alert_type', "success", show_command=False)
def announce_deploy_start(environment, service_name, commcare_rev=None): user = get_default_username() is_nonstandard_deploy_time = not within_maintenance_window(environment) is_non_default_branch = ( commcare_rev != environment.fab_settings_config.default_branch and commcare_rev is not None) env_name = environment.meta_config.deploy_env subject = f"{user} has initiated a {service_name} deploy to {env_name}" prefix = "" if is_nonstandard_deploy_time: subject += " outside maintenance window" prefix = "ATTENTION: " if is_non_default_branch: subject += f" with non-default branch '{commcare_rev}'" prefix = "ATTENTION: " subject = f"{prefix}{subject}" send_email( environment, subject=subject, )
def _aws_sign_in_with_iam(aws_profile, duration_minutes=DEFAULT_SIGN_IN_DURATION_MINUTES, force_new=False): """ Create a temp session through MFA for a given aws profile :param aws_profile: The name of an existing aws profile to create a temp session for :param duration_minutes: How long to set the session expiration if a new one is created :param force_new: If set to True, creates new credentials even if valid ones are found :return: The name of temp session profile. (Always the passed in profile followed by ':session') """ aws_session_profile = '{}:session'.format(aws_profile) if not force_new \ and _has_valid_v1_session_credentials(aws_session_profile): return aws_session_profile default_username = get_default_username() if default_username.is_guess: username = input("Enter username associated with credentials [{}]: ". format(default_username)) or default_username print_help_message_about_the_commcare_cloud_default_username_env_var( username) else: username = default_username mfa_token = input("Enter your MFA token: ") generate_session_profile(aws_profile, username, mfa_token, duration_minutes) puts(color_success("✓ Sign in accepted")) puts( "You will be able to use AWS from the command line for the next {} minutes." .format(duration_minutes)) puts( color_notice("To use this session outside of commcare-cloud, " "prefix your command with AWS_PROFILE={}:session".format( aws_profile))) return aws_session_profile
class Terraform(CommandBase): command = 'terraform' help = "Run terraform for this env with the given arguments" arguments = ( Argument('--skip-secrets', action='store_true', help=""" Skip regenerating the secrets file. Good for not having to enter vault password again. """), Argument('--apply-immediately', action='store_true', help=""" Apply immediately regardless fo the default. In RDS where the default is to apply in the next maintenance window, use this to apply immediately instead. This may result in a service interruption. """), Argument('--username', default=get_default_username(), help=""" The username of the user whose public key will be put on new servers. Normally this would be _your_ username. Defaults to the value of the COMMCARE_CLOUD_DEFAULT_USERNAME environment variable or else the username of the user running the command. """), ) def run(self, args, unknown_args): if 'destroy' in unknown_args: puts(color_error("Refusing to run a terraform command containing the argument 'destroy'.")) puts(color_error("It's simply not worth the risk.")) exit(-1) environment = get_environment(args.env_name) run_dir = environment.paths.get_env_file_path('.generated-terraform') modules_dir = os.path.join(TERRAFORM_DIR, 'modules') modules_dest = os.path.join(run_dir, 'modules') if not os.path.isdir(run_dir): os.mkdir(run_dir) if not os.path.isdir(run_dir): os.mkdir(run_dir) if not (os.path.exists(modules_dest) and os.readlink(modules_dest) == modules_dir): os.symlink(modules_dir, modules_dest) if args.username != get_default_username(): print_help_message_about_the_commcare_cloud_default_username_env_var(args.username) key_name = args.username try: generate_terraform_entrypoint(environment, key_name, run_dir, apply_immediately=args.apply_immediately) except UnauthorizedUser as e: allowed_users = environment.users_config.dev_users.present puts(color_error( "Unauthorized user {}.\n\n" "Use COMMCARE_CLOUD_DEFAULT_USERNAME or --username to pass in one of the allowed ssh users:{}" .format(e.username, '\n - '.join([''] + allowed_users)))) return -1 if not args.skip_secrets and unknown_args and unknown_args[0] in ('plan', 'apply'): rds_password = ( environment.get_secret('POSTGRES_USERS.root.password') if environment.terraform_config.rds_instances else '' ) with open(os.path.join(run_dir, 'secrets.auto.tfvars'), 'w', encoding='utf-8') as f: print('rds_password = {}'.format(json.dumps(rds_password)), file=f) env_vars = {'AWS_PROFILE': aws_sign_in(environment)} all_env_vars = os.environ.copy() all_env_vars.update(env_vars) cmd_parts = ['terraform'] + unknown_args cmd = ' '.join(shlex_quote(arg) for arg in cmd_parts) print_command('cd {}; {} {}; cd -'.format( run_dir, ' '.join('{}={}'.format(key, value) for key, value in env_vars.items()), cmd, )) return subprocess.call(cmd, shell=True, env=all_env_vars, cwd=run_dir)
def user(self): return get_default_username()