예제 #1
0
    def test_hash_string(self):
        """Asserts hash_string returns the correct sha256 hexdigest"""

        test_string = "test"
        expected_hash = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"

        self.assertEqual(expected_hash, target_module.hash_string(test_string))
예제 #2
0
def build_lambda_package_with_dependencies(
        code_directories: typing.List[str],
        requirement_files: typing.List[str],
        exclude_patterns: typing.List[str] = None) -> str:
    """
    This function bundles the code of one or more code_directories stripped
    from all files/directories that match the exclude_patterns together with
    the dependencies in requirement_files and returns a zip archive for deployment
    in AWS lambda.

    :param code_directories: List of paths to the directories that hold the code.
    :type code_directories: typing.List[str]
    :param requirement_files: List of paths to requirement files with the dependencies.
    :type requirement_files: typing.List[str]
    :param exclude_patterns: List of patterns to exclude from code_directories, defaults to None
    :type exclude_patterns: typing.List[str], optional
    :return: Path to the zipped artifacts.
    :rtype: str
    """

    collected_dependencies = collect_and_merge_requirements(
        *requirement_files
    )

    requirements_zip = create_or_return_zipped_dependencies(
        requirements_information=collected_dependencies,
        output_directory_path=util.get_build_dir(),
    )

    # Hash the requirement files and code directories in order to get
    # a unique hash for this combination
    target_zip_name = util.hash_string(
        "".join(code_directories) + "".join(requirement_files)) + ".zip"
    zip_path = os.path.join(util.get_build_dir(), target_zip_name)

    shutil.copyfile(
        src=requirements_zip,
        dst=zip_path
    )

    util.extend_zip(
        path_to_zip=zip_path,
        code_directories=code_directories,
        exclude_patterns=exclude_patterns
    )

    return zip_path
예제 #3
0
def build_lambda_package_without_dependencies(
        code_directories: typing.List[str],
        exclude_patterns: typing.List[str] = None) -> str:
    """
    This function builds a deployment package for lambda without dependencies.
    It bundles the code from the code_directories while excluding all files/
    directories from the exclude_patterns list.

    :param code_directories: List of paths to directories to include in the zip.
    :type code_directories: typing.List[str]
    :param exclude_patterns: List of patterns that should be excluded from the zip, defaults to None
    :type exclude_patterns: typing.List[str], optional
    :return: Path to the zipped artifact.
    :rtype: str
    """

    # Build the exclude patterns
    exclude_patterns = exclude_patterns or []
    exclude_patterns = exclude_patterns + util.DEFAULT_EXCLUDE_LIST
    ignore_during_copy = shutil.ignore_patterns(*exclude_patterns)

    # Create a working directory, copy all source directories there with the exclude list
    with tempfile.TemporaryDirectory() as working_directory:

        for directory in code_directories:

            # Get the name of the directory -> "path/to/directory" would return "directory"
            source_directory_name = os.path.basename(directory)

            # This is the directory that will ultimately be zipped
            target_directory = os.path.join(working_directory, source_directory_name)

            # Copy the source directory to the working directory
            shutil.copytree(directory, target_directory, ignore=ignore_during_copy)

        target_zip_name = util.hash_string("".join(code_directories))
        zip_path = os.path.join(util.get_build_dir(), target_zip_name)

        # Zip the directory after removing a potential .zip suffix
        shutil.make_archive(zip_path, "zip", working_directory)
        return zip_path + ".zip"
예제 #4
0
def create_or_return_zipped_dependencies(requirements_information: str,
                                         output_directory_path: str,
                                         prefix_in_zip: str = None) -> str:
    """
    This function creates or returns a zip archive that holds the python
    dependencies passed to this function via the requirements_information
    argument - if it has been built previously that path is returned. The
    output will be stored in output_directory_path with a unique name and
    returned. If prefix_in_zip is set, requirements will be installed in a
    subdirectory of the zip (useful for Lambda layers which require a
    python prefix).


    :param requirements_information: The content of the requirements.txt
    :type requirements_information: str
    :param output_directory_path: The directory to build the requirements and store the result in.
    :type output_directory_path: str
    :param prefix_in_zip: Optional prefix in the zip file, defaults to None
    :type prefix_in_zip: str, optional
    :return: Path to the finished zip archive.
    :rtype: str
    """

    prefix_seed = prefix_in_zip or ""
    artifact_name = util.hash_string(requirements_information + prefix_seed)

    artifact_path = os.path.join(output_directory_path, f"{artifact_name}.zip")
    if os.path.exists(artifact_path):
        LOGGER.debug("Using cached dependencies from %s", artifact_path)
        return artifact_path

    return create_zipped_dependencies(
        requirements_information=requirements_information,
        output_directory_path=output_directory_path,
        prefix_in_zip=prefix_in_zip
    )
예제 #5
0
def create_zipped_dependencies(requirements_information: str,
                               output_directory_path: str,
                               prefix_in_zip: str = None) -> str:
    """
    This function creates a zip archive that holds the python dependencies
    passed to this function via the requirements_information argument. The
    output will be stored in output_directory_path with a unique name and
    returned. If prefix_in_zip is set, requirements will be installed in a
    subdirectory of the zip (useful for Lambda layers which require a
    python prefix).


    :param requirements_information: The content of the requirements.txt
    :type requirements_information: str
    :param output_directory_path: The directory to build the requirements and store the result in.
    :type output_directory_path: str
    :param prefix_in_zip: Optional prefix in the zip file, defaults to None
    :type prefix_in_zip: str, optional
    :return: Path to the finished zip archive.
    :rtype: str
    """

    # Add the prefix to the hash so we distinguish between layers and regular packages
    prefix_seed = prefix_in_zip or ""
    directory_name = util.hash_string(requirements_information + prefix_seed)

    build_directory = os.path.join(output_directory_path, directory_name)

    # Check if directory exists
    if os.path.exists(build_directory):
        LOGGER.warning("Build-directory '%s' already exists, probably from a failed" \
                       "build - deleting it!", build_directory)
        shutil.rmtree(build_directory)

    # If the prefix is set, we create the install directory with it
    install_directory = build_directory
    if prefix_in_zip is not None:
        install_directory = os.path.join(install_directory, prefix_in_zip)

    # Now we have a clean space and can create our build directory
    pathlib.Path(install_directory).mkdir(parents=True, exist_ok=True)

    # Create the requirements.txt in the install_directory
    requirements_path = os.path.join(install_directory, "requirements.txt")
    with open(requirements_path, "w") as handle:
        handle.write(requirements_information)

    # Install the dependencies to the target directory
    install_dependencies(
        path_to_requirements=requirements_path,
        path_to_target_directory=install_directory
    )

    output_file_name = build_directory if build_directory[-1] != "/" else build_directory[:-1]
    # Zip the temporary directory
    shutil.make_archive(output_file_name, "zip", build_directory)

    # Delete the build directory
    shutil.rmtree(build_directory)

    return f"{output_file_name}.zip"