示例#1
0
    def test_local_validate(self, MockedCloudformation, mocked_local):
        self.capturedOutput.truncate(0)
        self.capturedOutput.seek(0)
        template_path = os.path.join(
            os.path.dirname(os.path.abspath(__file__)),
            "../data/cloudformation_template.yaml",
        )
        mocked_local.return_value = template_path
        cloudformation = MockedCloudformation()
        cloudformation.client.validate_template.return_value = self.response
        validate_stack(local_path=True, root=True)
        mocked_local.assert_called_once_with(
            search_from_root=True,
            cloudformation=True,
            header="select a cloudformation template to validate",
        )
        cloudformation.client.validate_template.assert_called_once()
        self.assertRegex(self.capturedOutput.getvalue(),
                         r'"ParameterKey": "foo"')
        self.assertRegex(self.capturedOutput.getvalue(),
                         r'"DefaultValue": "boo"')

        mocked_local.reset_mock()
        cloudformation.client.validate_template.reset_mock()
        validate_stack(local_path=template_path)
        mocked_local.assert_not_called()
        cloudformation.client.validate_template.assert_called_once()
示例#2
0
def construct_local_creation_args(cloudformation: Cloudformation,
                                  local_path: str) -> Dict[str, Any]:
    """Construct cloudformation create argument for local file.

    Perform fzf search on local files json/yaml and then use validate_stack to
    validate stack through boto3 API before constructing the argument.

    :param cloudformation: Cloudformation instance
    :type cloudformation: Cloudformation
    :param local_path: local file path
    :type local_path: str
    :return: return the constructed args thats ready for use with boto3
    :rtype: Dict[str, Any]
    """
    # validate file type, has to be either yaml or json
    check_is_valid(local_path)

    validate_stack(
        cloudformation.profile,
        cloudformation.region,
        local_path=local_path,
        no_print=True,
    )

    stack_name: str = input("StackName: ")
    if not stack_name:
        raise NoNameEntered("No stack name specified")

    fileloader = FileLoader(path=local_path)
    file_data: Dict[str, Any] = {}
    if is_yaml(local_path):
        file_data = fileloader.process_yaml_file()
    elif is_json(local_path):
        file_data = fileloader.process_json_file()

    # get params
    if "Parameters" in file_data["dictBody"]:
        paramprocessor = ParamProcessor(
            cloudformation.profile,
            cloudformation.region,
            file_data["dictBody"]["Parameters"],
        )
        paramprocessor.process_stack_params()
        create_parameters = paramprocessor.processed_params
    else:
        create_parameters = []

    cloudformation_args = {
        "cloudformation_action": cloudformation.client.create_stack,
        "StackName": stack_name,
        "TemplateBody": file_data["body"],
        "Parameters": create_parameters,
    }

    return cloudformation_args
示例#3
0
def local_replacing_update(cloudformation: Cloudformation,
                           local_path: str) -> Dict[str, Any]:
    """Format cloudformation argument for a local replacing update.

    Local replacing update as in using a template in the local machine
    to perform stack update.

    Process the new template and also comparing with previous parameter
    value to provide an old value preview.

    :param cloudformation: Cloudformation instance
    :type cloudformation: Cloudformation
    :param local_path: local file path to the template
    :type local_path: str
    :return: formatted argument thats ready to be used by boto3
    :rtype: Dict[str, Any]
    """
    check_is_valid(local_path)

    validate_stack(
        cloudformation.profile,
        cloudformation.region,
        local_path=local_path,
        no_print=True,
    )

    fileloader = FileLoader(path=local_path)
    file_data: Dict[str, Any] = {}
    if is_yaml(local_path):
        file_data = fileloader.process_yaml_file()
    elif is_json(local_path):
        file_data = fileloader.process_json_file()

    # process params
    if "Parameters" in file_data["dictBody"]:
        paramprocessor = ParamProcessor(
            cloudformation.profile,
            cloudformation.region,
            file_data["dictBody"]["Parameters"],
            cloudformation.stack_details.get("Parameters"),
        )
        paramprocessor.process_stack_params()
        updated_parameters = paramprocessor.processed_params
    else:
        updated_parameters = []

    cloudformation_args = {
        "cloudformation_action": cloudformation.client.update_stack,
        "StackName": cloudformation.stack_name,
        "TemplateBody": file_data["body"],
        "UsePreviousTemplate": False,
        "Parameters": updated_parameters,
    }

    return cloudformation_args
示例#4
0
    def test_s3_validate(self, MockedCloudformation, MockedS3):
        cloudformation = MockedCloudformation()
        cloudformation.client.validate_template.return_value = self.response
        s3 = MockedS3()
        s3.bucket_name = "kazhala-lol"
        s3.path_list = ["hello.yaml"]
        s3.get_object_url.return_value = (
            "https://s3-ap-southeast-2.amazonaws.com/kazhala-lol/hello.yaml")
        s3.get_object_version.return_value = [{"VersionId": "111111"}]
        validate_stack(profile="root", region="ap-southeast-2")
        MockedS3.assert_called_with("root", "ap-southeast-2")
        MockedCloudformation.assert_called_with("root", "ap-southeast-2")
        s3.get_object_url.assert_called_once_with("")
        cloudformation.client.validate_template.assert_called_once_with(
            TemplateURL=
            "https://s3-ap-southeast-2.amazonaws.com/kazhala-lol/hello.yaml")
        self.assertRegex(self.capturedOutput.getvalue(),
                         r'"ParameterKey": "foo"')
        self.assertRegex(self.capturedOutput.getvalue(),
                         r'"DefaultValue": "boo"')

        s3.get_object_url.reset_mock()
        cloudformation.client.validate_template.reset_mock()
        self.capturedOutput.truncate(0)
        self.capturedOutput.seek(0)
        s3.bucket_name = ""
        s3.path_list = ["hello.yaml"]
        s3.get_object_url.return_value = "https://s3-ap-southeast-2.amazonaws.com/kazhala-lol/hello.yaml?versionid=111111"
        validate_stack(no_print=True, bucket="kazhala-lol/", version=True)
        s3.set_s3_bucket.assert_called_once_with(
            header="select a bucket which contains the template")
        s3.get_object_url.assert_called_once_with("111111")
        cloudformation.client.validate_template.assert_called_once_with(
            TemplateURL=
            "https://s3-ap-southeast-2.amazonaws.com/kazhala-lol/hello.yaml?versionid=111111"
        )

        self.assertNotRegex(self.capturedOutput.getvalue(),
                            r'"ParameterKey": "foo"')
        self.assertNotRegex(self.capturedOutput.getvalue(),
                            r'"DefaultValue": "boo"')
示例#5
0
def construct_s3_creation_args(cloudformation: Cloudformation,
                               bucket: Optional[str],
                               version: Union[str, bool]) -> Dict[str, Any]:
    """Construct cloudformation argument for template in s3.

    Retrieve the template from s3 bucket and validate and process the content in it
    then return the ready to use cloudformation argument for boto3.

    :param cloudformation: Cloudformation instance
    :type cloudformation: Cloudformation
    :param bucket: bucket name
    :type bucket: 
    :return: return the formated cloudformation argument thats ready to use by boto3
    :rtype: Dict[str, Any]
    """
    s3 = S3(cloudformation.profile, cloudformation.region)
    s3.set_bucket_and_path(bucket)
    if not s3.bucket_name:
        s3.set_s3_bucket(header="select a bucket which contains the template")
    if not s3.path_list[0]:
        s3.set_s3_object()

    # check file type is yaml or json
    check_is_valid(s3.path_list[0])

    # if version is requested but not set through cmd line, get user to select a version
    if version == True:
        version = s3.get_object_version(s3.bucket_name,
                                        s3.path_list[0])[0].get(
                                            "VersionId", False)

    # validate the template through boto3
    validate_stack(
        cloudformation.profile,
        cloudformation.region,
        bucket="%s/%s" % (s3.bucket_name, s3.path_list[0]),
        version=version if version else False,
        no_print=True,
    )

    file_type: str = ""
    if is_yaml(s3.path_list[0]):
        file_type = "yaml"
    elif is_json(s3.path_list[0]):
        file_type = "json"

    stack_name: str = input("StackName: ")
    if not stack_name:
        raise NoNameEntered("No stack name specified")

    file_data: dict = s3.get_object_data(file_type)
    if "Parameters" in file_data:
        paramprocessor = ParamProcessor(cloudformation.profile,
                                        cloudformation.region,
                                        file_data["Parameters"])
        paramprocessor.process_stack_params()
        create_parameters = paramprocessor.processed_params
    else:
        create_parameters = []

    template_body_loacation: str = s3.get_object_url(
        version="" if not version else str(version))
    cloudformation_args = {
        "cloudformation_action": cloudformation.client.create_stack,
        "StackName": stack_name,
        "TemplateURL": template_body_loacation,
        "Parameters": create_parameters,
    }

    return cloudformation_args
示例#6
0
def cloudformation(raw_args: List[Any]) -> None:
    """Parse arguments and direct traffic to handler, internal use only.

    The raw_args are the processed args through cli.py main function.
    It also already contains the user default args so no need to process
    default args anymore.

    :param raw_args: list of args to be parsed
    :type raw_args: list
    """
    parser = argparse.ArgumentParser(
        description="Perform operations and interact with aws CloudFormation.",
        prog="fzfaws cloudformation",
    )
    subparsers = parser.add_subparsers(dest="subparser_name")

    update_cmd = subparsers.add_parser(
        "update", description="Perform update on an existing stack."
    )
    update_cmd.add_argument(
        "-b",
        "--bucket",
        nargs=1,
        action="store",
        default=[],
        help="specify a s3 path (bucketName/filename or bucketName/path/ or bucketName/) and skip s3 bucket/path selection",
    )
    update_cmd.add_argument(
        "-v",
        "--version",
        nargs="?",
        action="store",
        default=False,
        help="choose a version of the template in s3 bucket",
    )
    update_cmd.add_argument(
        "-r",
        "--root",
        action="store_true",
        default=False,
        help="search local files from root",
    )
    update_cmd.add_argument(
        "-l",
        "--local",
        nargs="?",
        action="store",
        default=False,
        help="update the stack using template in local machine",
    )
    update_cmd.add_argument(
        "-x",
        "--replace",
        action="store_true",
        default=False,
        help="perform replacing update, replace the stack with a new template",
    )
    update_cmd.add_argument(
        "-w",
        "--wait",
        action="store_true",
        default=False,
        help="wait for stack to finish update",
    )
    update_cmd.add_argument(
        "-E",
        "--extra",
        action="store_true",
        default=False,
        help="configure extra settings during update stack (E.g.Tags, iam role, notification, policy etc)",
    )
    update_cmd.add_argument(
        "-P",
        "--profile",
        nargs="?",
        action="store",
        default=False,
        help="choose/specify a profile for the operation",
    )
    update_cmd.add_argument(
        "-R",
        "--region",
        nargs="?",
        action="store",
        default=False,
        help="choose/specify a region for the operation",
    )

    create_cmd = subparsers.add_parser("create", description="Create a new stack.")
    create_cmd.add_argument(
        "-b",
        "--bucket",
        nargs=1,
        action="store",
        default=[],
        help="specify a s3 path (bucketName/filename or bucketName/path/ or bucketName/) and skip s3 bucket/path selection",
    )
    create_cmd.add_argument(
        "-v",
        "--version",
        nargs="?",
        action="store",
        default=False,
        help="choose a version of the template in s3 bucket",
    )
    create_cmd.add_argument(
        "-r",
        "--root",
        action="store_true",
        default=False,
        help="search local files from root",
    )
    create_cmd.add_argument(
        "-l",
        "--local",
        nargs="?",
        action="store",
        default=False,
        help="create stack using template in local machine",
    )
    create_cmd.add_argument(
        "-w",
        "--wait",
        action="store_true",
        default=False,
        help="wait for the stack to finish create",
    )
    create_cmd.add_argument(
        "-E",
        "--extra",
        action="store_true",
        default=False,
        help="configure extra settings during create stack (E.g.Tags, iam role, notification, policy etc)",
    )
    create_cmd.add_argument(
        "-P",
        "--profile",
        nargs="?",
        action="store",
        default=False,
        help="choose/specify a profile for the operation",
    )
    create_cmd.add_argument(
        "-R",
        "--region",
        nargs="?",
        action="store",
        default=False,
        help="choose/specify a region for the operation",
    )

    delete_cmd = subparsers.add_parser(
        "delete", description="Delete an existing stack."
    )
    delete_cmd.add_argument(
        "-i",
        "--iam",
        nargs="?",
        action="store",
        default=False,
        help="choose/specify a iam arn that has the permission to delete the current stack",
    )
    delete_cmd.add_argument(
        "-w",
        "--wait",
        action="store_true",
        default=False,
        help="wait for the stack to be deleted",
    )
    delete_cmd.add_argument(
        "-P",
        "--profile",
        nargs="?",
        action="store",
        default=False,
        help="choose/specify a profile for the operation",
    )
    delete_cmd.add_argument(
        "-R",
        "--region",
        nargs="?",
        action="store",
        default=False,
        help="choose/specify a region for the operation",
    )

    ls_cmd = subparsers.add_parser(
        "ls", description="Display infomation of the selcted stack."
    )
    ls_cmd.add_argument(
        "-r",
        "--resource",
        action="store_true",
        default=False,
        help="display information on stack resources",
    )
    ls_cmd.add_argument(
        "-P",
        "--profile",
        nargs="?",
        action="store",
        default=False,
        help="choose/specify a profile for the operation",
    )
    ls_cmd.add_argument(
        "-R",
        "--region",
        nargs="?",
        action="store",
        default=False,
        help="choose/specify a region for the operation",
    )
    ls_cmd.add_argument(
        "--tag",
        action="store_true",
        default=False,
        help="display tag of the selected stack",
    )
    ls_cmd.add_argument(
        "--arn",
        action="store_true",
        default=False,
        help="display arn of the selected stack",
    )
    ls_cmd.add_argument(
        "--name",
        action="store_true",
        default=False,
        help="display name of the selected stack",
    )
    ls_cmd.add_argument(
        "--type",
        action="store_true",
        default=False,
        help="display the type of the selected stack resource",
    )

    drift_cmd = subparsers.add_parser(
        "drift", description="Detect drift on stack/resources."
    )
    drift_cmd.add_argument(
        "-i",
        "--info",
        action="store_true",
        default=False,
        help="check the current drift status",
    )
    drift_cmd.add_argument(
        "-s",
        "--select",
        action="store_true",
        default=False,
        help="select individual resources to detect drift",
    )
    drift_cmd.add_argument(
        "-w",
        "--wait",
        action="store_true",
        default=False,
        help="wait for the drift detection result",
    )
    drift_cmd.add_argument(
        "-P",
        "--profile",
        nargs="?",
        action="store",
        default=False,
        help="choose/specify a profile for the operation",
    )
    drift_cmd.add_argument(
        "-R",
        "--region",
        nargs="?",
        action="store",
        default=False,
        help="choose/specify a region for the operation",
    )

    changeset_cmd = subparsers.add_parser(
        "changeset", description="Create a change set for the selected stack."
    )
    changeset_cmd.add_argument(
        "-b",
        "--bucket",
        nargs=1,
        action="store",
        default=[],
        help="specify a s3 path (bucketName/filename or bucketName/path/ or bucketName/) and skip s3 bucket/path selection",
    )
    changeset_cmd.add_argument(
        "-v",
        "--version",
        nargs="?",
        action="store",
        default=False,
        help="choose a version of the template in s3 bucket",
    )
    changeset_cmd.add_argument(
        "-r",
        "--root",
        action="store_true",
        default=False,
        help="search local files from root",
    )
    changeset_cmd.add_argument(
        "-l",
        "--local",
        action="store_true",
        default=False,
        help="create the changeset using template in local machine",
    )
    changeset_cmd.add_argument(
        "-w",
        "--wait",
        action="store_true",
        default=False,
        help="wait for the changeset to finish create",
    )
    changeset_cmd.add_argument(
        "-x",
        "--replace",
        action="store_true",
        default=False,
        help="perform replacing changeset, replace the current template with a new template and create the changeset",
    )
    changeset_cmd.add_argument(
        "-i", "--info", action="store_true", help="view the result of the changeset"
    )
    changeset_cmd.add_argument(
        "-e",
        "--execute",
        action="store_true",
        help="execute update based on the selected changeset",
    )
    changeset_cmd.add_argument(
        "-d", "--delete", action="store_true", help="delete the selected changeset"
    )
    changeset_cmd.add_argument(
        "-E",
        "--extra",
        action="store_true",
        default=False,
        help="configure extra settings during creating a changeset (E.g.Tags, iam role, notification, policy etc)",
    )
    changeset_cmd.add_argument(
        "-P",
        "--profile",
        nargs="?",
        action="store",
        default=False,
        help="choose/specify a profile for the operation",
    )
    changeset_cmd.add_argument(
        "-R",
        "--region",
        nargs="?",
        action="store",
        default=False,
        help="choose/specify a region for the operation",
    )

    validate_cmd = subparsers.add_parser(
        "validate",
        description="Validate the selected template.",
    )
    validate_cmd.add_argument(
        "-b",
        "--bucket",
        nargs=1,
        action="store",
        default=[],
        help="specify a s3 path (bucketName/filename or bucketName/path/ or bucketName/) and skip s3 bucket/path selection",
    )
    validate_cmd.add_argument(
        "-l",
        "--local",
        nargs="?",
        action="store",
        default=False,
        help="validate template in local machine",
    )
    validate_cmd.add_argument(
        "-r",
        "--root",
        action="store_true",
        default=False,
        help="search files from root",
    )
    validate_cmd.add_argument(
        "-v",
        "--version",
        nargs="?",
        default=False,
        help="choose a version of the template in s3 bucket",
    )
    validate_cmd.add_argument(
        "-P",
        "--profile",
        nargs="?",
        action="store",
        default=False,
        help="choose/specify a profile for the operation",
    )
    validate_cmd.add_argument(
        "-R",
        "--region",
        nargs="?",
        action="store",
        default=False,
        help="choose/specify a region for the operation",
    )
    args = parser.parse_args(raw_args)

    # if no argument provided, display help message through fzf
    if not raw_args:
        available_commands = [
            "update",
            "create",
            "delete",
            "ls",
            "drift",
            "changeset",
            "validate",
        ]
        fzf = Pyfzf()
        for command in available_commands:
            fzf.append_fzf("%s\n" % command)
        selected_command = fzf.execute_fzf(
            empty_allow=True, print_col=1, preview="fzfaws cloudformation {} -h"
        )
        if selected_command == "update":
            update_cmd.print_help()
        elif selected_command == "create":
            create_cmd.print_help()
        elif selected_command == "delete":
            delete_cmd.print_help()
        elif selected_command == "ls":
            ls_cmd.print_help()
        elif selected_command == "drift":
            drift_cmd.print_help()
        elif selected_command == "changeset":
            changeset_cmd.print_help()
        elif selected_command == "validate":
            changeset_cmd.print_help()
        sys.exit(0)

    # when user set --profile/region flag but without argument
    # argparse will have a None value instead of default value False
    # convert to True for better processing
    if args.profile == None:
        args.profile = True
    if args.region == None:
        args.region = True
    if hasattr(args, "local") and args.local == None:
        args.local = True
    if hasattr(args, "bucket"):
        args.bucket = args.bucket[0] if args.bucket else None
    if hasattr(args, "version") and args.version == None:
        args.version = True

    if args.subparser_name == "create":
        create_stack(
            args.profile,
            args.region,
            args.local,
            args.root,
            args.wait,
            args.extra,
            args.bucket,
            args.version,
        )
    elif args.subparser_name == "update":
        update_stack(
            args.profile,
            args.region,
            args.replace,
            args.local,
            args.root,
            args.wait,
            args.extra,
            args.bucket,
            args.version,
        )
    elif args.subparser_name == "delete":
        if args.iam == None:
            args.iam = True
        delete_stack(args.profile, args.region, args.wait, args.iam)
    elif args.subparser_name == "ls":
        ls_stack(
            args.profile,
            args.region,
            args.resource,
            args.name,
            args.arn,
            args.tag,
            args.type,
        )
    elif args.subparser_name == "drift":
        drift_stack(args.profile, args.region, args.info, args.select, args.wait)
    elif args.subparser_name == "changeset":
        changeset_stack(
            args.profile,
            args.region,
            args.replace,
            args.local,
            args.root,
            args.wait,
            args.info,
            args.execute,
            args.delete,
            args.extra,
            args.bucket,
            args.version,
        )
    elif args.subparser_name == "validate":
        validate_stack(
            args.profile, args.region, args.local, args.root, args.bucket, args.version
        )
示例#7
0
def s3_replacing_update(cloudformation: Cloudformation, bucket: Optional[str],
                        version: Union[str, bool]) -> Dict[str, Any]:
    """Format argument for a replacing updating through providing template on s3.

    Read the template from s3, comparing parameter names with the original stack
    to provide a preview of value if possible.

    :param cloudformation: Cloudformation instance
    :type cloudformation: Cloudformation
    :param bucket: bucket path, if set, skip fzf selection
    :type bucket: str, optional
    :param version: whether to use a versioned template in s3
    :type version: Union[str, bool]
    :return: formatted argument thats ready to be used by boto3
    :rtype: Dict[str, Any]
    """
    s3 = S3(profile=cloudformation.profile, region=cloudformation.region)
    s3.set_bucket_and_path(bucket)
    if not s3.bucket_name:
        s3.set_s3_bucket()
    if not s3.path_list[0]:
        s3.set_s3_object()

    check_is_valid(s3.path_list[0])

    if version == True:
        version = s3.get_object_version(s3.bucket_name,
                                        s3.path_list[0])[0].get(
                                            "VersionId", False)

    validate_stack(
        cloudformation.profile,
        cloudformation.region,
        bucket="%s/%s" % (s3.bucket_name, s3.path_list[0]),
        version=version if version else False,
        no_print=True,
    )

    file_type: str = ""
    if is_yaml(s3.path_list[0]):
        file_type = "yaml"
    elif is_json(s3.path_list[0]):
        file_type = "json"

    file_data: Dict[str, Any] = s3.get_object_data(file_type)
    if "Parameters" in file_data:
        paramprocessor = ParamProcessor(
            cloudformation.profile,
            cloudformation.region,
            file_data["Parameters"],
            cloudformation.stack_details.get("Parameters"),
        )
        paramprocessor.process_stack_params()
        updated_parameters = paramprocessor.processed_params
    else:
        updated_parameters = []

    template_body_loacation = s3.get_object_url(
        "" if not version else str(version))

    cloudformation_args = {
        "cloudformation_action": cloudformation.client.update_stack,
        "StackName": cloudformation.stack_name,
        "TemplateURL": template_body_loacation,
        "UsePreviousTemplate": False,
        "Parameters": updated_parameters,
    }

    return cloudformation_args