diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c1322dc --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = false +insert_final_newline = false \ No newline at end of file diff --git a/.gitignore b/.gitignore index b6d1e88..7b7a896 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ .vscode/* # OSX Garbage -.DS_Store \ No newline at end of file +.DS_Store + +example_output/* diff --git a/README.md b/README.md index 05fb101..8191cbd 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,12 @@ See: https://docs.aws.amazon.com/lambda/latest/dg/lambda-python-how-to-create-de I created this module because using the standard technique of using an `archive_file` data source has a few shortcomings: -1. This doesn't allow processing a requirements file. +1. It doesn't allow processing a requirements file. 2. Each apply results in a diff in `source_code_hash` becuase the archive includes metadata and generated files (*.pyc). This module doesn't include file metadata or .pyc files in the archive so the hash is stable unless the source or dependencies change. +3. There is no mechanism to exclude files from the archive. ## Example @@ -20,6 +21,14 @@ module "python_lambda_archive" { src_dir = "${path.module}/python" output_path = "${path.module}/lambda.zip" install_dependencies = false + exclude_files = [ + '.gitignore', + # Any other file(s) you don't want included + # I use pipenv for local development, but prefer requirements.txt for deployments + 'Pipfile', + 'Pipfile.lock', + '.env' + ] } resource "aws_iam_role" "iam_for_lambda" { @@ -59,4 +68,4 @@ resource "aws_lambda_function" "test_lambda" { ``` ## External Dependencies -1. Python3.4+ \ No newline at end of file +1. Python3.4+ diff --git a/examples/lambda_python_archive.tf b/examples/lambda_python_archive.tf index 0065f2a..48a4be8 100644 --- a/examples/lambda_python_archive.tf +++ b/examples/lambda_python_archive.tf @@ -1,7 +1,21 @@ module "python_lambda_archive" { - source = "../" - src_dir = "${path.module}/python" - output_path = "${path.module}/artifacts/lambda.zip" + # Original version: + # source = "rojopolis/lambda-python-archive/aws" + # Updates past v0.1.6: + source = "github.com/farfromunique/terraform-aws-lambda-python-archive.git?ref=0.1.8" + + src_dir = "${path.module}/python" + output_path = "${path.module}/lambda.zip" + install_dependencies = false + # Requires v0.1.8 or more. + exclude_files = [ + '.gitignore', + # Any other file(s) you don't want included + # I use pipenv for local development, but prefer requirements.txt for deployments + 'Pipfile', + 'Pipfile.lock', + '.env' + ] } resource "aws_iam_role" "iam_for_lambda" { diff --git a/main.tf b/main.tf index 2a4756b..afd9ebb 100644 --- a/main.tf +++ b/main.tf @@ -4,5 +4,6 @@ data "external" "lambda_archive" { src_dir = var.src_dir output_path = var.output_path install_dependencies = var.install_dependencies + exclude_files = var.exclude_files } -} \ No newline at end of file +} diff --git a/scripts/build_lambda.py b/scripts/build_lambda.py index 10b6911..65a612e 100644 --- a/scripts/build_lambda.py +++ b/scripts/build_lambda.py @@ -7,13 +7,15 @@ import json import logging import os -import shutil import subprocess import sys import tempfile +from typing import List import zipfile -def build(src_dir, output_path, install_dependencies): +logger = logging.getLogger() + +def build(src_dir: str, output_path: str, install_dependencies: bool, exclude_files: List[str] = []) -> str: with tempfile.TemporaryDirectory() as build_dir: copy_tree(src_dir, build_dir) if os.path.exists(os.path.join(src_dir, 'requirements.txt')): @@ -29,11 +31,13 @@ def build(src_dir, output_path, install_dependencies): check=True, stdout=subprocess.DEVNULL, ) - make_archive(build_dir, output_path) + make_archive(build_dir, output_path, exclude_files) return output_path - -def make_archive(src_dir, output_path): +def make_archive(src_dir: str, output_path: str, exclude_files: List[str] = []) -> None: + ''' + Create an archive at the designated location. + ''' try: os.makedirs(os.path.dirname(output_path)) except OSError as e: @@ -44,8 +48,17 @@ def make_archive(src_dir, output_path): with zipfile.ZipFile(output_path, 'w') as archive: for root, dirs, files in os.walk(src_dir): + for file in files: - if file.endswith('.pyc'): + is_pyc = file.endswith('.pyc') + is_distinfo = '.dist-info' in root + is_ignored = file in exclude_files + + if is_pyc or is_distinfo or is_ignored: + logger.info('Skipping {r}/{f}'.format( + r=root, + f=file + )) break metadata = zipfile.ZipInfo( os.path.join(root, file).replace(src_dir, '').lstrip(os.sep) @@ -58,7 +71,7 @@ def make_archive(src_dir, output_path): data ) -def get_hash(output_path): +def get_hash(output_path: str) -> str: ''' Return base64 encoded sha256 hash of archive file ''' @@ -67,10 +80,19 @@ def get_hash(output_path): h.update(f.read()) return base64.standard_b64encode(h.digest()).decode('utf-8', 'strict') - if __name__ == '__main__': logging.basicConfig(level='DEBUG') - query = json.loads(sys.stdin.read()) + query = { + 'src_dir': 'examples/python/', + 'output_path': 'example_output/example.zip', + 'install_dependencies': True, + 'exclude_files': [] + } + # query = json.loads(sys.stdin.read()) logging.debug(query) - archive = build(query['src_dir'], query['output_path'], query['install_dependencies']) - print(json.dumps({'archive': archive, 'base64sha256':get_hash(archive)})) \ No newline at end of file + archive = build( + src_dir=query['src_dir'], + output_path=query['output_path'], + install_dependencies=query['install_dependencies'], + exclude_files=query['exclude_files']) + print(json.dumps({'archive': archive, 'base64sha256':get_hash(archive)})) diff --git a/variables.tf b/variables.tf index 2561775..1d95792 100644 --- a/variables.tf +++ b/variables.tf @@ -9,7 +9,13 @@ variable "output_path" { } variable "install_dependencies" { - description = "Whether to install pip dependecies" + description = "Whether to install pip dependecies." type = bool default = true -} \ No newline at end of file +} + +variable "exclude_files" { + description = "List of filenames (relative to Lambda's root) to exclude from archive." + type = list + default = [] +}