예제 #1
0
    def __add_tag__(tag_name, list_of_objs):
        """Returns list_of_objs with tag_name added to each object"""

        # Create tag_to_add
        tag_to_add = {"tag_handle": tag_name, "value": None}

        err_msg = "Error adding tag '{0}'. '{1}' (printed above) is not a {2}. Instead a {3} was provided.\nProvided ImportDefinition in the customize.py file may be corrupt"

        # Check list_of_objs is a List
        if not isinstance(list_of_objs, list):
            LOG.error("Error adding tag.\n'list_of_objs': %s", list_of_objs)
            raise ExtException(
                err_msg.format(tag_name, "list_of_objs", "List",
                               type(list_of_objs)))

        # Loop each object in the List
        for obj in list_of_objs:

            # If its not a dict, error
            if not isinstance(obj, dict):
                LOG.error("Error adding tag.\n'list_of_objs': %s\n'obj': %s",
                          list_of_objs, obj)
                raise ExtException(
                    err_msg.format(tag_name, "obj", "Dictionary", type(obj)))

            # Try get current_tags
            current_tags = obj.get("tags")

            # If None, create new empty List
            if current_tags is None:
                current_tags = []

            # If current_tags is not a list, error
            if not isinstance(current_tags, list):
                LOG.error("Error adding tag.\n'current_tags': %s",
                          current_tags)
                raise ExtException(
                    err_msg.format(tag_name, "current_tags", "List",
                                   type(current_tags)))

            # Append our tag_to_add to current_tags
            current_tags.append(tag_to_add)

            # Set the obj's 'tags' value to current_tags
            obj["tags"] = current_tags

        # Return the updated list_of_objs
        return list_of_objs
예제 #2
0
    def __validate_directory__(permissions, path_to_dir):
        """Check the given path is absolute, exists and has the given permissions, else raises an Exception"""

        # Check the path is absolute
        if not os.path.isabs(path_to_dir):
            raise ExtException(
                "The path to the directory must be an absolute path: {0}".
                format(path_to_dir))

        # Check the directory exists
        if not os.path.isdir(path_to_dir):
            raise ExtException(
                "The path does not exist: {0}".format(path_to_dir))

        # Check we have the correct permissions
        Ext.__has_permissions__(permissions, path_to_dir)
예제 #3
0
    def __get_tar_file_path_to_extract__(tar_members, file_name):
        """Loop all the tar_members and return the path to the member that matcheds file_name.
        Raise an Exception if cannot find file_name in the tar package"""

        for member in tar_members:
            tar_file_name = os.path.split(member.name)

            if tar_file_name[1] == file_name:
                return member.name

        raise ExtException(
            "Invalid built distribution. Could not find {0}".format(file_name))
예제 #4
0
    def __validate_file_paths__(permissions=None, *args):
        """Check the given *args paths exist and has the given permissions, else raises an Exception"""

        # For each *args
        for path_to_file in args:
            # Check the file exists
            if not os.path.isfile(path_to_file):
                raise ExtException(
                    "Could not find file: {0}".format(path_to_file))

            if permissions:
                # Check we have the correct permissions
                Ext.__has_permissions__(permissions, path_to_file)
예제 #5
0
    def __has_permissions__(permissions, path):
        """Raises an exception if the user does not have the given permissions to path"""

        if not os.access(path, permissions):

            if permissions is os.R_OK:
                permissions = "READ"
            elif permissions is os.W_OK:
                permissions = "WRITE"

            raise ExtException(
                "User does not have {0} permissions for: {1}".format(
                    permissions, path))
예제 #6
0
    def __parse_setup_py__(path, attribute_names):
        """Parse the values of the given attribute_names and return a Dictionary attribute_name:attribute_value"""

        # Read the setup.py file into a List
        setup_py_lines = ExtCreate.__read_file__(path)

        # Raise an error if nothing found in the file
        if not setup_py_lines:
            raise ExtException(
                "No content found in provided setup.py file: {0}".format(path))

        setup_regex_pattern = r"setup\("
        setup_defined, index_of_setup, return_dict = False, None, dict()

        for i in range(len(setup_py_lines)):

            if re.match(pattern=setup_regex_pattern,
                        string=setup_py_lines[i]) is not None:
                setup_defined = True
                index_of_setup = i
                break

        # Raise an error if we can't find 'setup()' in the file
        if not setup_defined:
            raise ExtException(
                "Could not find 'setup()' defined in provided setup.py file: {0}"
                .format(path))

        # Get sublist containing lines from 'setup(' to EOF + trim whitespaces
        setup_py_lines = setup_py_lines[index_of_setup:]
        setup_py_lines = [file_line.strip() for file_line in setup_py_lines]

        # Foreach attribute_name, get its value and add to return_dict
        for attribute_name in attribute_names:
            return_dict[attribute_name] = ExtCreate.__parse_setup_attribute__(
                path, setup_py_lines, attribute_name)

        return return_dict
예제 #7
0
    def convert_to_extension(cls,
                             path_built_distribution,
                             custom_display_name=None):
        """ Function that converts an (old) Integration into a Resilient Extension.
        Validates then converts the given built_distribution (either .tar.gz or .zip).
        Returns the path to the new Extension.zip
        - path_built_distribution [String]:
            - If a .tar.gz: must include a setup.py, customize.py and config.py file.
            - If a .zip: must include a valid .tar.gz.
        - custom_display_name [String]: will give the Extension that display name. Default: name from setup.py file
        - The Extension.zip will be produced in the same directory as path_built_distribution"""

        LOG.info("Converting extension from: %s", path_built_distribution)

        path_tmp_built_distribution, path_extracted_tar = None, None

        # Dict of the required files we need to try extract in order to create an Extension
        extracted_required_files = {
            "setup.py": None,
            "customize.py": None,
            "config.py": None
        }

        # Raise Exception if the user tries to pass a Directory
        if os.path.isdir(path_built_distribution):
            raise ExtException(
                "You must specify a Built Distribution. Not a Directory\nDirectory Specified: {0}"
                .format(path_built_distribution))

        # Raise Exception if not a .tar.gz or .zip file
        if not os.path.isfile(path_built_distribution) or (
                not tarfile.is_tarfile(path_built_distribution)
                and not zipfile.is_zipfile(path_built_distribution)):
            raise ExtException(
                "File corrupt. Supported Built Distributions are .tar.gz and .zip\nInvalid Built Distribution provided: {0}"
                .format(path_built_distribution))

        # Validate we can read the built distribution
        cls.__validate_file_paths__(os.R_OK, path_built_distribution)

        # Create a tmp directory
        path_tmp_dir = tempfile.mkdtemp(prefix="resilient-circuits-tmp-")

        try:
            # Copy built distribution to tmp directory
            shutil.copy(path_built_distribution, path_tmp_dir)

            # Get the path of the built distribution in the tmp directory
            path_tmp_built_distribution = os.path.join(
                path_tmp_dir,
                os.path.split(path_built_distribution)[1])

            # Handle if it is a .tar.gz file
            if tarfile.is_tarfile(path_tmp_built_distribution):

                LOG.info(
                    "A .tar.gz file was provided. Will now attempt to convert it to a Resilient Extension."
                )

                # Extract the required files to the tmp dir and return a dict of their paths
                extracted_required_files = cls.__get_required_files_from_tar_file__(
                    path_tar_file=path_tmp_built_distribution,
                    dict_required_files=extracted_required_files,
                    output_dir=path_tmp_dir)

                path_extracted_tar = path_tmp_built_distribution

            # Handle if is a .zip file
            elif zipfile.is_zipfile(path_tmp_built_distribution):

                LOG.info(
                    "A .zip file was provided. Will now attempt to convert it to a Resilient Extension."
                )

                with zipfile.ZipFile(file=path_tmp_built_distribution,
                                     mode="r") as zip_file:

                    # Get a List of all the members of the zip file (including files in directories)
                    zip_file_members = zip_file.infolist()

                    LOG.info("\nValidating Built Distribution: %s",
                             path_built_distribution)

                    # Loop the members
                    for zip_member in zip_file_members:

                        LOG.info("\t- %s", zip_member.filename)

                        # Extract the member
                        path_extracted_member = zip_file.extract(
                            member=zip_member, path=path_tmp_dir)

                        # Handle if the member is a directory
                        if os.path.isdir(path_extracted_member):

                            LOG.debug(
                                "\t\t- Is a directory.\n\t\t- Skipping...")

                            # delete the extracted member
                            shutil.rmtree(path_extracted_member)
                            continue

                        # Handle if it is a .tar.gz file
                        elif tarfile.is_tarfile(path_extracted_member):

                            LOG.info("\t\t- Is a .tar.gz file!")

                            # Set the path to the extracted .tar.gz file
                            path_extracted_tar = path_extracted_member

                            # Try to extract the required files from the .tar.gz
                            try:
                                extracted_required_files = cls.__get_required_files_from_tar_file__(
                                    path_tar_file=path_extracted_member,
                                    dict_required_files=
                                    extracted_required_files,
                                    output_dir=path_tmp_dir)

                                LOG.info(
                                    "\t\t- Found files: %s\n\t\t- Its path: %s\n\t\t- Is a valid Built Distribution!",
                                    ", ".join(extracted_required_files.keys()),
                                    path_extracted_tar)
                                break

                            except ExtException as err:
                                # If "invalid" is in the error message,
                                # then we did not find one of the required files in the .tar.gz
                                # so we warn the user, delete the extracted member and continue the loop
                                if "invalid" in err.message.lower():
                                    LOG.warning(
                                        "\t\t- Failed to extract required files: %s\n\t\t- Invalid format.\n%s",
                                        ", ".join(
                                            extracted_required_files.keys()),
                                        err.message)
                                    os.remove(path_extracted_member)
                                else:
                                    raise ExtException(err)

                        # Handle if it is a regular file
                        elif os.path.isfile(path_extracted_member):

                            # Get the file name
                            file_name = os.path.basename(path_extracted_member)

                            # If the file is a required one, add its path to the dict
                            if file_name in extracted_required_files:
                                LOG.info("\t\t- Found %s file", file_name)
                                extracted_required_files[
                                    file_name] = path_extracted_member

                                # Set the path to extracted tar to this zip file
                                path_extracted_tar = zip_file.filename

                            # Else its some other file, so skip
                            else:
                                LOG.debug(
                                    "\t\t- It is not a .tar.gz file\n\t\t- Skipping..."
                                )
                                os.remove(path_extracted_member)

                        # if extracted_required_files contains values for all required files, then break
                        if all(extracted_required_files.values()):
                            LOG.info(
                                "\t\t- This is a valid Built Distribution!")
                            break

                        else:
                            LOG.debug(
                                "\t\t- Is not a valid .tar.gz built distribution\n\t\t- Skipping..."
                            )

            # If we could not get all the required files to create an Extension, raise an error
            if not all(extracted_required_files.values()):
                raise ExtException(
                    "Could not extract required files from given Built Distribution\nRequired Files: {0}\nDistribution: {1}"
                    .format(", ".join(extracted_required_files.keys()),
                            path_built_distribution))

            # Create the extension
            path_tmp_the_extension_zip = cls.create_extension(
                path_setup_py_file=extracted_required_files.get("setup.py"),
                path_customize_py_file=extracted_required_files.get(
                    "customize.py"),
                path_config_py_file=extracted_required_files.get("config.py"),
                output_dir=path_tmp_dir,
                path_built_distribution=path_extracted_tar,
                custom_display_name=custom_display_name)

            # Copy the extension.zip to the same directory as the original built distribution
            shutil.copy(path_tmp_the_extension_zip,
                        os.path.dirname(path_built_distribution))

            # Get the path to the final extension.zip
            path_the_extension_zip = os.path.join(
                os.path.dirname(path_built_distribution),
                os.path.basename(path_tmp_the_extension_zip))

            LOG.info("Extension created at: %s", path_the_extension_zip)

            return path_the_extension_zip

        except Exception as err:
            raise ExtException(err)

        finally:
            # Remove the tmp directory
            shutil.rmtree(path_tmp_dir)
예제 #8
0
    def __get_import_definition_from_customize_py__(path_customize_py_file):
        """Return the base64 encoded ImportDefinition in a customize.py file as a Dictionary"""

        # Insert the customize.py parent dir to the start of our Python PATH at runtime so we can import the customize module from within it
        path_to_util_dir = os.path.dirname(path_customize_py_file)
        sys.path.insert(0, path_to_util_dir)

        # Import the customize module
        customize_py = importlib.import_module("customize")

        # Reload the module so we get the latest one
        # If we do not reload, can get stale results if
        # this method is called more then once
        reload(customize_py)

        # Call customization_data() to get all ImportDefinitions that are "yielded"
        customize_py_import_definitions_generator = customize_py.customization_data(
        )
        customize_py_import_definitions = []

        # customization_data() returns a Generator object with all yield statements, so we loop them
        for definition in customize_py_import_definitions_generator:
            if isinstance(definition, ImportDefinition):
                customize_py_import_definitions.append(
                    json.loads(base64.b64decode(definition.value)))
            else:
                LOG.warning(
                    "WARNING: Unsupported data found in customize.py file. Expected an ImportDefinition. Got: '%s'",
                    definition)

        # If no ImportDefinition found
        if not customize_py_import_definitions:
            raise ExtException(
                "No ImportDefinition found in the customize.py file")

        # If more than 1 found
        elif len(customize_py_import_definitions) > 1:
            raise ExtException(
                "Multiple ImportDefinitions found in the customize.py file. There must only be 1 ImportDefinition defined"
            )

        # Get the import defintion as dict
        customize_py_import_definition = customize_py_import_definitions[0]

        # Get reference to incident_types if there are any
        incident_types = customize_py_import_definition.get(
            "incident_types", [])

        if incident_types:

            incident_type_to_remove = None

            # Loop through and remove this custom one (that is originally added using codegen)
            for incident_type in incident_types:
                if incident_type.get("uuid") == DEFAULT_INCIDENT_TYPE_UUID:
                    incident_type_to_remove = incident_type
                    break

            if incident_type_to_remove:
                incident_types.remove(incident_type_to_remove)

        # Remove the path from PYTHONPATH
        sys.path.remove(path_to_util_dir)

        return customize_py_import_definition
예제 #9
0
    def create_extension(cls,
                         path_setup_py_file,
                         path_customize_py_file,
                         path_config_py_file,
                         output_dir,
                         path_built_distribution=None,
                         path_extension_logo=None,
                         path_company_logo=None,
                         custom_display_name=None,
                         keep_build_dir=False):
        """ Function that creates The Extension.zip file from the given setup.py, customize.py and config.py files
        and copies it to the output_dir. Returns the path to the Extension.zip
        - path_setup_py_file [String]: abs path to the setup.py file
        - path_customize_py_file [String]: abs path to the customize.py file
        - path_config_py_file [String]: abs path to the config.py file
        - output_dir [String]: abs path to the directory the Extension.zip should be produced
        - path_built_distribution [String]: abs path to a tar.gz Built Distribution
            - if provided uses that .tar.gz
            - else looks for it in the output_dir. E.g: output_dir/package_name.tar.gz
        - path_extension_logo [String]: abs path to the extension_logo.png. Has to be 200x72 and a .png file
            - if not provided uses default icon
        - path_company_logo [String]: abs path to the extension_logo.png. Has to be 100x100 and a .png file
            - if not provided uses default icon
        - custom_display_name [String]: will give the Extension that display name. Default: name from setup.py file
        - keep_build_dir [Boolean]: if True, build/ will not be remove. Default: False
        """

        LOG.info("Creating Extension")

        # Ensure the output_dir exists, we have WRITE access and ensure we can READ setup.py and customize.py
        cls.__validate_directory__(os.W_OK, output_dir)
        cls.__validate_file_paths__(os.R_OK, path_setup_py_file,
                                    path_customize_py_file)

        # Parse the setup.py file
        setup_py_attributes = cls.__parse_setup_py__(
            path_setup_py_file, cls.supported_setup_py_attribute_names)

        # Validate setup.py attributes

        # Validate the name attribute. Raise exception if invalid
        if not cls.__is_valid_package_name__(setup_py_attributes.get("name")):
            raise ExtException(
                "'{0}' is not a valid Extension name. The name attribute must be defined and can only include 'a-z and _'.\nUpdate this value in the setup.py file located at: {1}"
                .format(setup_py_attributes.get("name"), path_setup_py_file))

        # Validate the version attribute. Raise exception if invalid
        if not cls.__is_valid_version_syntax__(
                setup_py_attributes.get("version")):
            raise ExtException(
                "'{0}' is not a valid Extension version syntax. The version attribute must be defined. Example: version=\"1.0.0\".\nUpdate this value in the setup.py file located at: {1}"
                .format(setup_py_attributes.get("version"),
                        path_setup_py_file))

        # Validate the url supplied in the setup.py file, set to an empty string if not valid
        if not cls.__is_valid_url__(setup_py_attributes.get("url")):
            LOG.warning("WARNING: '%s' is not a valid url. Ignoring.",
                        setup_py_attributes.get("url"))
            setup_py_attributes["url"] = ""

        # Get ImportDefinition from customize.py
        customize_py_import_definition = cls.__get_import_definition_from_customize_py__(
            path_customize_py_file)

        # Get the tag name
        tag_name = setup_py_attributes.get("name")

        # Add the tag to the import defintion
        customize_py_import_definition = cls.__add_tag_to_import_definition__(
            tag_name, cls.supported_res_obj_names,
            customize_py_import_definition)

        # Parse the app.configs from the config.py file
        app_configs = cls.__get_configs_from_config_py__(path_config_py_file)

        # Generate the name for the extension
        extension_name = "{0}-{1}".format(setup_py_attributes.get("name"),
                                          setup_py_attributes.get("version"))

        # Generate paths to the directories and files we will use in the build directory
        path_build = os.path.join(output_dir, BASE_NAME_BUILD)
        path_extension_json = os.path.join(path_build,
                                           BASE_NAME_EXTENSION_JSON)
        path_export_res = os.path.join(path_build, BASE_NAME_EXPORT_RES)
        path_executables = os.path.join(path_build, BASE_NAME_EXECUTABLES)
        path_executable_zip = os.path.join(
            path_executables, "{0}{1}".format(PREFIX_EXECUTABLE_ZIP,
                                              extension_name))
        path_executable_json = os.path.join(path_executable_zip,
                                            BASE_NAME_EXECUTABLE_JSON)
        path_executable_dockerfile = os.path.join(
            path_executable_zip, BASE_NAME_EXECUTABLE_DOCKERFILE)

        try:
            # If there is an old build directory, remove it first
            if os.path.exists(path_build):
                shutil.rmtree(path_build)

            # Create the directories for the path "/build/executables/exe-<package-name>/"
            os.makedirs(path_executable_zip)

            # If no path_built_distribution is given, use the default: "<output_dir>/<package-name>.tar.gz"
            if not path_built_distribution:
                path_built_distribution = os.path.join(
                    output_dir, "{0}.tar.gz".format(extension_name))

            # Validate the built distribution exists and we have READ access
            cls.__validate_file_paths__(os.R_OK, path_built_distribution)

            # Copy the built distribution to the executable_zip dir and  enforce rename to .tar.gz
            shutil.copy(
                path_built_distribution,
                os.path.join(path_executable_zip,
                             "{0}.tar.gz".format(extension_name)))

            # Generate the contents for the executable.json file
            the_executable_json_file_contents = {"name": extension_name}

            # Write the executable.json file
            cls.__write_file__(
                path_executable_json,
                json.dumps(the_executable_json_file_contents, sort_keys=True))

            # NOTE: Dockerfile creation commented out for this release
            '''
            # Load Dockerfile template
            docker_file_template = cls.jinja_env.get_template(JINJA_TEMPLATE_DOCKERFILE)

            # Render Dockerfile template with required variables
            the_dockerfile_contents = docker_file_template.render({
                "extension_name": extension_name,
                "installed_package_name": setup_py_attributes.get("name").replace("_", "-"),
                "app_configs": app_configs[1]
            })

            # Write the Dockerfile
            cls.__write_file__(path_executable_dockerfile, the_dockerfile_contents)
            '''

            # zip the executable_zip dir
            shutil.make_archive(base_name=path_executable_zip,
                                format="zip",
                                root_dir=path_executable_zip)

            # Remove the executable_zip dir
            shutil.rmtree(path_executable_zip)

            # Get the extension_logo (icon) and company_logo (author.icon) as base64 encoded strings
            extension_logo = cls.__get_icon__(
                icon_name=os.path.basename(PATH_DEFAULT_ICON_EXTENSION_LOGO),
                path_to_icon=path_extension_logo,
                width_accepted=200,
                height_accepted=72,
                default_path_to_icon=PATH_DEFAULT_ICON_EXTENSION_LOGO)

            company_logo = cls.__get_icon__(
                icon_name=os.path.basename(PATH_DEFAULT_ICON_COMPANY_LOGO),
                path_to_icon=path_company_logo,
                width_accepted=100,
                height_accepted=100,
                default_path_to_icon=PATH_DEFAULT_ICON_COMPANY_LOGO)

            # Generate the contents for the extension.json file
            the_extension_json_file_contents = {
                "author": {
                    "name": setup_py_attributes.get("author"),
                    "website": setup_py_attributes.get("url"),
                    "icon": {
                        "data": company_logo,
                        "media_type": "image/png"
                    }
                },
                "description": {
                    "content": setup_py_attributes.get("description"),
                    "format": "text"
                },
                "display_name":
                custom_display_name if custom_display_name is not None else
                setup_py_attributes.get("name"),
                "icon": {
                    "data": extension_logo,
                    "media_type": "image/png"
                },
                "long_description": {
                    "content":
                    "<div>{0}</div>".format(
                        setup_py_attributes.get("long_description")),
                    "format":
                    "html"
                },
                "minimum_resilient_version": {
                    "major":
                    customize_py_import_definition.get("server_version").get(
                        "major"),
                    "minor":
                    customize_py_import_definition.get("server_version").get(
                        "minor"),
                    "build_number":
                    customize_py_import_definition.get("server_version").get(
                        "build_number"),
                    "version":
                    customize_py_import_definition.get("server_version").get(
                        "version")
                },
                "name":
                setup_py_attributes.get("name"),
                "tag": {
                    "prefix": tag_name,
                    "name": tag_name,
                    "display_name": tag_name,
                    "uuid": cls.__generate_uuid_from_string__(tag_name)
                },
                "uuid":
                cls.__generate_uuid_from_string__("{0}-{1}".format(
                    setup_py_attributes.get("name"),
                    setup_py_attributes.get("version"))),
                "version":
                setup_py_attributes.get("version"),
                # TODO: discuss with Sasquatch. Can add the app_config_str here, but will not install
                # TODO: get 'Unrecognized field "app_config_str"' error
                # "app_config_str": app_configs[0]
            }

            # Write the executable.json file
            cls.__write_file__(
                path_extension_json,
                json.dumps(the_extension_json_file_contents, sort_keys=True))

            # Write the customize ImportDefinition to the export.res file
            cls.__write_file__(
                path_export_res,
                json.dumps(customize_py_import_definition, sort_keys=True))

            # Copy the built distribution to the build dir, enforce rename to .tar.gz
            shutil.copy(
                path_built_distribution,
                os.path.join(path_build, "{0}.tar.gz".format(extension_name)))

            # create The Extension Zip by zipping the build directory
            extension_zip_base_path = os.path.join(
                output_dir, "{0}{1}".format(PREFIX_EXTENSION_ZIP,
                                            extension_name))
            extension_zip_name = shutil.make_archive(
                base_name=extension_zip_base_path,
                format="zip",
                root_dir=path_build)
            path_the_extension_zip = os.path.join(extension_zip_base_path,
                                                  extension_zip_name)

        except ExtException as err:
            raise err

        except Exception as err:
            raise ExtException(err)

        finally:
            # Remove the executable_zip dir. Keep it if user passes --keep-build-dir
            if not keep_build_dir:
                shutil.rmtree(path_build)

        LOG.info("Extension %s created",
                 "{0}{1}".format(PREFIX_EXTENSION_ZIP, extension_name))

        # Return the path to the extension zip
        return path_the_extension_zip
예제 #10
0
    def __get_icon__(icon_name, path_to_icon, width_accepted, height_accepted,
                     default_path_to_icon):
        """Returns the icon at path_to_icon as a base64 encoded string if it is a valid .png file with the resolution
        width_accepted x height_accepted. If path_to_icon does not exist, default_path_to_icon is returned as a base64
        encoded string"""

        path_icon_to_use = path_to_icon

        # Use default_path_to_icon if path_to_icon does not exist
        if not path_icon_to_use or not os.path.isfile(path_icon_to_use):
            LOG.warning(
                "WARNING: Default Extension Icon will be used\nProvided custom icon path for %s is invalid: %s\nNOTE: %s should be placed in the /icons directory",
                icon_name, path_icon_to_use, icon_name)
            path_icon_to_use = default_path_to_icon

        else:
            LOG.info("INFO: Using custom %s icon: %s", icon_name,
                     path_icon_to_use)

        # Validate path_icon_to_use and ensure we have READ permissions
        try:
            ExtCreate.__validate_file_paths__(os.R_OK, path_icon_to_use)
        except ExtException as err:
            raise OSError(
                "Could not find valid icon file. Looked at two locations:\n{0}\n{1}\n{2}"
                .format(path_to_icon, default_path_to_icon, err.message))

        # Get the extension of the file. os.path.splitext returns a Tuple with the file extension at position 1 and can be an empty string
        split_path = os.path.splitext(path_icon_to_use)
        file_extension = split_path[1]

        if not file_extension:
            raise ExtException(
                "Provided icon file does not have an extension. Icon file must be .png\nIcon File: {0}"
                .format(path_icon_to_use))

        elif file_extension != ".png":
            raise ExtException(
                "{0} is not a supported icon file type. Icon file must be .png\nIcon File: {1}"
                .format(file_extension, path_icon_to_use))

        # Open the icon_file in Bytes mode to validate its resolution
        with open(path_icon_to_use, mode="rb") as icon_file:
            # According to: https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_format
            # First need to seek 16 bytes:
            #   8 bytes: png signature
            #   4 bytes: IDHR Chunk Length
            #   4 bytes: IDHR Chunk type
            icon_file.seek(16)

            try:
                # Bytes 17-20 = image width. Use struct to unpack big-endian encoded unsigned int
                icon_width = struct.unpack(">I", icon_file.read(4))[0]

                # Bytes 21-24 = image height. Use struct to unpack big-endian encoded unsigned int
                icon_height = struct.unpack(">I", icon_file.read(4))[0]
            except Exception as err:
                raise ExtException(
                    "Failed to read icon's resolution. Icon file corrupt. Icon file must be .png\nIcon File: {0}"
                    .format(path_icon_to_use))

        # Raise exception if resolution is not accepted
        if icon_width != width_accepted or icon_height != height_accepted:
            raise ExtException(
                "Icon resolution is {0}x{1}. Resolution must be {2}x{3}\nIcon File:{4}"
                .format(icon_width, icon_height, width_accepted,
                        height_accepted, path_icon_to_use))

        # If we get here all validations have passed. Open the file in Bytes mode and encode it as base64 and decode to a utf-8 string
        with open(path_icon_to_use, "rb") as icon_file:
            encoded_icon_string = base64.b64encode(
                icon_file.read()).decode("utf-8")

        return encoded_icon_string
예제 #11
0
    def __get_configs_from_config_py__(path_config_py_file):
        """Returns a tuple (config_str, config_list). If no configs found, return ("", []).
        Raises Exception if it fails to parse configs
        - config_str: is the full string found in the config.py file
        - config_list: is a list of dict objects that contain each un-commented config
            - Each dict object has the attributes: name, placeholder, env_name, section_name
        """

        config_str, config_list = "", []

        # Insert the customize.py parent dir to the start of our Python PATH at runtime so we can import the customize module from within it
        path_to_util_dir = os.path.dirname(path_config_py_file)
        sys.path.insert(0, path_to_util_dir)

        try:
            # Import the config module
            config_py = importlib.import_module("config")

            # Reload the module so we get the latest one
            # If we do not reload, can get stale results if
            # this method is called more then once
            reload(config_py)

            # Call config_section_data() to get the string containing the configs
            config_str = config_py.config_section_data()

            # Instansiate a new configparser
            config_parser = configparser.ConfigParser()

            # Read and parse the configs from the config_str
            if sys.version_info < (3, 2):
                # config_parser.readfp() was deprecated and replaced with read_file in PY3.2
                config_parser.readfp(io.StringIO(config_str))

            else:
                config_parser.read_file(io.StringIO(config_str))

            # Get the configs from each section
            for section_name in config_parser.sections():

                parsed_configs = config_parser.items(section_name)

                for config in parsed_configs:
                    config_list.append({
                        "name":
                        config[0],
                        "placeholder":
                        config[1],
                        "env_name":
                        "{0}_{1}".format(section_name.upper(),
                                         config[0].upper()),
                        "section_name":
                        section_name
                    })

        except Exception as err:
            raise ExtException(
                "Failed to parse configs from config.py file\nThe config.py file may be corrupt. Visit the App Exchange to contact the developer\nReason: {0}"
                .format(err))

        finally:
            # Remove the path from PYTHONPATH
            sys.path.remove(path_to_util_dir)

        return (config_str, config_list)