diff --git a/poetry.lock b/poetry.lock index ca0d3c9..27233b4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,17 @@ +[[package]] +category = "main" +description = "Classes Without Boilerplate" +name = "attrs" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "19.3.0" + +[package.extras] +azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] +dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] +docs = ["sphinx", "zope.interface"] +tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] + [[package]] category = "main" description = "The AWS SDK for Python" @@ -105,10 +119,14 @@ secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0 socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] [metadata] -content-hash = "fa81518e4d444b0a7f89674fc430d34b9385d203cfe95b27c9df000c3b67c945" +content-hash = "7e5c57e9b9fb763066437b51f2872a123a9bb6002bea5ebedd285d7294e17312" python-versions = "^3.7" [metadata.files] +attrs = [ + {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, + {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, +] boto3 = [ {file = "boto3-1.13.18-py2.py3-none-any.whl", hash = "sha256:1bdab4f87ff39d5aab59b0aae69965bf604fa5608984c673877f4c62c1f16240"}, {file = "boto3-1.13.18.tar.gz", hash = "sha256:2b4924ccc1603d562969b9f3c8c74ff4a1f3bdbafe857c990422c73d8e2e229e"}, diff --git a/pyproject.toml b/pyproject.toml index f24ab8f..143f9f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ python = "^3.7" click = "^7.1.1" boto3 = "^1.13.18" pyyaml = "^5.3.1" +attrs = "^19.3.0" [tool.poetry.dev-dependencies] diff --git a/tripod.py b/tripod.py new file mode 100755 index 0000000..55688ba --- /dev/null +++ b/tripod.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +import os +import glob +import subprocess +import zipfile +import boto3 +import click +import yaml +import attr +import typing + + +@attr.s(auto_attribs=True) +class Function: + name: str + runtime: str + role_arn: str + handler: str + files: typing.List[str] + layers: typing.List[str] + environment: typing.Dict[str, str] + + +def create_psycopg2_layer(): + if not os.path.exists("awslambda-psycopg2"): + subprocess.run( + ["git", "clone", "git@github.com:jkehler/awslambda-psycopg2.git"], + check=True, + ) + prefix = "awslambda-psycopg2/psycopg2-3.7/" + with zipfile.ZipFile("psycopg2.zip", "w") as lz: + for file in glob.glob(prefix + "*"): + arcname = file.replace(prefix, "python/psycopg2/") + lz.write(file, arcname) + + # upload + client = boto3.client("lambda") + client.publish_layer_version( + LayerName="py37-psycopg2", + Description="python 3.7 psycopg2 layer", + Content={"ZipFile": open("psycopg2.zip", "rb").read()}, + CompatibleRuntimes=["python3.7"], + ) + + +def do_publish(function): + zipfilename = "upload.zip" + with zipfile.ZipFile(zipfilename, "w") as zf: + for fn in function.files: + zf.write(fn) + + client = boto3.client("lambda") + + layer_arns = [] + for layer in function.layers: + versions = client.list_layer_versions(LayerName=layer) + # TODO: is zero the right index? + layer_arn = versions["LayerVersions"][0]["LayerVersionArn"] + layer_arns.append(layer_arn) + + try: + existing_config = client.get_function_configuration(FunctionName=function.name) + except client.exceptions.ResourceNotFoundException: + existing_config = False + client.create_function( + FunctionName=function.name, + Runtime=function.runtime, + Role=function.role_arn, + Handler=function.handler, + Code={"ZipFile": open(zipfilename, "rb").read()}, + Description=function.description, + Environment={"Variables": function.environment}, + Publish=True, + Layers=layer_arns, + ) + print(f"created function {function.name}") + + if existing_config: + client.update_function_code( + FunctionName=function.name, ZipFile=open(zipfilename, "rb").read() + ) + client.update_function_configuration( + FunctionName=function.name, + Role=function.role_arn, + Handler=function.handler, + Description=function.description, + Environment={"Variables": function.environment}, + ) + client.publish_version(FunctionName=function.name) + print(f"updated function {function.name}") + + +functions = {} + + +@click.group() +def cli(): + filename = "tripod.yaml" + with open(filename) as f: + data = yaml.safe_load(f) + for function in data["functions"]: + functions[function["name"]] = Function(**function) + + +@cli.command() +def list(): + click.echo("available functions: ") + for function in functions.values(): + click.echo(f" {function.name}") + + +@cli.command() +@click.argument("function") +def publish(function): + click.echo(f"publishing {function}") + do_publish(functions[function]) + + +if __name__ == "__main__": + cli()