arkime / aws-aio

Apache License 2.0
8 stars 3 forks source link

Surface CDK Confirmations #12

Open chelma opened 1 year ago

chelma commented 1 year ago

Description

Currently, the code that deploys and destroys CDK stacks auto-confirms any changes without making that process visible to the user. This hides impact changes like IAM policy and AWS Resource deletion from the user and could cause impact. We should surface this somehow.

See: https://github.com/arkime/cloud-demo/pull/11

Acceptance Criteria

chelma commented 1 year ago

Here's some sample code for destroy that I couldn't get to work reliably. While we're surfacing a confirmation to the user, ideally we'd check w/ a regex that the stacks being modified are the ones they confirmed. However, the CDK CLI spits out the confirmation in a weird way to stdout that I'm having a hard time matching using a regex and pexpect (spent a couple hours on it). Pasted the code below in case we circle back on this.

    def destroy(self, stack_names: List[str], aws_profile: str = None, aws_region: str = None) -> None:
        command_prefix = get_command_prefix(aws_profile=aws_profile, aws_region=aws_region)
        command_suffix = f"destroy --force {' '.join(stack_names)}"
        command = f"{command_prefix} {command_suffix}"

        # Get the CDK Environment and confirm user wants to tear down the stacks, abort if not
        cdk_env = get_cdk_env(aws_profile=aws_profile, aws_region=aws_region)
        destroy_prompt = ("Your command will result in the the following CloudFormation stacks being destroyed in"
                           + f" AWS Account {cdk_env.aws_account} and Region {cdk_env.aws_region}: {stack_names}"
                           + "\n\n"
                           + "Do you wish to proceed (y/yes or n/no)? ")
        prompt_response = shell.louder_input(message=destroy_prompt, print_header=True)
        if prompt_response.strip().lower() not in ["y", "yes"]:
            logger.info("Aborting per user response")
            return

        # Set up the prompt/response pairs for the CDK CLI.  We do this for each stack instead of using the "--force"
        # option for safety.  We add a default response of "no" to the end of the list to make sure we don't
        # accidentally destroy other stacks (the pairs are checked in order of first-to-last).  The regex we're trying
        # to match looks like:
        #
        # Are you sure you want to delete: MyStack2, MyStack1 (y/n)?
        #
        # However - the way the CLI spits the prompt to stdout is wacky, so we have a to be bit crafty in how we match
        regex_stacks = "|".join([f"{stack_name}" for stack_name in stack_names]) # => "MyStack1|MyStack2"
        num_stacks = len(stack_names)
        regex_str = f"(({regex_stacks})[ ,]+){{{num_stacks}}}" # => "((MyStack1|MyStack2)[ ,]+){2}"
        regex = re.compile(bytes(regex_str, "utf-8"))

        pairs = [
            (regex, "y"),
            # ("Are you sure you want to delete:", "n")
        ]

        logger.info(f"Executing command: {command_suffix}")
        logger.warning("NOTE: This operation can take a while.  You can 'tail -f' the logfile to track the status.")
        exit_code, stdout = shell.call_shell_command(command=command, request_response_pairs=pairs)
        exceptions.raise_common_exceptions(exit_code, stdout)

        if exit_code != 0:
            logger.error(f"Destruction failed")
            raise exceptions.CdkDestroyFailedUnknown()

        logger.info(f"Destruction succeeded")