Esempio n. 1
0
    def get_stack_resources(
        self, empty_allow: bool = False, header: str = None, no_progress: bool = False
    ) -> List[str]:
        """List all stack logical resources and return the selected resources.

        :param empty_allow: allow empty selection
        :type empty_allow: bool, optional
        :param header: information to be displayed in fzf header
        :type header: str, optional
        :param no_progress: don't display progress bar, useful for ls command
        :type no_progress: bool, optional
        :return: selected list of logical resources LogicalResourceId
        :rtype: List[str]
        """
        fzf = Pyfzf()
        with Spinner.spin(
            message="Fetching stack resources ...", no_progress=no_progress
        ):
            paginator = self.client.get_paginator("list_stack_resources")
            for result in paginator.paginate(StackName=self.stack_name):
                for resource in result.get("StackResourceSummaries"):
                    resource["Drift"] = resource.get("DriftInformation").get(
                        "StackResourceDriftStatus"
                    )
                fzf.process_list(
                    result.get("StackResourceSummaries"),
                    "LogicalResourceId",
                    "ResourceType",
                    "Drift",
                )
        return list(
            fzf.execute_fzf(multi_select=True, header=header, empty_allow=empty_allow)
        )
Esempio n. 2
0
    def set_arns(
        self,
        arns: Optional[Union[str, list]] = None,
        empty_allow: bool = False,
        header: Optional[str] = None,
        multi_select: bool = False,
    ) -> None:
        """Set cloudwatch arns for further operations.

        :param arns: arns to init
        :type arns: Union[list, str], optional
        :param empty_allow: allow empty fzf selection
        :type empty_allow: bool, optional
        :param header: header in fzf
        :type header: str, optional
        :param multi_select: allow multi selection in fzf
        :type multi_select: bool, optional
        """
        if not arns:
            fzf = Pyfzf()
            with Spinner.spin(message="Fetching cloudwatch alarms ..."):
                paginator = self.client.get_paginator("describe_alarms")
                for result in paginator.paginate():
                    if result.get("CompositeAlarms"):
                        fzf.process_list(result["CompositeAlarms"], "AlarmArn")
                    if result.get("MetricAlarms"):
                        fzf.process_list(result["MetricAlarms"], "AlarmArn")
            arns = fzf.execute_fzf(
                empty_allow=empty_allow, multi_select=multi_select, header=header
            )
        if type(arns) == str:
            self.arns[0] = str(arns)
        elif type(arns) == list:
            self.arns = list(arns)
Esempio n. 3
0
    def get_vpc_id(self,
                   multi_select: bool = False,
                   header: str = None,
                   no_progress: bool = False) -> Union[str, list]:
        """Get user selected vpc id through fzf.

        :param multi_select: allow multiple value selection
        :type multi_select: bool, optional
        :param header: header to display in fzf header
        :type header: str, optional
        :param no_progress: don't display progress bar, useful for ls command
        :type no_progress: bool, optional
        :return: selected vpc id
        :rtype: Union[str, list]
        """
        fzf = Pyfzf()
        with Spinner.spin(message="Fetching VPCs ...",
                          no_progress=no_progress):
            paginator = self.client.get_paginator("describe_vpcs")
            for result in paginator.paginate():
                response_generator = self._name_tag_generator(
                    result.get("Vpcs", []))
                fzf.process_list(response_generator, "VpcId", "IsDefault",
                                 "CidrBlock", "Name")
        return fzf.execute_fzf(empty_allow=True,
                               multi_select=multi_select,
                               header=header)
Esempio n. 4
0
    def get_security_groups(
        self,
        multi_select: bool = False,
        return_attr: str = "id",
        header: str = None,
        no_progress: bool = False,
    ) -> Union[str, list]:
        """Use paginator to get the user selected security groups.

        :param multi_select: allow multiple value selection
        :type multi_select: bool, optional
        :param return_attr: what attribute to return (id|name)
        :type return_attr: str, optional
        :param header: header to display in fzf
        :type header: str, optional
        :param no_progress: don't display progress bar, useful for ls command
        :type no_progress: bool, optional
        :return: selected security groups/ids
        :rtype: Union[str, list]
        """
        fzf = Pyfzf()
        with Spinner.spin(message="Fetching SecurityGroups ...",
                          no_progress=no_progress):
            paginator = self.client.get_paginator("describe_security_groups")
            for result in paginator.paginate():
                response_generator = self._name_tag_generator(
                    result.get("SecurityGroups", []))
                if return_attr == "id":
                    fzf.process_list(response_generator, "GroupId",
                                     "GroupName", "Name")
                elif return_attr == "name":
                    fzf.process_list(response_generator, "GroupName", "Name")
        return fzf.execute_fzf(multi_select=multi_select,
                               empty_allow=True,
                               header=header)
Esempio n. 5
0
    def set_arns(
        self,
        arns: Optional[Union[str, List[str]]] = None,
        empty_allow: bool = False,
        header: Optional[str] = None,
        multi_select: bool = False,
    ) -> None:
        """Set the sns arn for other operations.

        :param arns: arns to init
        :type arns: Union[list, str], optional
        :param empty_allow: allow empty fzf selection
        :type empty_allow: bool, optional
        :param header: header in fzf
        :type header: str, optional
        :param multi_select: allow multi selection in fzf
        :type multi_select: bool, optional
        """
        if not arns:
            fzf = Pyfzf()
            with Spinner.spin(message="Fetching sns topics ..."):
                paginator = self.client.get_paginator("list_topics")
                for result in paginator.paginate():
                    fzf.process_list(result.get("Topics", []), "TopicArn")
            arns = fzf.execute_fzf(empty_allow=empty_allow,
                                   multi_select=multi_select,
                                   header=header)
        if type(arns) == str:
            self.arns[0] = str(arns)
        elif type(arns) == list:
            self.arns = list(arns)
Esempio n. 6
0
    def set_ec2_instance(self,
                         multi_select: bool = True,
                         header: str = None,
                         no_progress: bool = False) -> None:
        """Set ec2 instance for current operation.

        :param multi_select: enable multi select
        :type multi_select: bool, optional
        :param header: helper information to display in fzf header
        :type header: str, optional
        :param no_progress: don't display progress bar, useful for ls command
        :type no_progress: bool, optional
        """
        fzf = Pyfzf()
        with Spinner.spin(message="Fetching EC2 instances ...",
                          no_progress=no_progress):
            paginator = self.client.get_paginator("describe_instances")
            for result in paginator.paginate():
                response_generator = self._instance_generator(
                    result["Reservations"])
                fzf.process_list(
                    response_generator,
                    "InstanceId",
                    "Status",
                    "InstanceType",
                    "Name",
                    "KeyName",
                    "PublicDnsName",
                    "PublicIpAddress",
                    "PrivateIpAddress",
                )
        selected_instance = fzf.execute_fzf(multi_select=multi_select,
                                            header=header,
                                            print_col=0)

        if multi_select:
            self.instance_ids[:] = []
            self.instance_list[:] = []
            for instance in selected_instance:
                curr = fzf.format_selected_to_dict(str(instance))
                self.instance_list.append(curr)
                self.instance_ids.append(curr["InstanceId"])
        else:
            self.instance_ids[:] = []
            self.instance_list[:] = []
            self.instance_list.append(
                fzf.format_selected_to_dict(str(selected_instance)))
            self.instance_ids.append(self.instance_list[0]["InstanceId"])
        if len(self.instance_ids) == 0:
            self.instance_ids = [""]
        if len(self.instance_list) == 0:
            self.instance_list = [{}]
Esempio n. 7
0
    def set_s3_bucket(self, header: str = "", no_progress: bool = False) -> None:
        """List bucket through fzf and let user select a bucket.

        :param header: header to display in fzf header
        :type header: str, optional
        :param no_progress: don't display progress bar, useful for ls command
        :type no_progress: bool, optional
        """
        fzf = Pyfzf()
        with Spinner.spin(message="Fetching s3 buckets ...", no_progress=no_progress):
            response = self.client.list_buckets()
        fzf.process_list(response["Buckets"], "Name")
        self.bucket_name = str(fzf.execute_fzf(header=header))
Esempio n. 8
0
    def _get_list_param_value(self, type_name: str,
                              param_header: str) -> List[str]:
        """Handle operation if parameter type is a list type.

        This function is almost the same as _get_selected_param_value besides its
        handling list type rather than single vaiable type.

        :param type_name: name of the type of the parameter
        :type type_name: str
        :param param_header: information about the current parameter
        :type param_header: str
        :return: processed list of selection from the user
        :rtype: List[str]
        """
        fzf = Pyfzf()

        if type_name == "List<AWS::EC2::AvailabilityZone::Name>":
            with Spinner.spin(message="Fetching AvailabilityZones ..."):
                response = self.ec2.client.describe_availability_zones()
                response_list = response["AvailabilityZones"]
            fzf.process_list(response_list, "ZoneName", empty_allow=True)
        elif type_name == "List<AWS::EC2::Instance::Id>":
            return list(
                self.ec2.get_instance_id(multi_select=True,
                                         header=param_header))
        elif type_name == "List<AWS::EC2::SecurityGroup::GroupName>":
            return list(
                self.ec2.get_security_groups(multi_select=True,
                                             return_attr="name",
                                             header=param_header))
        elif type_name == "List<AWS::EC2::SecurityGroup::Id>":
            return list(
                self.ec2.get_security_groups(multi_select=True,
                                             header=param_header))
        elif type_name == "List<AWS::EC2::Subnet::Id>":
            return list(
                self.ec2.get_subnet_id(multi_select=True, header=param_header))
        elif type_name == "List<AWS::EC2::Volume::Id>":
            return list(
                self.ec2.get_volume_id(multi_select=True, header=param_header))
        elif type_name == "List<AWS::EC2::VPC::Id>":
            return list(
                self.ec2.get_vpc_id(multi_select=True, header=param_header))
        elif type_name == "List<AWS::Route53::HostedZone::Id>":
            self.route53.set_zone_id(multi_select=True)
            return self.route53.zone_ids
        return list(
            fzf.execute_fzf(multi_select=True,
                            empty_allow=True,
                            header=param_header))
Esempio n. 9
0
    def _get_selected_param_value(self, type_name: str,
                                  param_header: str) -> str:
        """Use fzf to display aws specific parameters.

        :param type_name: name of the parameter type
        :type type_name: str
        :param param_header: information about current parameter
        :type param_header: str
        :return: return the selected value
        :rtype: str
        """
        fzf = Pyfzf()

        if type_name == "AWS::EC2::KeyPair::KeyName":
            with Spinner.spin(message="Fetching KeyPair ..."):
                response = self.ec2.client.describe_key_pairs()
                response_list = response["KeyPairs"]
            fzf.process_list(response_list, "KeyName", empty_allow=True)
        elif type_name == "AWS::EC2::SecurityGroup::Id":
            return str(self.ec2.get_security_groups(header=param_header))
        elif type_name == "AWS::EC2::AvailabilityZone::Name":
            with Spinner.spin(message="Fetching AvailabilityZones ..."):
                response = self.ec2.client.describe_availability_zones()
                response_list = response.get("AvailabilityZones", [])
            fzf.process_list(response_list, "ZoneName", empty_allow=True)
        elif type_name == "AWS::EC2::Instance::Id":
            return str(self.ec2.get_instance_id(header=param_header))
        elif type_name == "AWS::EC2::SecurityGroup::GroupName":
            return str(
                self.ec2.get_security_groups(return_attr="name",
                                             header=param_header))
        elif type_name == "AWS::EC2::Subnet::Id":
            return str(self.ec2.get_subnet_id(header=param_header))
        elif type_name == "AWS::EC2::Volume::Id":
            return str(self.ec2.get_volume_id(header=param_header))
        elif type_name == "AWS::EC2::VPC::Id":
            return str(self.ec2.get_vpc_id(header=param_header))
        elif type_name == "AWS::Route53::HostedZone::Id":
            self.route53.set_zone_id()
            return self.route53.zone_ids[0]
        return str(fzf.execute_fzf(empty_allow=True, header=param_header))
Esempio n. 10
0
    def set_stack(self, no_progress=False) -> None:
        """Store the selected stack into the instance attribute.

        :param no_progress: don't display progress bar, useful for ls command
        :type no_progress: bool, optional
        """
        fzf = Pyfzf()
        with Spinner.spin(
            message="Fetching cloudformation stacks ...", no_progress=no_progress
        ):
            paginator = self.client.get_paginator("describe_stacks")
            response = paginator.paginate()
            stack_generator = self._get_stack_generator(response)
            for result in response:
                fzf.process_list(
                    result["Stacks"], "StackName", "StackStatus", "Description"
                )
        self.stack_name = str(fzf.execute_fzf(empty_allow=False))
        self.stack_details = search_dict_in_list(
            self.stack_name, stack_generator, "StackName"
        )
Esempio n. 11
0
    def get_instance_id(self,
                        multi_select: bool = False,
                        header: str = None) -> Union[str, list]:
        """Use paginator to get instance id and return it.

        :param multi_select: allow multiple value selection
        :type multi_select: bool, optional
        :param header: header to display in fzf header
        :type header: str, optional
        :return: selected instance id
        :rtype: Union[str, list]
        """
        fzf = Pyfzf()
        with Spinner.spin(message="Fetching EC2 instances ..."):
            paginator = self.client.get_paginator("describe_instances")
            for result in paginator.paginate():
                response_generator = self._instance_id_generator(
                    result.get("Reservations", []))
                fzf.process_list(response_generator, "InstanceId", "Name")
        return fzf.execute_fzf(multi_select=multi_select,
                               empty_allow=True,
                               header=header)
Esempio n. 12
0
    def set_keyids(
        self,
        keyids: Optional[Union[list, str]] = None,
        header: Optional[str] = None,
        multi_select: bool = False,
        empty_allow: bool = True,
    ) -> None:
        """Set the key for kms for further operations.

        :param keyids: keyids to set
        :type keyids: Union[list, str], optional
        :param header: header information in fzf
        :type header: str, optional
        :param multi_select: enable multi select
        :type multi_select: bool, optional
        :param empty_allow: allow empty selection
        :type empty_allow: bool, optional
        """
        if not keyids:
            fzf = Pyfzf()
            with Spinner.spin(message="Fetching kms keys ..."):
                paginator = self.client.get_paginator("list_aliases")
                for result in paginator.paginate():
                    aliases = [
                        alias for alias in result.get("Aliases")
                        if alias.get("TargetKeyId")
                    ]
                    fzf.process_list(aliases, "TargetKeyId", "AliasName",
                                     "AliasArn")
            keyids = fzf.execute_fzf(header=header,
                                     multi_select=multi_select,
                                     empty_allow=empty_allow)
        if type(keyids) == str:
            self.keyids[0] = str(keyids)
        elif type(keyids) == list:
            self.keyids = list(keyids)
Esempio n. 13
0
    def set_zone_id(
        self,
        zone_ids: Optional[Union[str, List[str]]] = None,
        multi_select: bool = False,
    ) -> None:
        """Set the hostedzone id for futher operations.

        :param zone_ids: list of zone_ids to set
        :type zone_ids: list, optional
        :param multi_select: allow multi_select
        :type multi_select: bool, optional
        """
        if zone_ids is None:
            fzf = Pyfzf()
            with Spinner.spin(message="Fetching hostedzones ..."):
                paginator = self.client.get_paginator("list_hosted_zones")
                for result in paginator.paginate():
                    result = self._process_hosted_zone(result["HostedZones"])
                    fzf.process_list(result, "Id", "Name")
            zone_ids = fzf.execute_fzf(multi_select=multi_select, empty_allow=True)
        if type(zone_ids) == str:
            self.zone_ids[0] = str(zone_ids)
        elif type(zone_ids) == list:
            self.zone_ids = list(zone_ids)
Esempio n. 14
0
    def get_object_version(
        self,
        bucket: str = "",
        key: str = "",
        delete: bool = False,
        select_all: bool = False,
        non_current: bool = False,
        multi_select: bool = True,
        no_progress: bool = False,
    ) -> List[Dict[str, str]]:
        """List object versions through fzf.
        
        :param bucket: object's bucketname, if not set, class instance's bucket_name will be used
        :type bucket: str, optional
        :param key: object's key, if not set, class instance's path_list[0] will be used
        :type key: str, optional
        :param delete: allow to choose delete marker
        :type delete: bool, optional
        :param select_all: skip fzf and select all version and put into return list
        :type select_all: bool, optional
        :param non_current: only put non_current versions into list
        :type non_current: bool, optional
        :param multi_select: allow multi selection
        :type multi_select: bool, optional
        :param no_progress: don't display progress bar, useful for ls command
        :type no_progress: bool, optional
        :return: list of selected versions
        :rtype: List[Dict[str, str]]

        Example return value:
            [{'Key': s3keypath, 'VersionId': s3objectid}]
        """
        bucket = bucket if bucket else self.bucket_name
        key_list: list = []
        fzf = Pyfzf()

        if key:
            key_list.append(key)
        else:
            key_list.extend(self.path_list)
        selected_versions: list = []
        for key in key_list:
            response_generator: Union[list, Generator[Dict[str, str], None,
                                                      None]] = []
            with Spinner.spin(message="Fetching object versions ...",
                              no_progress=no_progress):
                paginator = self.client.get_paginator("list_object_versions")
                for result in paginator.paginate(Bucket=bucket, Prefix=key):
                    response_generator = self._version_generator(
                        result.get("Versions", []),
                        result.get("DeleteMarkers", []),
                        non_current,
                        delete,
                    )
                    if not select_all:
                        fzf.process_list(
                            response_generator,
                            "VersionId",
                            "Key",
                            "IsLatest",
                            "DeleteMarker",
                            "LastModified",
                        )
                    else:
                        selected_versions.extend([{
                            "Key":
                            key,
                            "VersionId":
                            version.get("VersionId")
                        } for version in response_generator])

            if not select_all:
                if delete and multi_select:
                    for result in fzf.execute_fzf(multi_select=True):
                        selected_versions.append({
                            "Key": key,
                            "VersionId": result
                        })
                else:
                    selected_versions.append({
                        "Key":
                        key,
                        "VersionId":
                        str(fzf.execute_fzf())
                    })
        return selected_versions
Esempio n. 15
0
def changeset_stack(
    profile: Union[str, bool] = False,
    region: Union[str, bool] = False,
    replace: bool = False,
    local_path: Union[str, bool] = False,
    root: bool = False,
    wait: bool = False,
    info: bool = False,
    execute: bool = False,
    delete: bool = False,
    extra: bool = False,
    bucket: str = None,
    version: Union[str, bool] = False,
) -> None:
    """Handle changeset actions.

    Main function to interacte with changeset, use argument
    to control the actions.

    This function is using update_stack to handle all the dirty
    works as both functions are processing cloudformation arguments
    and having the same arguments.

    :param profile: use a different profile for this operation
    :type profile: Union[bool, str], optional
    :param region: use a different region for this operation
    :type region: Union[bool, str], optional
    :param replace: replace the template during update
    :type replace: bool, optional
    :param local_path: Select a template from local machine
    :type local_path: Union[bool, str], optional
    :param root: Search local file from root directory
    :type root: bool, optional
    :param wait: wait for stack to be completed before exiting the program
    :type wait: bool, optional
    :param info: display result of a changeset
    :type info: bool, optional
    :param execute: execute changeset
    :type execute: bool, optional
    :param delete: delete changeset
    :type delete: bool, optional
    :param extra: configure extra options for the stack, (tags, IAM, termination protection etc..)
    :type extra: bool, optional
    :param bucket: specify a bucket/bucketpath to skip s3 selection
    :type bucket: str, optional
    :param version: use previous version of template in s3 bucket
    :type version: Union[bool, str], optional
    :raises NoNameEntered: If no changset name is entered
    """
    cloudformation = Cloudformation(profile, region)
    cloudformation.set_stack()

    # if not creating new changeset
    if info or execute or delete:
        fzf = Pyfzf()
        response: Dict[str, Any] = cloudformation.client.list_change_sets(
            StackName=cloudformation.stack_name)
        # get the changeset name
        fzf.process_list(
            response.get("Summaries", []),
            "ChangeSetName",
            "StackName",
            "ExecutionStatus",
            "Status",
            "Description",
        )

        if info:
            selected_changeset = str(fzf.execute_fzf())
            describe_changes(cloudformation, selected_changeset)

        # execute the change set
        elif execute:
            selected_changeset = fzf.execute_fzf()
            if get_confirmation("Execute changeset %s?" % selected_changeset):
                response = cloudformation.client.execute_change_set(
                    ChangeSetName=selected_changeset,
                    StackName=cloudformation.stack_name,
                )
                cloudformation.wait("stack_update_complete",
                                    "Wating for stack to be updated ...")
                print("Stack updated")

        elif delete:
            selected_changeset = fzf.execute_fzf(multi_select=True)
            for changeset in selected_changeset:
                print("(dryrun) Delete changeset %s" % changeset)
            if get_confirmation("Confirm?"):
                for changeset in selected_changeset:
                    cloudformation.client.delete_change_set(
                        ChangeSetName=changeset,
                        StackName=cloudformation.stack_name)

    else:
        changeset_name = input("Enter name of this changeset: ")
        if not changeset_name:
            raise NoNameEntered("No changeset name specified")
        changeset_description = input("Description: ")
        # since is almost same operation as update stack
        # let update_stack handle it, but return update details instead of execute
        cloudformation_args = update_stack(
            cloudformation.profile,
            cloudformation.region,
            replace,
            local_path,
            root,
            wait,
            extra,
            bucket,
            version,
            dryrun=True,
            cloudformation=cloudformation,
        )
        cloudformation_args[
            "cloudformation_action"] = cloudformation.client.create_change_set
        cloudformation_args["ChangeSetName"] = changeset_name
        if changeset_description:
            cloudformation_args["Description"] = changeset_description

        response = cloudformation.execute_with_capabilities(
            **cloudformation_args)

        response.pop("ResponseMetadata", None)
        print(json.dumps(response, indent=4, default=str))
        print(80 * "-")
        print("Changeset create initiated")

        if wait:
            cloudformation.wait(
                "change_set_create_complete",
                "Wating for changset to be created ...",
                ChangeSetName=changeset_name,
            )
            print("Changeset created")
            describe_changes(cloudformation, changeset_name)
Esempio n. 16
0
class TestPyfzf(unittest.TestCase):
    def setUp(self):
        fileloader = FileLoader()
        config_path = Path(__file__).resolve().parent.joinpath(
            "../data/fzfaws.yml")
        fileloader.load_config_file(config_path=str(config_path))
        self.capturedOutput = io.StringIO()
        sys.stdout = self.capturedOutput
        self.fzf = Pyfzf()

    def tearDown(self):
        sys.stdout = sys.__stdout__

    @patch("fzfaws.utils.pyfzf.sys")
    def test_constructor(self, mocked_sys):
        self.assertRegex(
            self.fzf.fzf_path,
            r".*/fzfaws.*/libs/fzf-[0-9]\.[0-9]+\.[0-9]-(linux|darwin)_(386|amd64)",
        )
        self.assertEqual("", self.fzf.fzf_string)

        mocked_sys.maxsize = 4294967295
        mocked_sys.platform = "linux"
        fzf = Pyfzf()
        self.assertRegex(
            fzf.fzf_path,
            r".*/fzfaws.*/libs/fzf-[0-9]\.[0-9]+\.[0-9]-linux_386")

        mocked_sys.maxsize = 42949672951
        mocked_sys.platform = "darwin"
        fzf = Pyfzf()
        self.assertRegex(
            fzf.fzf_path,
            r".*/fzfaws.*/libs/fzf-[0-9]\.[0-9]+\.[0-9]-darwin_amd64")

        mocked_sys.maxsize = 42949672951
        mocked_sys.platform = "windows"
        mocked_sys.exit.side_effect = sys.exit
        self.assertRaises(SystemExit, Pyfzf)
        self.assertEqual(
            self.capturedOutput.getvalue(),
            "fzfaws currently is only compatible with python3.6+ on MacOS or Linux\n",
        )

    def test_append_fzf(self):
        self.fzf.fzf_string = ""
        self.fzf.append_fzf("hello\n")
        self.fzf.append_fzf("world\n")
        self.assertEqual("hello\nworld\n", self.fzf.fzf_string)

    def test_construct_fzf_command(self):
        cmd_list = self.fzf._construct_fzf_cmd()
        self.assertEqual(
            cmd_list[1:],
            [
                "--ansi",
                "--expect=ctrl-c",
                "--color=dark",
                "--color=fg:-1,bg:-1,hl:#c678dd,fg+:#ffffff,bg+:-1,hl+:#c678dd",
                "--color=info:#98c379,prompt:#61afef,pointer:#e06c75,marker:#e5c07b,spinner:#61afef,header:#61afef",
                "--height",
                "100%",
                "--layout=reverse",
                "--border",
                "--cycle",
                "--bind=alt-a:toggle-all,alt-j:jump,alt-0:top,alt-s:toggle-sort",
            ],
        )

    @patch.object(subprocess, "Popen")
    @patch.object(subprocess, "check_output")
    def test_execute_fzf(self, mocked_output, mocked_popen):
        mocked_output.return_value = b"hello"
        result = self.fzf.execute_fzf(print_col=1)
        self.assertEqual(result, "hello")
        mocked_output.assert_called_once()

        mocked_output.return_value = b""
        self.assertRaises(NoSelectionMade, self.fzf.execute_fzf)

        mocked_output.return_value = b""
        result = self.fzf.execute_fzf(empty_allow=True)
        self.assertEqual("", result)

        mocked_output.return_value = b"hello"
        result = self.fzf.execute_fzf(multi_select=True, print_col=1)
        self.assertEqual(result, ["hello"])

        mocked_output.return_value = b"hello\nworld"
        result = self.fzf.execute_fzf(multi_select=True,
                                      print_col=1,
                                      preview="hello",
                                      header="foo boo")
        self.assertEqual(result, ["hello", "world"])

        mocked_output.return_value = b"hello world\nfoo boo"
        result = self.fzf.execute_fzf(multi_select=True, print_col=0)
        self.assertEqual(result, ["hello world", "foo boo"])

    @patch.object(subprocess, "Popen")
    @patch.object(subprocess, "check_output")
    def test_check_ctrl_c(self, mocked_output, mocked_popen):
        mocked_output.return_value = b"ctrl-c"
        self.assertRaises(KeyboardInterrupt, self.fzf.execute_fzf)
        mocked_output.return_value = b"hello world"
        try:
            result = self.fzf.execute_fzf()
            self.assertEqual(result, "world")
        except:
            self.fail("ctrl-c test failed, unexpected exception raise")

    @patch("fzfaws.utils.Pyfzf._check_fd")
    @patch.object(subprocess, "Popen")
    @patch.object(subprocess, "check_output")
    def test_get_local_file(self, mocked_output, mocked_popen, mocked_check):
        mocked_check.return_value = False
        mocked_output.return_value = b""
        self.assertRaises(NoSelectionMade, self.fzf.get_local_file)
        mocked_popen.assert_called_with("find * -type f",
                                        shell=True,
                                        stderr=ANY,
                                        stdout=ANY)

        mocked_output.return_value = b"hello"
        result = self.fzf.get_local_file()
        self.assertEqual("hello", result)

        mocked_output.return_value = b"hello"
        result = self.fzf.get_local_file(multi_select=True)
        self.assertEqual(result, ["hello"])

        mocked_output.return_value = b"hello\nworld\n"
        result = self.fzf.get_local_file(multi_select=True)
        self.assertEqual(result, ["hello", "world"])

        result = self.fzf.get_local_file(directory=True, search_from_root=True)
        mocked_popen.assert_called_with(
            "echo \033[33m./\033[0m; find * -type d",
            shell=True,
            stderr=ANY,
            stdout=ANY)

        result = self.fzf.get_local_file(cloudformation=True)
        mocked_popen.assert_called_with(
            'find * -type f -name "*.json" -o -name "*.yaml" -o -name "*.yml"',
            shell=True,
            stderr=ANY,
            stdout=ANY,
        )

        mocked_output.reset_mock()
        mocked_check.return_value = True
        result = self.fzf.get_local_file(cloudformation=True, header="hello")
        mocked_popen.assert_called_with(
            "fd --type f --regex '(yaml|yml|json)$'",
            shell=True,
            stderr=ANY,
            stdout=ANY,
        )
        mocked_output.assert_called_once()

        result = self.fzf.get_local_file(directory=True)
        mocked_popen.assert_called_with(
            "echo \033[33m./\033[0m; fd --type d",
            shell=True,
            stderr=ANY,
            stdout=ANY,
        )

        result = self.fzf.get_local_file()
        mocked_popen.assert_called_with(
            "fd --type f",
            shell=True,
            stderr=ANY,
            stdout=ANY,
        )

    @patch("fzfaws.utils.pyfzf.subprocess")
    def test_check_fd(self, mocked_subprocess):
        mocked_subprocess.run.return_value = True
        result = self.fzf._check_fd()
        self.assertEqual(result, True)

        mocked_subprocess.run.side_effect = Exception(
            subprocess.CalledProcessError)
        result = self.fzf._check_fd()
        self.assertEqual(result, False)

    def test_process_list(self):
        self.fzf.fzf_string = ""
        self.assertRaises(EmptyList, self.fzf.process_list, [], "123")

        self.fzf.fzf_string = ""
        self.fzf.process_list([], "123", "asfasd", "bbbb", empty_allow=True)

        test_list = [{"foo": 1, "boo": 2}, {"foo": "b"}]
        self.fzf.process_list(test_list, "foo")
        self.assertEqual(self.fzf.fzf_string, "foo: 1\nfoo: b\n")

        self.fzf.fzf_string = ""
        self.fzf.process_list(test_list, "boo")
        self.assertEqual(self.fzf.fzf_string, "boo: 2\nboo: None\n")

        self.fzf.fzf_string = ""
        self.fzf.process_list(test_list, "www")
        self.assertEqual(self.fzf.fzf_string, "www: None\nwww: None\n")

        self.fzf.fzf_string = ""
        self.fzf.process_list(test_list, "foo", "boo")
        self.assertEqual(self.fzf.fzf_string,
                         "foo: 1 | boo: 2\nfoo: b | boo: None\n")

    @patch.object(Pyfzf, "execute_fzf")
    def test_format_selected_to_dict(self, mocked_execute):
        mocked_execute.return_value = "foo: 1 | boo: 2 | wtf: None"
        result = str(self.fzf.execute_fzf(print_col=0))
        result = self.fzf.format_selected_to_dict(result)
        self.assertEqual(result, {"boo": "2", "foo": "1", "wtf": None})