Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@
.vscode/*

# OSX Garbage
.DS_Store
.DS_Store

example_output/*
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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" {
Expand Down Expand Up @@ -59,4 +68,4 @@ resource "aws_lambda_function" "test_lambda" {
```

## External Dependencies
1. Python3.4+
1. Python3.4+
20 changes: 17 additions & 3 deletions examples/lambda_python_archive.tf
Original file line number Diff line number Diff line change
@@ -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" {
Expand Down
3 changes: 2 additions & 1 deletion main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
44 changes: 33 additions & 11 deletions scripts/build_lambda.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')):
Expand All @@ -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:
Expand All @@ -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)
Expand All @@ -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
'''
Expand All @@ -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)}))
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)}))
10 changes: 8 additions & 2 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

variable "exclude_files" {
description = "List of filenames (relative to Lambda's root) to exclude from archive."
type = list
default = []
}