diff --git a/lambda-function.Dockerfile b/lambda-function.Dockerfile new file mode 100644 index 0000000..4a57eb8 --- /dev/null +++ b/lambda-function.Dockerfile @@ -0,0 +1,92 @@ +ARG RUNTIME_VERSION=3.10.7 +ARG DISTRO_VERSION=slim-buster +ARG FUNCTION_DIR=/function + +# +# Stage: build +# +FROM python:${RUNTIME_VERSION}-${DISTRO_VERSION} as build-image + +ARG POETRY_VERSION=1.4.2 +ARG APP_NAME=pritunl_slack_app +ARG APP_PATH=/opt/${APP_NAME} +ARG FUNCTION_DIR + +ENV \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PYTHONFAULTHANDLER=1 + +ENV \ + POETRY_VERSION=${POETRY_VERSION} \ + POETRY_HOME="/opt/poetry" \ + POETRY_VIRTUALENVS_IN_PROJECT=true \ + POETRY_NO_INTERACTION=1 + +ENV \ + PIP_NO_CACHE_DIR=off \ + PIP_DISABLE_PIP_VERSION_CHECK=on \ + PIP_DEFAULT_TIMEOUT=100 + +ARG \ + AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION:-"us-east-1"} \ + AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-""} \ + AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-""} + +ENV \ + AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION} \ + AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \ + AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} + +RUN apt-get update && \ + apt-get install -y \ + curl \ + unzip + +RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \ + unzip awscliv2.zip && \ + ./aws/install + +RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | python +ENV PATH="$POETRY_HOME/bin:$PATH" + +WORKDIR ${APP_PATH} +COPY ./poetry.lock ./pyproject.toml ./README.md ./ +COPY ./${APP_NAME} ./${APP_NAME} + +RUN poetry build --format wheel +RUN poetry export --extras aws \ + --without-hashes \ + --format requirements.txt \ + --output constraints.txt + +RUN mkdir -p ${FUNCTION_DIR} + +RUN curl \ + $(aws lambda get-layer-version-by-arn --arn arn:aws:lambda:us-east-1:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:4 --query 'Content.Location' --output text) \ + --output layer.zip && \ + unzip layer.zip -d /opt && \ + rm layer.zip + +RUN python -m pip install \ + awslambdaric \ + --target ${FUNCTION_DIR} + +RUN python -m pip install --find-links=dist/ pritunl_slack_app[aws] \ + --constraint constraints.txt \ + --target ${FUNCTION_DIR} + +# +# Stage: production +# +FROM python:${RUNTIME_VERSION}-${DISTRO_VERSION} + +ARG FUNCTION_DIR + +WORKDIR ${FUNCTION_DIR} + +COPY --from=build-image /opt/extensions /opt/extensions +COPY --from=build-image ${FUNCTION_DIR} ${FUNCTION_DIR} + +ENTRYPOINT [ "/usr/local/bin/python", "-m", "awslambdaric" ] +CMD [ "pritunl_slack_app.function.pritunl_slack_app.function_handler.handler" ] diff --git a/poetry.lock b/poetry.lock index 6546d15..42460ae 100644 --- a/poetry.lock +++ b/poetry.lock @@ -12,6 +12,46 @@ files = [ {file = "blinker-1.6.2.tar.gz", hash = "sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213"}, ] +[[package]] +name = "boto3" +version = "1.26.137" +description = "The AWS SDK for Python" +category = "main" +optional = true +python-versions = ">= 3.7" +files = [ + {file = "boto3-1.26.137-py3-none-any.whl", hash = "sha256:b6d729beec0462ac1ae4a83a0fb04a62061a9f1f406d3151b45345daa9d1a5bc"}, + {file = "boto3-1.26.137.tar.gz", hash = "sha256:cac699fc46b43c10ca12aa6ea087c0b979613c5e3570aea11d86891652cb581e"}, +] + +[package.dependencies] +botocore = ">=1.29.137,<1.30.0" +jmespath = ">=0.7.1,<2.0.0" +s3transfer = ">=0.6.0,<0.7.0" + +[package.extras] +crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] + +[[package]] +name = "botocore" +version = "1.29.137" +description = "Low-level, data-driven core of boto 3." +category = "main" +optional = true +python-versions = ">= 3.7" +files = [ + {file = "botocore-1.29.137-py3-none-any.whl", hash = "sha256:5921a15c0a2a438f5ba9de4189d846f31d3770c690b0ffb237da5069444f2ba5"}, + {file = "botocore-1.29.137.tar.gz", hash = "sha256:1909fe368c08b4879d52ac1e3963f8bfcbebe3a84366c3f2000a45bd4510b027"}, +] + +[package.dependencies] +jmespath = ">=0.7.1,<2.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = ">=1.25.4,<1.27" + +[package.extras] +crt = ["awscrt (==0.16.9)"] + [[package]] name = "certifi" version = "2023.5.7" @@ -258,6 +298,18 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +category = "main" +optional = true +python-versions = ">=3.7" +files = [ + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, +] + [[package]] name = "markupsafe" version = "2.1.2" @@ -337,6 +389,21 @@ requests = "2.28.2" cli = ["click (==8.1.3)", "rich (==13.3.2)"] repl = ["ipython (==8.11.0)", "ptpython (==3.0.23)"] +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = true +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + [[package]] name = "requests" version = "2.28.2" @@ -359,23 +426,53 @@ urllib3 = ">=1.21.1,<1.27" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "s3transfer" +version = "0.6.1" +description = "An Amazon S3 Transfer Manager" +category = "main" +optional = true +python-versions = ">= 3.7" +files = [ + {file = "s3transfer-0.6.1-py3-none-any.whl", hash = "sha256:3c0da2d074bf35d6870ef157158641178a4204a6e689e82546083e31e0311346"}, + {file = "s3transfer-0.6.1.tar.gz", hash = "sha256:640bb492711f4c0c0905e1f62b6aaeb771881935ad27884852411f8e9cacbca9"}, +] + +[package.dependencies] +botocore = ">=1.12.36,<2.0a.0" + +[package.extras] +crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] + [[package]] name = "setuptools" -version = "67.7.2" +version = "67.8.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "main" optional = true python-versions = ">=3.7" files = [ - {file = "setuptools-67.7.2-py3-none-any.whl", hash = "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b"}, - {file = "setuptools-67.7.2.tar.gz", hash = "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990"}, + {file = "setuptools-67.8.0-py3-none-any.whl", hash = "sha256:5df61bf30bb10c6f756eb19e7c9f3b473051f48db77fddbe06ff2ca307df9a6f"}, + {file = "setuptools-67.8.0.tar.gz", hash = "sha256:62642358adc77ffa87233bc4d2354c4b2682d214048f500964dbe760ccedf102"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + [[package]] name = "slack-bolt" version = "1.18.0" @@ -466,9 +563,10 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [extras] +aws = ["boto3"] flask = ["flask", "flask-healthz", "gunicorn"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.11" -content-hash = "24564b0baaa09cba5f4042557019f007b5c52f3b292806135fe5196a294351ac" +content-hash = "643914eed87fcfbf8b28d88f4b14949041b15d602a32c0ba6a4e7fba60103113" diff --git a/pyproject.toml b/pyproject.toml index adfc44f..f460883 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,9 +14,11 @@ slack-bolt = "~1.18.0" flask = {version = "~2.3.1", optional = true} gunicorn = {version = "~20.1.0", optional = true} flask-healthz = {version = "^0.0.3", optional = true} +boto3 = {version = "^1.26.137", optional = true} [tool.poetry.extras] -flask = ["flask", "gunicorn", "flask-healthz"] +flask = ["flask", "gunicorn", "flask-healthz"] +aws = ["boto3"] [build-system] requires = ["poetry-core"] diff --git a/template.yaml.docker-image.patch b/template.yaml.docker-image.patch new file mode 100644 index 0000000..8a3491a --- /dev/null +++ b/template.yaml.docker-image.patch @@ -0,0 +1,63 @@ +--- a/template.yaml ++++ b/template.yaml +@@ -55,12 +55,25 @@ + Description: Salck Signing Token + NoEcho: true + +-Mappings: +- RegionToLayerArnMap: +- us-east-1: +- "LayerArn": "arn:aws:lambda:us-east-1:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:4" +- us-east-2: +- "LayerArn": "arn:aws:lambda:us-east-2:590474943231:layer:AWS-Parameters-and-Secrets-Lambda-Extension:4" ++ ### ++ # Parameters used only for `sam build` ++ ### ++ AwsAccessKeyId: ++ Type: String ++ Description: AWS_ACCESS_KEY_ID ++ NoEcho: true ++ Default: '' ++ ++ AwsSecretAccessKey: ++ Type: String ++ Description: AWS_SECRET_ACCESS_KEY ++ NoEcho: true ++ Default: '' ++ ++ Tag: ++ Type: String ++ Default: latest ++ Description: Docker tag to build and deploy. + + Globals: + Function: +@@ -71,10 +84,7 @@ + PritunlSlackFunction: + Type: AWS::Serverless::Function + Properties: +- CodeUri: pritunl_slack_app/function +- Handler: pritunl_slack_app.function_handler.handler +- Runtime: python3.10 +- PackageType: Zip ++ PackageType: Image + Architectures: + - x86_64 + Environment: +@@ -109,8 +119,14 @@ + - "lambda:InvokeAsync" + Resource: + - "*" +- Layers: +- - !FindInMap [RegionToLayerArnMap, !Ref "AWS::Region", LayerArn] ++ Metadata: ++ Dockerfile: lambda-function.Dockerfile ++ DockerContext: . ++ DockerTag: !Ref Tag ++ ++ DockerBuildArgs: ++ AWS_ACCESS_KEY_ID: !Ref AwsAccessKeyId ++ AWS_SECRET_ACCESS_KEY: !Ref AwsSecretAccessKey + + PritunlSlackUrlFunctionPermissions: + Type: AWS::Lambda::Permission