Example #1
0
    def __init__(
        self,
        profile: Optional[Union[str, bool]] = None,
        region: Optional[Union[str, bool]] = None,
        service_name: str = "",
    ) -> None:
        """Construct a BaseSession instance.

        If profile or region is True value, then
        fzf will be launched to let user select region or profile.
        """
        session = Session()
        selected_profile: Optional[str] = None
        selected_region: Optional[str] = None
        if profile and type(profile) == bool:
            fzf = Pyfzf()
            for profile in session.available_profiles:
                fzf.append_fzf("%s\n" % profile)
            selected_profile = str(fzf.execute_fzf(print_col=1))
        elif profile and type(profile) is str:
            selected_profile = str(profile)

        if region and type(region) == bool:
            fzf = Pyfzf()
            regions = session.get_available_regions(service_name)
            for region in regions:
                fzf.append_fzf("%s\n" % region)
            selected_region = str(fzf.execute_fzf(print_col=1))
        elif region and type(region) is str:
            selected_region = str(region)

        if not selected_profile:
            selected_profile = os.getenv(
                "FZFAWS_%s_PROFILE" % service_name.upper(), "")
            if not selected_profile:
                selected_profile = os.getenv("FZFAWS_GLOBAL_PROFILE", None)
        if not selected_region:
            selected_region = os.getenv(
                "FZFAWS_%s_REGION" % service_name.upper(), "")
            if not selected_region:
                selected_region = os.getenv("FZFAWS_GLOBAL_REGION", None)

        self.profile: Optional[str] = selected_profile
        self.region: Optional[str] = selected_region
        self.session = Session(region_name=selected_region,
                               profile_name=selected_profile)
        self._client = self.session.client(service_name)

        # only certain service support resource
        resources = self.session.get_available_resources()
        if service_name in resources:
            self._resource = self.session.resource(service_name)
Example #2
0
    def _get_path_option(self, download: bool = False) -> str:
        """Pop fzf for user to select what to do with the path.

        :param download: if not download, insert append option
        :type download: bool, optional
        :return: selected option
        :rtype: str
        """
        fzf = Pyfzf()
        fzf.append_fzf("root: operate on the root level of the bucket\n")
        fzf.append_fzf(
            "interactively: interactively select a path through s3\n")
        fzf.append_fzf("input: manully input the path/name\n")
        if not download:
            fzf.append_fzf(
                "append: interactively select a path and then input new path/name to append"
            )
        selected_option = str(
            fzf.execute_fzf(
                print_col=1,
                header=
                "Please select which level of the bucket would you like to operate in",
                delimiter=": ",
            ))
        return selected_option
Example #3
0
    def _get_capabilities(self, message: str = "") -> List[str]:
        """Display help message and let user select capabilities.

        :param message: message to display in fzf header
        :type message: str, optional
        :return: selected capabilities to acknowledge
        :rtype: List[str]
        """
        fzf = Pyfzf()
        fzf.append_fzf("CAPABILITY_IAM\n")
        fzf.append_fzf("CAPABILITY_NAMED_IAM\n")
        fzf.append_fzf("CAPABILITY_AUTO_EXPAND")
        message += "\nPlease select the capabilities to acknowledge and proceed"
        message += "\nMore information: https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_CreateStack.html"
        return list(
            fzf.execute_fzf(
                empty_allow=True, print_col=1, multi_select=True, header=message
            )
        )
Example #4
0
    def set_ACL(self,
                original: bool = False,
                version: Optional[List[Dict[str, str]]] = None) -> None:
        """Set the ACL option.

        :param original: display original value
        :type original: bool, optional
        :param version: version object to set acl for version
        :type version: List[Dict[str, str]], optional
        """
        if not version:
            version = []

        fzf = Pyfzf()
        fzf.append_fzf("None (use bucket default ACL setting)\n")
        fzf.append_fzf(
            "Canned ACL (predefined set of grantees and permissions)\n")
        fzf.append_fzf(
            "Explicit ACL (explicit set grantees and permissions)\n")
        result = fzf.execute_fzf(
            empty_allow=True,
            print_col=1,
            header=
            "select a type of ACL to grant, aws accept one of canned ACL or explicit ACL",
        )
        if result == "Canned":
            self._set_canned_ACL()
        elif result == "Explicit":
            self._set_explicit_ACL(original=original, version=version)
        else:
            return
Example #5
0
    def set_encryption(self, original: str = None) -> None:
        """Set the encryption setting.

        :param original: previous value of the encryption
        :type original: str, optional
        """
        header = "select an ecryption setting, esc to use the default encryption setting for the bucket"
        if original:
            header += "\nOriginal: %s" % original

        fzf = Pyfzf()
        fzf.append_fzf("None (Use bucket default setting)\n")
        fzf.append_fzf("AES256\n")
        fzf.append_fzf("aws:kms\n")
        result: str = str(
            fzf.execute_fzf(empty_allow=True, print_col=1, header=header))
        if result:
            self._extra_args["ServerSideEncryption"] = result
        if result == "aws:kms":
            current_region = self.s3.client.get_bucket_location(
                Bucket=self.s3.bucket_name)
            current_region = current_region.get("LocationConstraint")
            kms = KMS(self.s3.profile, self.s3.region)
            kms.set_keyids(header="select encryption key to use")
            self._extra_args["SSEKMSKeyId"] = kms.keyids[0]
Example #6
0
    def _get_user_input(
        self,
        parameter_key: str,
        parameter_type: str,
        param_header: str,
        value_type: str = None,
        default: str = None,
    ) -> Union[str, List[str]]:
        """Get user input.

        :param parameter_key: the current parameter key to obtain user input
        :type parameter_key: str
        :param parameter_type: type of the parameter
        :type parameter_type: str
        :param param_header: information about current parameter
        :type param_header: str
        :param value_type: determine if the current action is update or new creation ('Default|Original')
        :type value_type: str, optional
        :param default: default value of params or orignal value
        :type default: str, optional
        :return: return the user selection through fzf or python input
        :rtype: Union[str, List[str]]
        """
        user_input: Union[str, List[str]] = ""

        # execute fzf if allowed_value array exists
        if "AllowedValues" in self.params[parameter_key]:
            param_header += self._print_parameter_key(parameter_key,
                                                      value_type, default)
            fzf = Pyfzf()
            for allowed_value in self.params[parameter_key]["AllowedValues"]:
                fzf.append_fzf("%s\n" % allowed_value)
            user_input = fzf.execute_fzf(empty_allow=True,
                                         print_col=1,
                                         header=param_header)
        else:
            if parameter_type in self._aws_specific_param:
                param_header += self._print_parameter_key(
                    parameter_key, value_type, default)
                user_input = self._get_selected_param_value(
                    parameter_type, param_header)
            elif parameter_type in self._aws_specific_list_param:
                param_header += self._print_parameter_key(
                    parameter_key, value_type, default)
                user_input = self._get_list_param_value(
                    parameter_type, param_header)
            else:
                print(param_header.rstrip())
                if not value_type:
                    user_input = input("%s: " % parameter_key)
                elif value_type == "Default":
                    user_input = input("%s(Default: %s): " %
                                       (parameter_key, default))
                elif value_type == "Original":
                    user_input = input("%s(Original: %s): " %
                                       (parameter_key, default))
        if not user_input and default:
            return default
        elif user_input == "''":
            return ""
        elif user_input == '""':
            return ""
        else:
            return user_input
Example #7
0
    def set_s3_path(self, download: bool = False) -> None:
        """Set 'path' of s3 to upload or download.

        s3 folders are not actually folder, found this path listing on
        https://github.com/boto/boto3/issues/134#issuecomment-116766812

        This method would set the 'path' for s3 however the self.path_list cannot be used
        as the destination of upload immediately. This only set the path
        without handling different upload sceanario. Please use the
        get_s3_destination_key after set_s3_path to obtain the correct destination key

        :param download: if not download, add append option
        :type download: bool, optional
        :raises NoSelectionMade: when user did not make a bucket selection, exit
        """
        selected_option = self._get_path_option(download=download)

        if selected_option == "input":
            self.path_list[0] = input("Input the path(newname or newpath/): ")
        elif selected_option == "root":
            # print("S3 file path is set to root")
            pass
        elif selected_option == "append" or selected_option == "interactively":
            paginator = self.client.get_paginator("list_objects")
            fzf = Pyfzf()
            parents = []
            # interactively search down 'folders' in s3
            while True:
                if len(parents) > 0:
                    fzf.append_fzf("\033[34m../\033[0m\n")
                fzf.append_fzf("\033[33m./\033[0m\n")
                with Spinner.spin(message="Fetching s3 objects ..."):
                    preview: str = ""
                    for result in paginator.paginate(
                            Bucket=self.bucket_name,
                            Prefix=self.path_list[0],
                            Delimiter="/",
                    ):
                        for prefix in result.get("CommonPrefixes", []):
                            fzf.append_fzf("%s\n" % prefix.get("Prefix"))
                        for content in result.get("Contents", []):
                            preview += content.get("Key")
                            preview += "^"

                # has to use tr to transform the string to new line during preview by fzf
                # not sure why, but if directly use \n, fzf preview interpret as a new command
                # TODO: findout why
                selected_path = str(
                    fzf.execute_fzf(
                        print_col=0,
                        header=
                        'PWD: s3://%s/%s (select "./" will the current path)' %
                        (self.bucket_name, self.path_list[0]),
                        preview="echo %s | tr '^' '\n'" % preview.rstrip(),
                    ))
                if not selected_path:
                    raise NoSelectionMade
                if selected_path == "../":
                    self.path_list[0] = parents.pop()
                elif selected_path == "./":
                    break
                else:
                    parents.append(self.path_list[0])
                    self.path_list[0] = selected_path
                # reset fzf string
                fzf.fzf_string = ""

            if selected_option == "append":
                print("Current PWD is s3://%s/%s" %
                      (self.bucket_name, self.path_list[0]))
                new_path = input(
                    "Input the new path to append(newname or newpath/): ")
                self.path_list[0] += new_path
        print("S3 file path is set to %s" %
              (self.path_list[0] if self.path_list[0] else "root"))
Example #8
0
    def set_s3_object(
        self,
        version: bool = False,
        multi_select: bool = False,
        deletemark: bool = False,
        no_progress: bool = False,
    ) -> None:
        """List object within a bucket and let user select a object.

        Stores the file path and the filetype into the instance attributes
        using paginator to get all results.

        All of the deleted object are displayed in red color when version mode
        is enabled.

        :param version: enable version search
        :type version: bool, optional
        :param multi_select: enable multi selection
        :type multi_select: bool, optional
        :param deletemark: show deletemark object in the list
        :type deletemark: bool, optional
        :param no_progress: don't display progress bar, useful for ls command
        :type no_progress: bool, optional
        :raises NoSelectionMade: when there is no selection made
        """
        fzf = Pyfzf()

        if not version:
            paginator = self.client.get_paginator("list_objects")
            with Spinner.spin(message="Fetching s3 objects ...",
                              no_progress=no_progress):
                for result in paginator.paginate(Bucket=self.bucket_name):
                    for file in result.get("Contents", []):
                        if file.get("Key").endswith(
                                "/") or not file.get("Key"):
                            # user created dir in S3 console will appear in the result and is not operatable
                            continue
                        fzf.append_fzf("Key: %s\n" % file.get("Key"))
            if multi_select:
                self.path_list = list(
                    fzf.execute_fzf(multi_select=True, delimiter=": "))
            else:
                self.path_list[0] = str(fzf.execute_fzf(delimiter=": "))

        else:
            paginator = self.client.get_paginator("list_object_versions")
            with Spinner.spin(message="Fetching s3 objects ...",
                              no_progress=no_progress):
                results = paginator.paginate(Bucket=self.bucket_name)
                version_obj_genrator = self._uniq_object_generator(
                    results, deletemark)
                generated = False
                for item in version_obj_genrator:
                    generated = True
                    fzf.append_fzf(item + "\n")
                if not generated:
                    raise NoSelectionMade
            if multi_select:
                self.path_list = list(
                    fzf.execute_fzf(delimiter=": ", multi_select=True))
            else:
                self.path_list[0] = str(fzf.execute_fzf(delimiter=": "))
Example #9
0
 def _set_canned_ACL(self) -> None:
     """Set the canned ACL for the current operation."""
     fzf = Pyfzf()
     fzf.append_fzf("private\n")
     fzf.append_fzf("public-read\n")
     fzf.append_fzf("public-read-write\n")
     fzf.append_fzf("authenticated-read\n")
     fzf.append_fzf("aws-exec-read\n")
     fzf.append_fzf("bucket-owner-read\n")
     fzf.append_fzf("bucket-owner-full-control\n")
     result: str = str(
         fzf.execute_fzf(
             empty_allow=True,
             print_col=1,
             header=
             "select a Canned ACL option, esc to use the default ACL setting for the bucket",
         ))
     if result:
         self._extra_args["ACL"] = result
Example #10
0
    def _set_explicit_ACL(
            self,
            original: bool = False,
            version: Optional[List[Dict[str, str]]] = None) -> None:
        """Set explicit ACL for grantees and permissions.

        Get user id/email first than display fzf allow multi_select
        to select permissions

        example version: [{"Key", key, "VersionId": versionid}]

        :param original: display original value
        :type original: bool, optional
        :param version: version of the object
        :type version: List[Dict[str, str]], optional
        """
        original_acl: Dict[str, List[str]] = {
            "FULL_CONTROL": [],
            "WRITE_ACP": [],
            "READ": [],
            "READ_ACP": [],
        }

        # get original values
        if original:
            acls = None
            if not version:
                acls = self.s3.client.get_object_acl(
                    Bucket=self.s3.bucket_name, Key=self.s3.path_list[0])
            elif len(version) == 1:
                acls = self.s3.client.get_object_acl(
                    Bucket=self.s3.bucket_name,
                    Key=self.s3.path_list[0],
                    VersionId=version[0].get("VersionId"),
                )
            if acls:
                owner = acls["Owner"]["ID"]
                for grantee in acls.get("Grants", []):
                    if grantee["Grantee"].get("EmailAddress"):
                        original_acl[grantee["Permission"]].append(
                            "%s=%s" % ("emailAddress",
                                       grantee["Grantee"].get("EmailAddress")))
                    elif (grantee["Grantee"].get("ID")
                          and grantee["Grantee"].get("ID") != owner):
                        original_acl[grantee["Permission"]].append(
                            "%s=%s" % ("id", grantee["Grantee"].get("ID")))
                    elif grantee["Grantee"].get("URI"):
                        original_acl[grantee["Permission"]].append(
                            "%s=%s" % ("uri", grantee["Grantee"].get("URI")))

                print("Current ACL:")
                print(json.dumps(original_acl, indent=4, default=str))
                print("Note: fzf.aws cannot preserve previous ACL permission")
                if not get_confirmation("Continue?"):
                    return

        # get what permission to set
        fzf = Pyfzf()
        fzf.append_fzf("GrantFullControl\n")
        fzf.append_fzf("GrantRead\n")
        fzf.append_fzf("GrantReadACP\n")
        fzf.append_fzf("GrantWriteACP\n")
        results: List[str] = list(
            fzf.execute_fzf(empty_allow=True, print_col=1, multi_select=True))
        if not results:
            print(
                "No permission is set, default ACL settings of the bucket would be used"
            )
        else:
            for result in results:
                print("Set permisstion for %s" % result)
                print(
                    "Enter a list of either the Canonical ID, Account email, Predefined Group url to grant permission (Seperate by comma)"
                )
                print(
                    "Format: id=XXX,id=XXX,[email protected],uri=http://acs.amazonaws.com/groups/global/AllUsers"
                )
                if original:
                    print(80 * "-")
                    if result == "GrantFullControl" and original_acl.get(
                            "FULL_CONTROL"):
                        print("Orignal: %s" %
                              ",".join(original_acl.get("FULL_CONTROL", [])))
                    elif result == "GrantRead" and original_acl.get("READ"):
                        print("Orignal: %s" %
                              ",".join(original_acl.get("READ", [])))
                    elif result == "GrantReadACP" and original_acl.get(
                            "READ_ACP"):
                        print("Orignal: %s" %
                              ",".join(original_acl.get("READ_ACP", [])))
                    elif result == "GrantWriteACP" and original_acl.get(
                            "WRITE_ACP"):
                        print("Orignal: %s" %
                              ",".join(original_acl.get("WRITE_ACP", [])))
                accounts = input("Accounts: ")
                print(80 * "-")
                self._extra_args[result] = str(accounts)
Example #11
0
    def set_extra_args(
        self,
        storage: bool = False,
        acl: bool = False,
        metadata: bool = False,
        encryption: bool = False,
        tags: bool = False,
        version: Optional[List[Dict[str, str]]] = None,
        upload: bool = False,
    ) -> None:
        """Determine what are the extra settings to set.

        Use fzf menu to let user select attributes to configure.

        :param storage: set storage class
        :type storage: bool, optional
        :param bool: set acl of object
        :type bool: bool, optional
        :param metadata: set metadata
        :type metadata: bool, optional
        :param encryption: set encryption
        :type encryption: bool, optional
        :param tags: set tags
        :type tags: bool, optional
        :param version: specify version of object to modify
        :type version: List[Dict[str, str]], optional
        :param upload: determine if the fzf menu could have empty selection
            allow empty selection during upload operation but not for other operations
        :type upload: bool, optional
        """
        if not version:
            version = []
        attributes: List[str] = []

        if version:
            # only allow modification of the two attributes for versioned object
            # because other modification would introduce a new version
            if not metadata and not acl and not tags:
                fzf = Pyfzf()
                fzf.append_fzf("ACL\n")
                fzf.append_fzf("Tagging")
                attributes = list(
                    fzf.execute_fzf(
                        print_col=1,
                        multi_select=True,
                        empty_allow=False,
                        header="select attributes to configure",
                    ))
        else:
            if not storage and not acl and not metadata and not encryption and not tags:
                fzf = Pyfzf()
                fzf.append_fzf("StorageClass\n")
                fzf.append_fzf("ACL\n")
                fzf.append_fzf("Encryption\n")
                fzf.append_fzf("Metadata\n")
                fzf.append_fzf("Tagging\n")
                attributes = list(
                    fzf.execute_fzf(
                        print_col=1,
                        multi_select=True,
                        empty_allow=upload,
                        header="select attributes to configure",
                    ))

        for attribute in attributes:
            if attribute == "StorageClass":
                storage = True
            elif attribute == "ACL":
                acl = True
            elif attribute == "Metadata":
                metadata = True
            elif attribute == "Encryption":
                encryption = True
            elif attribute == "Tagging":
                tags = True

        old_storage_class: str = ""
        old_encryption: str = ""
        old_metadata: str = ""

        # only show previous values if one object is selected
        if (not upload and not version and len(self.s3.path_list) == 1
                and not self.s3.path_list[0].endswith("/")
                and self.s3.path_list[0] != ""):
            s3_obj = self.s3.resource.Object(self.s3.bucket_name,
                                             self.s3.path_list[0])
            old_storage_class = (s3_obj.storage_class
                                 if s3_obj.storage_class else "STANDARD")
            old_encryption = (s3_obj.server_side_encryption
                              if s3_obj.server_side_encryption else "None")
            if s3_obj.metadata:
                old_metadata_list: list = []
                for key, value in s3_obj.metadata.items():
                    old_metadata_list.append("%s=%s" % (key, value))
                old_metadata = "&".join(old_metadata_list)

        if storage:
            self.set_storageclass(original=old_storage_class)
        if acl:
            display_original = (True if not upload
                                and len(self.s3.path_list) == 1 else False)
            self.set_ACL(original=display_original, version=version)
        if encryption:
            self.set_encryption(original=old_encryption)
        if metadata:
            self.set_metadata(original=old_metadata)
        if tags:
            display_original = (True if not upload
                                and len(self.s3.path_list) == 1 else False)
            self.set_tags(original=display_original, version=version)
Example #12
0
    def set_storageclass(self, original: str = None) -> None:
        """Set valid storage class.

        :param original: original value of the storage_class
        :type original: str, optional
        """
        header = "select a storage class, esc to use the default storage class of the bucket setting"
        if original:
            header += "\nOriginal: %s" % original

        fzf = Pyfzf()
        fzf.append_fzf("STANDARD\n")
        fzf.append_fzf("REDUCED_REDUNDANCY\n")
        fzf.append_fzf("STANDARD_IA\n")
        fzf.append_fzf("ONEZONE_IA\n")
        fzf.append_fzf("INTELLIGENT_TIERING\n")
        fzf.append_fzf("GLACIER\n")
        fzf.append_fzf("DEEP_ARCHIVE\n")
        result = fzf.execute_fzf(empty_allow=True, print_col=1, header=header)
        if result:
            self._extra_args["StorageClass"] = result
Example #13
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})
Example #14
0
    def set_extra_args(
        self,
        tags: bool = False,
        rollback: bool = False,
        permissions: bool = False,
        stack_policy: bool = False,
        creation_option: bool = False,
        notification: bool = False,
        update: bool = False,
        search_from_root: bool = False,
        dryrun: bool = False,
    ) -> None:
        """Set extra arguments.

        Used to determine what args to set and acts like a router

        :param tags: configure tags for the stack
        :type tags: bool, optional
        :param rollback: set rollback configuration for the stack
        :type rollback: bool, optional
        :param iam: use a specific iam role for this operation
        :type iam: bool, optional
        :param stack_policy: add stack_policy to the stack
        :type stack_policy: bool, optional
        :param creation_policy: configure creation_policy (termination protection, rollback on failure)
        :type creation_policy: bool, optional
        :param notification: set sns topic to publish
        :type notification: bool, optional
        :param update: determine if is creating stack or updating stack
        :type update: bool, optional
        :param search_from_root: search from root for stack_policy
        :type search_from_root: bool, optional
        :param dryrun: if true, changeset operation and don't add CreationOption or UpdateOption entry
        :type dryrun: bool, optional
        """
        attributes: List[str] = []

        if (not tags and not rollback and not permissions and not stack_policy
                and not creation_option and not notification):
            fzf = Pyfzf()
            fzf.append_fzf("Tags\n")
            fzf.append_fzf("Permissions\n")
            if not dryrun:
                fzf.append_fzf("StackPolicy\n")
            fzf.append_fzf("Notifications\n")
            fzf.append_fzf("RollbackConfiguration\n")
            if not dryrun and not update:
                fzf.append_fzf("CreationOption\n")
            attributes = list(
                fzf.execute_fzf(
                    empty_allow=True,
                    print_col=1,
                    multi_select=True,
                    header="select options to configure",
                ))

        for attribute in attributes:
            if attribute == "Tags":
                tags = True
            elif attribute == "Permissions":
                permissions = True
            elif attribute == "StackPolicy":
                stack_policy = True
            elif attribute == "Notifications":
                notification = True
            elif attribute == "RollbackConfiguration":
                rollback = True
            elif attribute == "CreationOption":
                creation_option = True

        if tags:
            self._set_tags(update)
        if permissions:
            self._set_permissions(update)
        if stack_policy:
            self._set_policy(update, search_from_root)
        if notification:
            self._set_notification(update)
        if rollback:
            self._set_rollback(update)
        if creation_option:
            self._set_creation()
Example #15
0
    def _set_creation(self) -> None:
        """Set creation option for stack."""
        print(80 * "-")
        fzf = Pyfzf()
        fzf.append_fzf("RollbackOnFailure\n")
        fzf.append_fzf("TimeoutInMinutes\n")
        fzf.append_fzf("EnableTerminationProtection\n")
        selected_options: List[str] = list(
            fzf.execute_fzf(
                empty_allow=True,
                print_col=1,
                multi_select=True,
                header="select options to configure",
            ))

        for option in selected_options:
            result: str = ""
            if option == "RollbackOnFailure":
                fzf.fzf_string = ""
                fzf.append_fzf("True\n")
                fzf.append_fzf("False\n")
                result = str(
                    fzf.execute_fzf(
                        empty_allow=True,
                        print_col=1,
                        header="roll back on failue? (Default: True)",
                    ))
                if result:
                    self._extra_args["OnFailure"] = (
                        "ROLLBACK" if result == "True" else "DO_NOTHING")
            elif option == "TimeoutInMinutes":
                message = "Specify number of minutes before stack timeout (Default: no timeout): "
                timeout = input(message)
                if timeout:
                    self._extra_args["TimeoutInMinutes"] = int(timeout)
            elif option == "EnableTerminationProtection":
                fzf.fzf_string = ""
                fzf.append_fzf("True\n")
                fzf.append_fzf("False\n")
                result = str(
                    fzf.execute_fzf(
                        empty_allow=True,
                        print_col=1,
                        header=
                        "enable termination protection? (Default: False)",
                    ))
                if result:
                    self._extra_args["EnableTerminationProtection"] = (
                        True if result == "True" else False)