diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index d887a660..bd5f2df7 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -8,7 +8,7 @@ jobs: lock: runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v3 + - uses: dessant/lock-threads@v5 with: github-token: ${{ secrets.GITHUB_TOKEN }} issue-comment: > diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index cb32a0f8..6419f3aa 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -14,7 +14,7 @@ jobs: steps: # Please look up the latest version from # https://github.com/amannn/action-semantic-pull-request/releases - - uses: amannn/action-semantic-pull-request@v5.0.2 + - uses: amannn/action-semantic-pull-request@v6.1.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 06270c8a..057b9c42 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -7,7 +7,8 @@ on: - master env: - TERRAFORM_DOCS_VERSION: v0.16.0 + TERRAFORM_DOCS_VERSION: v0.20.0 + TFLINT_VERSION: v0.59.1 jobs: collectInputs: @@ -17,11 +18,11 @@ jobs: directories: ${{ steps.dirs.outputs.directories }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: Get root directories id: dirs - uses: clowdhaus/terraform-composite-actions/directories@v1.8.0 + uses: clowdhaus/terraform-composite-actions/directories@v1.14.0 preCommitMinVersions: name: Min TF pre-commit @@ -31,29 +32,61 @@ jobs: matrix: directory: ${{ fromJson(needs.collectInputs.outputs.directories) }} steps: + - name: Install rmz + uses: jaxxstorm/action-install-gh-release@v2.1.0 + with: + repo: SUPERCILEX/fuc + asset-name: x86_64-unknown-linux-gnu-rmz + rename-to: rmz + chmod: 0755 + extension-matching: disable + + # https://github.com/orgs/community/discussions/25678#discussioncomment-5242449 + - name: Delete unnecessary files + run: | + formatByteCount() { echo $(numfmt --to=iec-i --suffix=B --padding=7 $1'000'); } + getAvailableSpace() { echo $(df -a $1 | awk 'NR > 1 {avail+=$4} END {print avail}'); } + + BEFORE=$(getAvailableSpace) + + ln -s /opt/hostedtoolcache/SUPERCILEX/x86_64-unknown-linux-gnu-rmz/latest/linux-x64/rmz /usr/local/bin/rmz + rmz -f /opt/hostedtoolcache/CodeQL & + rmz -f /opt/hostedtoolcache/Java_Temurin-Hotspot_jdk & + rmz -f /opt/hostedtoolcache/PyPy & + rmz -f /opt/hostedtoolcache/Ruby & + rmz -f /opt/hostedtoolcache/go & + + wait + + AFTER=$(getAvailableSpace) + SAVED=$((AFTER-BEFORE)) + echo "=> Saved $(formatByteCount $SAVED)" + - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: Terraform min/max versions id: minMax - uses: clowdhaus/terraform-min-max@v1.2.0 + uses: clowdhaus/terraform-min-max@v2.1.0 with: directory: ${{ matrix.directory }} - name: Pre-commit Terraform ${{ steps.minMax.outputs.minVersion }} # Run only validate pre-commit check on min version supported if: ${{ matrix.directory != '.' }} - uses: clowdhaus/terraform-composite-actions/pre-commit@v1.8.0 + uses: clowdhaus/terraform-composite-actions/pre-commit@v1.14.0 with: terraform-version: ${{ steps.minMax.outputs.minVersion }} + tflint-version: ${{ env.TFLINT_VERSION }} args: 'terraform_validate --color=always --show-diff-on-failure --files ${{ matrix.directory }}/*' - name: Pre-commit Terraform ${{ steps.minMax.outputs.minVersion }} # Run only validate pre-commit check on min version supported if: ${{ matrix.directory == '.' }} - uses: clowdhaus/terraform-composite-actions/pre-commit@v1.8.0 + uses: clowdhaus/terraform-composite-actions/pre-commit@v1.14.0 with: terraform-version: ${{ steps.minMax.outputs.minVersion }} + tflint-version: ${{ env.TFLINT_VERSION }} args: 'terraform_validate --color=always --show-diff-on-failure --files $(ls *.tf)' preCommitMaxVersion: @@ -61,18 +94,75 @@ jobs: runs-on: ubuntu-latest needs: collectInputs steps: + - name: Install rmz + uses: jaxxstorm/action-install-gh-release@v2.1.0 + with: + repo: SUPERCILEX/fuc + asset-name: x86_64-unknown-linux-gnu-rmz + rename-to: rmz + chmod: 0755 + extension-matching: disable + + # https://github.com/orgs/community/discussions/25678#discussioncomment-5242449 + - name: Delete unnecessary files + run: | + formatByteCount() { echo $(numfmt --to=iec-i --suffix=B --padding=7 $1'000'); } + getAvailableSpace() { echo $(df -a $1 | awk 'NR > 1 {avail+=$4} END {print avail}'); } + + BEFORE=$(getAvailableSpace) + + ln -s /opt/hostedtoolcache/SUPERCILEX/x86_64-unknown-linux-gnu-rmz/latest/linux-x64/rmz /usr/local/bin/rmz + rmz -f /opt/hostedtoolcache/CodeQL & + rmz -f /opt/hostedtoolcache/Java_Temurin-Hotspot_jdk & + rmz -f /opt/hostedtoolcache/PyPy & + rmz -f /opt/hostedtoolcache/Ruby & + rmz -f /opt/hostedtoolcache/go & + sudo rmz -f /usr/local/lib/android & + + if [[ ${{ github.repository }} == terraform-aws-modules/terraform-aws-security-group ]]; then + sudo rmz -f /usr/share/dotnet & + sudo rmz -f /usr/local/.ghcup & + sudo apt-get -qq remove -y 'azure-.*' + sudo apt-get -qq remove -y 'cpp-.*' + sudo apt-get -qq remove -y 'dotnet-runtime-.*' + sudo apt-get -qq remove -y 'google-.*' + sudo apt-get -qq remove -y 'libclang-.*' + sudo apt-get -qq remove -y 'libllvm.*' + sudo apt-get -qq remove -y 'llvm-.*' + sudo apt-get -qq remove -y 'mysql-.*' + sudo apt-get -qq remove -y 'postgresql-.*' + sudo apt-get -qq remove -y 'php.*' + sudo apt-get -qq remove -y 'temurin-.*' + sudo apt-get -qq remove -y kubectl firefox mono-devel + sudo apt-get -qq autoremove -y + sudo apt-get -qq clean + fi + + wait + + AFTER=$(getAvailableSpace) + SAVED=$((AFTER-BEFORE)) + echo "=> Saved $(formatByteCount $SAVED)" + - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v5 with: ref: ${{ github.event.pull_request.head.ref }} repository: ${{github.event.pull_request.head.repo.full_name}} - name: Terraform min/max versions id: minMax - uses: clowdhaus/terraform-min-max@v1.2.0 + uses: clowdhaus/terraform-min-max@v2.1.0 + + - name: Hide template dir + # Special to this repo, we don't want to check this dir + if: ${{ github.repository == 'terraform-aws-modules/terraform-aws-security-group' }} + run: rm -rf modules/_templates - name: Pre-commit Terraform ${{ steps.minMax.outputs.maxVersion }} - uses: clowdhaus/terraform-composite-actions/pre-commit@v1.8.0 + uses: clowdhaus/terraform-composite-actions/pre-commit@v1.14.0 with: terraform-version: ${{ steps.minMax.outputs.maxVersion }} + tflint-version: ${{ env.TFLINT_VERSION }} terraform-docs-version: ${{ env.TERRAFORM_DOCS_VERSION }} + install-hcledit: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 98c8b258..e739b790 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,18 +20,26 @@ jobs: if: github.repository_owner == 'terraform-aws-modules' steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v5 with: persist-credentials: false fetch-depth: 0 + - name: Set correct Node.js version + uses: actions/setup-node@v6 + with: + node-version: 24 + + - name: Install dependencies + run: | + npm install \ + @semantic-release/changelog@6.0.3 \ + @semantic-release/git@10.0.1 \ + conventional-changelog-conventionalcommits@9.1.0 + - name: Release - uses: cycjimmy/semantic-release-action@v2 + uses: cycjimmy/semantic-release-action@v5 with: - semantic_version: 18.0.0 - extra_plugins: | - @semantic-release/changelog@6.0.0 - @semantic-release/git@10.0.0 - conventional-changelog-conventionalcommits@4.6.3 + semantic_version: 25.0.0 env: GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }} diff --git a/.github/workflows/stale-actions.yaml b/.github/workflows/stale-actions.yaml index 50379957..3e826dcf 100644 --- a/.github/workflows/stale-actions.yaml +++ b/.github/workflows/stale-actions.yaml @@ -7,7 +7,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v6 + - uses: actions/stale@v10 with: repo-token: ${{ secrets.GITHUB_TOKEN }} # Staling issues and PR's diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 139774bf..ab145451 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,7 +1,9 @@ name: Tests env: - PYTEST_VERSION: 7.1.3 + PYTEST_VERSION: 7.4.4 + RUFF_VERSION: 0.1.13 + RUFF_PY_VERSION: 3.12 on: push: @@ -16,22 +18,43 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python_version: ["3.7", "3.8", "3.9", "3.10"] + python_version: ["3.8", "3.9", "3.10", "3.11", "3.12"] fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4.1.1 - name: Set up Python ${{ matrix.python_version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v5.0.0 with: python-version: ${{ matrix.python_version }} - - name: Install poetry and tox + - name: Install poetry shell: bash run: | - pip install pytest==${PYTEST_VERSION} + pip install pytest==${{ env.PYTEST_VERSION }} - - name: Run tox + - name: Run pytest shell: bash run: | python -m pytest -vvv tests/ + + format: + name: Ruff Format check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.1.1 + + - name: Set up Python + uses: actions/setup-python@v5.0.0 + with: + python-version: ${{ env.RUFF_PY_VERSION }} + + - name: Install ruff + shell: bash + run: | + pip install ruff==${{ env.RUFF_VERSION }} + + - name: Run ruff format check + shell: bash + run: | + ruff format --check . diff --git a/.gitignore b/.gitignore index d5763d01..fd39819e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ # .tfstate files *.tfstate *.tfstate.* -*.tfplan # Crash log files crash.log @@ -29,9 +28,12 @@ override.tf.json .terraformrc terraform.rc -# Lambda directories +# Lambda build artifacts builds/ __pycache__/ - -# Test directories +*.zip .tox + +# Local editors/macos files +.DS_Store +.idea diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 74f3751c..991a8bbf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,29 +1,33 @@ repos: - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.76.0 + rev: v1.103.0 hooks: - id: terraform_fmt - - id: terraform_validate + - id: terraform_wrapper_module_for_each - id: terraform_docs args: - - '--args=--lockfile=false' + - "--args=--lockfile=false" - id: terraform_tflint args: - - '--args=--only=terraform_deprecated_interpolation' - - '--args=--only=terraform_deprecated_index' - - '--args=--only=terraform_unused_declarations' - - '--args=--only=terraform_comment_syntax' - - '--args=--only=terraform_documented_outputs' - - '--args=--only=terraform_documented_variables' - - '--args=--only=terraform_typed_variables' - - '--args=--only=terraform_module_pinned_source' - - '--args=--only=terraform_naming_convention' - - '--args=--only=terraform_required_version' - - '--args=--only=terraform_required_providers' - - '--args=--only=terraform_standard_module_structure' - - '--args=--only=terraform_workspace_remote' + - "--args=--only=terraform_deprecated_interpolation" + - "--args=--only=terraform_deprecated_index" + - "--args=--only=terraform_unused_declarations" + - "--args=--only=terraform_comment_syntax" + - "--args=--only=terraform_documented_outputs" + - "--args=--only=terraform_documented_variables" + - "--args=--only=terraform_typed_variables" + - "--args=--only=terraform_module_pinned_source" + - "--args=--only=terraform_naming_convention" + - "--args=--only=terraform_required_version" + - "--args=--only=terraform_required_providers" + - "--args=--only=terraform_standard_module_structure" + - "--args=--only=terraform_workspace_remote" + - id: terraform_validate - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v6.0.0 hooks: - id: check-merge-conflict - id: end-of-file-fixer + - id: trailing-whitespace + - id: mixed-line-ending + args: [--fix=lf] diff --git a/CHANGELOG.md b/CHANGELOG.md index c4e80327..7b60d567 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,515 @@ All notable changes to this project will be documented in this file. +## [8.1.2](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v8.1.1...v8.1.2) (2025-10-22) + +### Bug Fixes + +* Make quiet_archive_local_exec properly suppress Poetry/pip/npm output ([#709](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/709)) ([bae0385](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/bae03859f4b7a389c20e52dcbd5c83d58f1916a8)) + +## [8.1.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v8.1.0...v8.1.1) (2025-10-21) + +### Bug Fixes + +* Update CI workflow versions to latest ([#713](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/713)) ([feb4561](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/feb456187b3727b6b94562cc39d3cbce509b3d03)) + +## [8.1.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v8.0.1...v8.1.0) (2025-08-22) + + +### Features + +* Respect the package-lock.json for a NodeJS Lambda function ([#681](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/681)) ([5e4391c](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/5e4391c55605d11ac98655a2fd2d6a8f2583d3b6)) + +## [8.0.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v8.0.0...v8.0.1) (2025-06-25) + + +### Bug Fixes + +* Lower minimum Terraform version to 1.5.7 ([#688](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/688)) ([ab60651](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/ab606514be095d7ad55ebd920069cb090fa39cd5)) + +## [8.0.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.21.1...v8.0.0) (2025-06-25) + + +### ⚠ BREAKING CHANGES + +* Upgrade AWS provider and min required Terraform version to 6.0 and 1.10 respectively (#687) + +### Features + +* Upgrade AWS provider and min required Terraform version to 6.0 and 1.10 respectively ([#687](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/687)) ([367e9a2](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/367e9a2c5c7e6a4335fcc7c13c14e54f8e347f9c)) + +## [7.21.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.21.0...v7.21.1) (2025-06-19) + + +### Bug Fixes + +* Add .NET 8 runtime example ([#685](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/685)) ([d5c657c](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/d5c657c96234b1ee352af418243690c297f3f3b2)) + +## [7.21.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.20.3...v7.21.0) (2025-05-16) + + +### Features + +* Add buildx and multi-stage build support to docker-build module ([#679](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/679)) ([29893ab](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/29893ab17086b6ec45955f1f5d2f1be4f7cf2285)) + +## [7.20.3](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.20.2...v7.20.3) (2025-05-16) + + +### Bug Fixes + +* Do not expose output from build command in Docker ([#677](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/677)) ([75ee97d](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/75ee97d184231a45bdb8d8398ecccb6f2558d0a5)) + +## [7.20.2](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.20.1...v7.20.2) (2025-04-09) + + +### Bug Fixes + +* Add aws_partition to support usage of this module in aws-cn and gov ([64433c0](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/64433c096e690b767a8b106b67383edfe8263ba7)) + +## [7.20.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.20.0...v7.20.1) (2025-01-26) + + +### Bug Fixes + +* Make default tag `terraform-aws-modules` optional ([#657](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/657)) ([685af53](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/685af5370e580a89cee68aeae06bb40dc3257892)) + +## [7.20.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.19.0...v7.20.0) (2025-01-08) + + +### Features + +* Use inline instead of managed policies ([#615](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/615)) ([394d337](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/394d337450d88aa877ec560cd49080bb8b9a45ba)) + +## [7.19.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.18.0...v7.19.0) (2025-01-08) + + +### Features + +* Add `cache_from` option in the docker-build module ([#641](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/641)) ([55cdaa6](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/55cdaa68a63413f4ae5724c8b3a09a6b10d72f12)) + +## [7.18.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.17.1...v7.18.0) (2025-01-08) + + +### Features + +* Allow temp dir for poetry docker builds ([#638](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/638)) ([65ffea2](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/65ffea2cfd99a27b6be3fc3e48482cf0fb821f2f)) + +## [7.17.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.17.0...v7.17.1) (2025-01-07) + + +### Bug Fixes + +* Rename npm_package_json to npm_requirements ([#621](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/621)) ([4bc61eb](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/4bc61eb58005e149dc1ca87ba79f42b0cba944fd)) + +## [7.17.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.16.0...v7.17.0) (2024-12-08) + + +### Features + +* Support Event Source Mapping `metrics_config`, `provisioned_poller_config`, and Lambda Recursion Loop ([#649](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/649)) ([002d7ec](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/002d7ec3c9bc3e7a44fac536c3443ba640ff9828)) + +## [7.16.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.15.0...v7.16.0) (2024-11-26) + + +### Features + +* Radically redesign the build plan form ([#646](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/646)) ([32d8d06](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/32d8d060a660b0ec5702403da1b970118f62a314)) + +## [7.15.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.14.1...v7.15.0) (2024-11-18) + + +### Features + +* Make `source_path` blocks independent ([#640](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/640)) ([0fdac2e](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/0fdac2ec54fdcd5fd34787f348803000c1e21eb6)) + +## [7.14.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.14.0...v7.14.1) (2024-11-17) + + +### Bug Fixes + +* Skip broken symlinks on hash computing ([#639](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/639)) ([c28b940](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/c28b940c8b8a8ea8b423728e05883942f5eaf661)) + +## [7.14.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.13.0...v7.14.0) (2024-10-11) + + +### Features + +* Support lambda function `vpc_config.ipv6_allowed_for_dual_stack` and event source mapping `tags` ([#628](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/628)) ([2a602f9](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/2a602f9a4f76d11005d1dba56d9c966a87553f4e)) + + +### Bug Fixes + +* Update CI workflow versions to latest ([#631](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/631)) ([d06718f](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/d06718f605294f59a42ae6e3db70bfd7b9fa35f3)) + +## [7.13.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.12.0...v7.13.0) (2024-10-05) + + +### Features + +* Support `aws_lambda_event_source_mapping.document_db_event_source_config` ([#626](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/626)) ([5d48199](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/5d481996ed6ef5ce784847b7e5bae1bae1ee8bfd)) + +## [7.12.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.11.0...v7.12.0) (2024-10-05) + + +### Features + +* Add support for kafka event source config ([#617](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/617)) ([2c077cb](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/2c077cb1450af76cf44b56bfeba796ee9d9d9a00)) + +## [7.11.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.10.0...v7.11.0) (2024-10-01) + + +### Features + +* Add function_url_auth_type option to aws_lambda_permission ([#625](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/625)) ([9f13397](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/9f13397f20467e660eba0ae5fcf98c66c75187ba)) + +## [7.10.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.9.0...v7.10.0) (2024-09-29) + + +### Features + +* Add `tumbling_window_in_seconds` ([#623](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/623)) ([eedacff](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/eedacffef287cb02f776da4950e8345d9ec0200f)) + +## [7.9.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.8.1...v7.9.0) (2024-09-10) + + +### Features + +* Added more examples for Rust, Go, Java runtimes ([#612](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/612)) ([a6fe411](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/a6fe4115ac96592ecbda27f72d42536da6518add)) + +## [7.8.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.8.0...v7.8.1) (2024-08-23) + + +### Bug Fixes + +* Fix package.py commands after :zip not being executed ([#606](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/606)) ([801e69c](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/801e69c08b74217e7f1319b128d5efd264162aaf)) + +## [7.8.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.7.1...v7.8.0) (2024-08-23) + + +### Features + +* Added the skip_destroy argument for functions ([#600](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/600)) ([36c6109](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/36c61093dbb6114f9880d40b225e7f00f83493f9)) + +## [7.7.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.7.0...v7.7.1) (2024-07-25) + + +### Bug Fixes + +* Always use absolute path to temp folders ([#599](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/599)) ([a058372](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/a058372431c552a0cb740a76beffe77285edeb91)) + +## [7.7.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.6.0...v7.7.0) (2024-06-18) + + +### Features + +* Added support for alias to have multiple filter criteria same as function ([#585](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/585)) ([6549ca1](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/6549ca1301c74880e41440aa314e732739283e8a)) + +## [7.6.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.5.0...v7.6.0) (2024-06-12) + + +### Features + +* Support passing extra args to poetry export ([#584](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/584)) ([3aa288f](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/3aa288fee324e64a8db409e5a32abaeebe38e6c2)) + +## [7.5.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.4.0...v7.5.0) (2024-06-07) + + +### Features + +* Renamed python3.8-11 to python3.12 in examples, added tag to resources ([#583](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/583)) ([02ab668](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/02ab668458c87792861a54f54fd1b00e97afcc68)) + +## [7.4.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.3.0...v7.4.0) (2024-05-03) + + +### Features + +* Added support for CW log_group_class and skip_destroy ([#565](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/565)) ([7256f7c](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/7256f7c226adf294bb6280f1fc4326d015e78d83)) + +## [7.3.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.2.6...v7.3.0) (2024-05-03) + + +### Features + +* Added create before destroy on aws_lambda_permission ([#561](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/561)) ([e9c4676](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/e9c467688de057a454646d5f947f3d4527f78a19)) + +## [7.2.6](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.2.5...v7.2.6) (2024-04-12) + + +### Bug Fixes + +* Zip source directory should read from sh_work_dir ([#560](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/560)) ([f786681](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/f7866811bc1429ce224bf6a35448cb44aa5155e7)) + +## [7.2.5](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.2.4...v7.2.5) (2024-03-29) + + +### Bug Fixes + +* Run pre-commit autoupdate (trigger patch release) ([#555](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/555)) ([8bb79de](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/8bb79de2733503aeb5824423b1a5f573ac25004d)) + +## [7.2.4](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.2.3...v7.2.4) (2024-03-29) + + +### Bug Fixes + +* Dont raise FileNotFoundError from close() on tmpfile rename ([#550](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/550)) ([58ba987](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/58ba987a07710957abce30b4bba6587873f9b0e1)) + +## [7.2.3](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.2.2...v7.2.3) (2024-03-22) + + +### Bug Fixes + +* Fixed constant drift with Lambda logging configuration ([#551](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/551)) ([8f97707](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/8f97707f6ea9aa3d382106a4917a0ddd1c3ec3e2)) + +## [7.2.2](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.2.1...v7.2.2) (2024-03-13) + + +### Bug Fixes + +* Update CI workflow versions to remove deprecated runtime warnings ([#549](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/549)) ([cfe47e6](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/cfe47e63e906658dd4e8a5162ebac290b6a2cdf8)) + +### [7.2.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.2.0...v7.2.1) (2024-01-31) + + +### Bug Fixes + +* Dynamic logging config for Gov Cloud ([#541](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/541)) ([b9a6ea1](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/b9a6ea18aa5b060d9d1b6e1bddfa50f60954da0d)) + +## [7.2.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.1.0...v7.2.0) (2024-01-26) + + +### Features + +* Added support to override default tags of provider in S3 object ([#538](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/538)) ([e33a1a1](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/e33a1a1ad214d1c1e5aa0adb0d40c50cfd21d135)) + +## [7.1.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v7.0.0...v7.1.0) (2024-01-22) + + +### Features + +* Commands should fail the build if their exit code is not zero ([#534](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/534)) ([eebfc36](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/eebfc3618ae290683456dc4e2fc7136857a95c57)) + +## [7.0.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v6.8.0...v7.0.0) (2024-01-19) + + +### ⚠ BREAKING CHANGES + +* Added advanced logging configuration. Bump version of AWS provider to 5.32 (#531) + +### Features + +* Added advanced logging configuration. Bump version of AWS provider to 5.32 ([#531](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/531)) ([259b403](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/259b40300f0719179a0e5c5a0143795597329ae8)) + +## [6.8.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v6.7.1...v6.8.0) (2024-01-17) + + +### Features + +* Allow defining direct path to pyproject.toml ([#525](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/525)) ([d33b722](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/d33b722d30e346b3966fe8f6e5d92ee554c2011d)) + +### [6.7.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v6.7.0...v6.7.1) (2024-01-15) + + +### Bug Fixes + +* Set timeouts only when values are given ([#522](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/522)) ([b4bfe39](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/b4bfe39fab2a53607dc770bed18599a0fca5a694)) + +## [6.7.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v6.6.0...v6.7.0) (2024-01-14) + + +### Features + +* Add control to use timestamp to trigger the package creation or not (useful for CI/CD) ([#521](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/521)) ([57dbfc1](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/57dbfc1909206bd6034b0d36883029a953c199db)) + +## [6.6.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v6.5.0...v6.6.0) (2024-01-12) + + +### Features + +* Added support for triggers on docker_registry_image resource ([#518](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/518)) ([4ed7d19](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/4ed7d196dc26ca80daf6d04416e2a9fa91af6c1b)) + +## [6.5.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v6.4.0...v6.5.0) (2023-11-22) + + +### Features + +* Added variable to control the create log group permission ([#514](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/514)) ([c173c27](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/c173c27fb57969da85967f2896b858c4654b0bba)) + +## [6.4.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v6.3.0...v6.4.0) (2023-11-07) + + +### Features + +* Added support for triggers in docker-build module when hash changes ([#510](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/510)) ([41d8db7](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/41d8db71ad4fc9f56bb55c314133ce007f587e33)) + +## [6.3.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v6.2.0...v6.3.0) (2023-11-03) + + +### Features + +* Allow to specify custom KMS key for S3 object ([#505](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/505)) ([eb339d6](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/eb339d658c232d0afa0a7f4f7902becab2a2a2e9)) + +## [6.2.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v6.1.0...v6.2.0) (2023-10-27) + + +### Features + +* Make `compatible_runtimes` optional, added sam metadata control ([#493](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/493)) ([180da4c](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/180da4cb0a720f7138e6504700ddfe8d9c63abfd)) + +## [6.1.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v6.0.1...v6.1.0) (2023-10-27) + + +### Features + +* Allows tags to be provided only to the function ([#508](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/508)) ([610d602](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/610d602bb2038d3c2719c14d938b303cefcccac9)) + +### [6.0.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v6.0.0...v6.0.1) (2023-10-05) + + +### Bug Fixes + +* Fixed npm install on Windows without having to use wsl ([#502](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/502)) ([ffa56e8](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/ffa56e896d7c5e5c8cbc851f0c453b70e4ec100f)) + +## [6.0.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v5.3.0...v6.0.0) (2023-08-09) + + +### ⚠ BREAKING CHANGES + +* Disable creation of SAM metadata null-resources by default (#494) + +### Features + +* Disable creation of SAM metadata null-resources by default ([#494](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/494)) ([9c9603c](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/9c9603cbb889a2cda1555deaed908d320e013515)) + +## [5.3.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v5.2.0...v5.3.0) (2023-07-17) + + +### Features + +* Added timeouts for Lambda Functions ([#485](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/485)) ([2a59ba2](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/2a59ba2948fa22dd7cb7a1c8a721fa826c3832e8)) + +## [5.2.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v5.1.0...v5.2.0) (2023-07-05) + + +### Features + +* Add module wrappers ([#479](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/479)) ([b5e9346](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/b5e9346de58bff16a63b63f76209bdb59534105e)) + +## [5.1.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v5.0.0...v5.1.0) (2023-07-04) + + +### Features + +* Support maximum concurrency of Lambda Alias with SQS as an event source ([#457](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/457)) ([24bd26e](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/24bd26e8d598d183a995e2742713e122ecc607a5)) + +## [5.0.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.18.0...v5.0.0) (2023-06-05) + + +### ⚠ BREAKING CHANGES + +* Bump versions of Terraform to 1.0, kreuzwerker/docker provider to 3.0 (#464) + +### Features + +* Bump versions of Terraform to 1.0, kreuzwerker/docker provider to 3.0 ([#464](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/464)) ([3f2044f](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/3f2044f0d6a5cad4b37100c26b2558d1acb9b982)) + +## [4.18.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.17.0...v4.18.0) (2023-05-17) + + +### Features + +* Added control to create logs by Lambda@Edge in all regions ([#462](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/462)) ([712d8ec](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/712d8ecb9a224be8ed36cb34eebf4b7e815d0565)) + +## [4.17.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.16.0...v4.17.0) (2023-05-04) + + +### Features + +* add qualified invoke ARN output ([#437](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/437)) ([dcd899b](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/dcd899b40bdeb4c7f607a5568e6f24dac81f26a0)) + +## [4.16.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.15.0...v4.16.0) (2023-04-18) + + +### Features + +* Adding variable principal_org_id to resource aws_lambda_permission ([#448](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/448)) ([31d75e7](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/31d75e7206d2816471fe828e86ef3f2a1ad1218d)) + +## [4.15.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.14.0...v4.15.0) (2023-04-17) + + +### Features + +* Add invoke_mode input ([#446](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/446)) ([d7b3ac9](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/d7b3ac970b7f18be19c95ec43ce4d1fac9ae2572)) + +## [4.14.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.13.0...v4.14.0) (2023-04-14) + + +### Features + +* Add max session duration for IAM role ([#391](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/391)) ([3a21ac5](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/3a21ac58bc5c4e1cb369a935a977246c10f31cf5)) + +## [4.13.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.12.1...v4.13.0) (2023-04-03) + + +### Features + +* Support maximum concurrency of Lambda with SQS as an event source ([#402](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/402)) ([268975c](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/268975c9e2224cb05bdad8d7c39f879610dedc53)) + +### [4.12.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.12.0...v4.12.1) (2023-03-10) + + +### Bug Fixes + +* Set the default value of replacement_security_group_ids to null ([#434](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/434)) ([a2d9ff9](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/a2d9ff97d437670feb2f361cf4874e193eea8a12)) + +## [4.12.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.11.0...v4.12.0) (2023-03-10) + + +### Features + +* Added configuration options to replace security groups on destroy of Lambda function ([#422](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/422)) ([2d92236](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/2d92236245edf0f614fb949e6b5e84f2c0216dcd)) + +## [4.11.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.10.1...v4.11.0) (2023-03-10) + + +### Features + +* Add dynamic blocks for consumer group id config ([#399](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/399)) ([7d7bb79](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/7d7bb792ceb0ba97192a8f8fe5b4a232e3239af8)) + +### [4.10.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.10.0...v4.10.1) (2023-02-13) + + +### Bug Fixes + +* Properly construct poetry commands when not using docker for building package ([#420](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/420)) ([97b00d3](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/97b00d309a5b8e8c16f9790658db1fc411c124f4)) + +## [4.10.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.9.0...v4.10.0) (2023-02-10) + + +### Features + +* Allow multiple filters in event source mappings ([#379](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/379)) ([66eb330](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/66eb330d4352a2bd95feded7f17f4c5046175aa5)) + +## [4.9.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.8.0...v4.9.0) (2023-01-30) + + +### Features + +* Add snap_start functionality ([#406](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/406)) ([91c811b](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/91c811bfdf190f3eb1f4f2beaad3e401916d67b3)) + +## [4.8.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.7.2...v4.8.0) (2023-01-18) + + +### Features + +* Update docker provider pin to 2.x in docker-build submodule ([#401](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/401)) ([fc2a39b](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/fc2a39b3e81d3a86992deab198566500a7066fab)) + +### [4.7.2](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.7.1...v4.7.2) (2023-01-10) + + +### Bug Fixes + +* Use a version for to avoid GitHub API rate limiting on CI workflows ([#393](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/393)) ([5481694](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/54816948d469cc753adca5b9bbd28c690c25ee3a)) + ### [4.7.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.7.0...v4.7.1) (2022-11-11) diff --git a/README.md b/README.md index be8fa241..3d045ce0 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This Terraform module is the part of [serverless.tf framework](https://github.co ## Features - Build dependencies for your Lambda Function and Layer. -- Support builds locally and in Docker (with or without SSH agent support for private builds). +- Support builds locally and in Docker (with or without SSH agent support for private builds) for any runtime and architecture supported by AWS Lambda. - Create deployment package or deploy existing (previously built package) from local, from S3, from URL, or from AWS ECR repository. - Store deployment packages locally or in the S3 bucket. - Support almost all features of Lambda resources (function, layer, alias, etc.) @@ -37,7 +37,7 @@ module "lambda_function" { function_name = "my-lambda1" description = "My awesome lambda function" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" source_path = "../src/lambda-function1" @@ -56,7 +56,7 @@ module "lambda_function" { function_name = "lambda-with-layer" description = "My awesome lambda function" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" publish = true source_path = "../src/lambda-function1" @@ -84,7 +84,7 @@ module "lambda_layer_s3" { layer_name = "lambda-layer-s3" description = "My amazing lambda layer (deployed from S3)" - compatible_runtimes = ["python3.8"] + compatible_runtimes = ["python3.12"] source_path = "../src/lambda-layer" @@ -102,7 +102,7 @@ module "lambda_function_existing_package_local" { function_name = "my-lambda-existing-package-local" description = "My awesome lambda function" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" create_package = false local_existing_package = "../existing_package.zip" @@ -126,7 +126,7 @@ module "lambda_function_externally_managed_package" { function_name = "my-lambda-externally-managed-package" description = "My lambda function code is deployed separately" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" create_package = false local_existing_package = "./lambda_functions/code.zip" @@ -161,7 +161,7 @@ module "lambda_function_existing_package_s3" { function_name = "my-lambda-existing-package-local" description = "My awesome lambda function" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" create_package = false s3_existing_package = { @@ -197,9 +197,9 @@ module "lambda_layer_local" { layer_name = "my-layer-local" description = "My amazing lambda layer (deployed from local)" - compatible_runtimes = ["python3.8"] + compatible_runtimes = ["python3.12"] - source_path = "../fixtures/python3.8-app1" + source_path = "../fixtures/python-app1" } module "lambda_layer_s3" { @@ -209,9 +209,9 @@ module "lambda_layer_s3" { layer_name = "my-layer-s3" description = "My amazing lambda layer (deployed from S3)" - compatible_runtimes = ["python3.8"] + compatible_runtimes = ["python3.12"] - source_path = "../fixtures/python3.8-app1" + source_path = "../fixtures/python-app1" store_on_s3 = true s3_bucket = "my-bucket-id-with-lambda-builds" @@ -231,9 +231,9 @@ module "lambda_at_edge" { function_name = "my-lambda-at-edge" description = "My awesome lambda@edge function" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" - source_path = "../fixtures/python3.8-app1" + source_path = "../fixtures/python-app1" tags = { Module = "lambda-at-edge" @@ -250,9 +250,9 @@ module "lambda_function_in_vpc" { function_name = "my-lambda-in-vpc" description = "My awesome lambda function" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" - source_path = "../fixtures/python3.8-app1" + source_path = "../fixtures/python-app1" vpc_subnet_ids = module.vpc.intra_subnets vpc_security_group_ids = [module.vpc.default_security_group_id] @@ -293,6 +293,10 @@ module "lambda_function" { # ...omitted for brevity allowed_triggers = { + Config = { + principal = "config.amazonaws.com" + principal_org_id = "o-abcdefghij" + } APIGatewayAny = { service = "apigateway" source_arn = "arn:aws:execute-api:eu-west-1:135367859851:aqnku8akd0/*/*/*" @@ -380,7 +384,7 @@ When `source_path` is set to a list of directories the content of each will be t ### Combine various options for extreme flexibility -This is the most complete way of creating a deployment package from multiple sources with multiple dependencies. This example is showing some of the available options (see [examples/build-package](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/build-package) for more): +This is the most complete way of creating a deployment package from multiple sources with multiple dependencies. This example is showing some of the available options (see [examples/build-package](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/build-package) and [examples/runtimes](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/runtimes) for more): ```hcl source_path = [ @@ -392,12 +396,12 @@ source_path = [ "!.*/.*\\.txt", # Skip all txt files recursively ] }, { - path = "src/python3.8-app1", + path = "src/python-app1", pip_requirements = true, pip_tmp_dir = "/tmp/dir/location" prefix_in_zip = "foo/bar1", }, { - path = "src/python3.8-app2", + path = "src/python-app2", pip_requirements = "requirements-large.txt", patterns = [ "!vendor/colorful-0.5.4.dist-info/RECORD", @@ -410,7 +414,7 @@ source_path = [ npm_tmp_dir = "/tmp/dir/location" prefix_in_zip = "foo/bar1", }, { - path = "src/python3.8-app3", + path = "src/python-app3", commands = [ "npm install", ":zip" @@ -420,7 +424,7 @@ source_path = [ "node_modules/.+", # Include all node_modules ], }, { - path = "src/python3.8-app3", + path = "src/python-app3", commands = ["go build"], patterns = < How to use AWS SAM CLI to test Lambda Function? -[AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-command-reference.html) is an open source tool that help the developers to initiate, build, test, and deploy serverless -applications. Currently, SAM CLI tool only supports CFN applications, but SAM CLI team is working on a feature to extend the testing capabilities to support terraform applications (check this [Github issue](https://github.com/aws/aws-sam-cli/issues/3154) -to be updated about the incoming releases, and features included in each release for the Terraform support feature). +[AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-command-reference.html) is an open source tool that help the developers to initiate, build, test, and deploy serverless +applications. SAM CLI tool [supports Terraform applications](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-terraform-support.html). SAM CLI provides two ways of testing: local testing and testing on-cloud (Accelerate). ### Local Testing Using SAM CLI, you can invoke the lambda functions defined in the terraform application locally using the [sam local invoke](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-invoke.html) -command, providing the function terraform address, or function name, and to set the `hook-name` to `terraform` to tell SAM CLI that the underlying project is a terraform application. +command, providing the function terraform address, or function name, and to set the `hook-name` to `terraform` to tell SAM CLI that the underlying project is a terraform application. You can execute the `sam local invoke` command from your terraform application root directory as following: ``` -sam local invoke --hook-name terraform module.hello_world_function.aws_lambda_function.this[0] +sam local invoke --hook-name terraform module.hello_world_function.aws_lambda_function.this[0] ``` You can also pass an event to your lambda function, or overwrite its environment variables. Check [here](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-using-invoke.html) for more information. You can also invoke your lambda function in debugging mode, and step-through your lambda function source code locally in your preferred editor. Check [here](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-using-debugging.html) for more information. ### Testing on-cloud (Accelerate) -You can use AWS SAM CLI to quickly test your application on your AWS development account. Using SAM Accelerate, you will be able to develop your lambda functions locally, +You can use AWS SAM CLI to quickly test your application on your AWS development account. Using SAM Accelerate, you will be able to develop your lambda functions locally, and once you save your updates, SAM CLI will update your development account with the updated Lambda functions. So, you can test it on cloud, and if there is any bug, you can quickly update the code, and SAM CLI will take care of pushing it to the cloud. Check [here](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/accelerate.html) for more information about SAM Accelerate. You can execute the `sam sync` command from your terraform application root directory as following: ``` -sam sync --hook-name terraform --watch +sam sync --hook-name terraform --watch ``` ## How to deploy and manage Lambda Functions? @@ -617,7 +623,7 @@ Q2: How to force recreate deployment package? Q3: `null_resource.archive[0] must be replaced` -> Answer: This probably mean that zip-archive has been deployed, but is currently absent locally, and it has to be recreated locally. When you run into this issue during CI/CD process (where workspace is clean) or from multiple workspaces, you can set environment variable `TF_RECREATE_MISSING_LAMBDA_PACKAGE=false` or pass `recreate_missing_package = false` as a parameter to the module and run `terraform apply`. +> Answer: This probably mean that zip-archive has been deployed, but is currently absent locally, and it has to be recreated locally. When you run into this issue during CI/CD process (where workspace is clean) or from multiple workspaces, you can set environment variable `TF_RECREATE_MISSING_LAMBDA_PACKAGE=false` or pass `recreate_missing_package = false` as a parameter to the module and run `terraform apply`. Alternatively, you can pass `trigger_on_package_timestamp = false` as a parameter to ignore the file timestamp when deciding to create the archive or not. Q4: What does this error mean - `"We currently do not support adding policies for $LATEST."` ? @@ -637,6 +643,7 @@ Q4: What does this error mean - `"We currently do not support adding policies fo - [Complete](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/complete) - Create Lambda resources in various combinations with all supported features. - [Container Image](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/container-image) - Create a Docker image with a platform specified in the Dockerfile (using [docker provider](https://registry.terraform.io/providers/kreuzwerker/docker)), push it to AWS ECR, and create Lambda function from it. - [Build and Package](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/build-package) - Build and create deployment packages in various ways. +- [Runtimes](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/build-package) - Build and create deployment packages for various runtimes (such as Rust, Go, Java). - [Alias](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/alias) - Create static and dynamic aliases in various ways. - [Deploy](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/deploy) - Complete end-to-end build/update/deploy process using AWS CodeDeploy. - [Async Invocations](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/async) - Create Lambda Function with async event configuration (with SQS, SNS, and EventBridge integration). @@ -647,19 +654,20 @@ Q4: What does this error mean - `"We currently do not support adding policies fo - [Event Source Mapping](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/event-source-mapping) - Create Lambda Function with event source mapping configuration (SQS, DynamoDB, Amazon MQ, and Kinesis). - [Triggers](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/triggers) - Create Lambda Function with some triggers (eg, Cloudwatch Events, EventBridge). - [Code Signing](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/code-signing) - Create Lambda Function with code signing configuration. +- [Simple CI/CD](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/simple-cicd) - Create Lambda Function as if it runs on CI/CD platform where `builds` directory is often absent. # Examples by the users of this module - [1Mill/serverless-tf-examples](https://github.com/1Mill/serverless-tf-examples/tree/main/src) - + ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 4.9 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | | [external](#requirement\_external) | >= 1.0 | | [local](#requirement\_local) | >= 1.0 | | [null](#requirement\_null) | >= 2.0 | @@ -668,7 +676,7 @@ Q4: What does this error mean - `"We currently do not support adding policies fo | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.9 | +| [aws](#provider\_aws) | >= 6.0 | | [external](#provider\_external) | >= 1.0 | | [local](#provider\_local) | >= 1.0 | | [null](#provider\_null) | >= 2.0 | @@ -682,28 +690,21 @@ No modules. | Name | Type | |------|------| | [aws_cloudwatch_log_group.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | -| [aws_iam_policy.additional_inline](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | -| [aws_iam_policy.additional_json](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | -| [aws_iam_policy.additional_jsons](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | -| [aws_iam_policy.async](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | -| [aws_iam_policy.dead_letter](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | -| [aws_iam_policy.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | -| [aws_iam_policy.tracing](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | -| [aws_iam_policy.vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_role.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | -| [aws_iam_role_policy_attachment.additional_inline](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_iam_role_policy_attachment.additional_json](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_iam_role_policy_attachment.additional_jsons](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy.additional_inline](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_iam_role_policy.additional_json](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_iam_role_policy.additional_jsons](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_iam_role_policy.async](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_iam_role_policy.dead_letter](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_iam_role_policy.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_iam_role_policy.tracing](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_iam_role_policy.vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | | [aws_iam_role_policy_attachment.additional_many](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.additional_one](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_iam_role_policy_attachment.async](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_iam_role_policy_attachment.dead_letter](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_iam_role_policy_attachment.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_iam_role_policy_attachment.tracing](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_iam_role_policy_attachment.vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_lambda_event_source_mapping.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_event_source_mapping) | resource | | [aws_lambda_function.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource | | [aws_lambda_function_event_invoke_config.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function_event_invoke_config) | resource | +| [aws_lambda_function_recursion_config.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function_recursion_config) | resource | | [aws_lambda_function_url.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function_url) | resource | | [aws_lambda_layer_version.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_layer_version) | resource | | [aws_lambda_permission.current_version_triggers](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource | @@ -738,6 +739,7 @@ No modules. | [assume\_role\_policy\_statements](#input\_assume\_role\_policy\_statements) | Map of dynamic policy statements for assuming Lambda Function role (trust relationship) | `any` | `{}` | no | | [attach\_async\_event\_policy](#input\_attach\_async\_event\_policy) | Controls whether async event policy should be added to IAM role for Lambda Function | `bool` | `false` | no | | [attach\_cloudwatch\_logs\_policy](#input\_attach\_cloudwatch\_logs\_policy) | Controls whether CloudWatch Logs policy should be added to IAM role for Lambda Function | `bool` | `true` | no | +| [attach\_create\_log\_group\_permission](#input\_attach\_create\_log\_group\_permission) | Controls whether to add the create log group permission to the CloudWatch logs policy | `bool` | `true` | no | | [attach\_dead\_letter\_policy](#input\_attach\_dead\_letter\_policy) | Controls whether SNS/SQS dead letter notification policy should be added to IAM role for Lambda Function | `bool` | `false` | no | | [attach\_network\_policy](#input\_attach\_network\_policy) | Controls whether VPC/network policy should be added to IAM role for Lambda Function | `bool` | `false` | no | | [attach\_policies](#input\_attach\_policies) | Controls whether list of policies should be added to IAM role for Lambda Function | `bool` | `false` | no | @@ -749,7 +751,9 @@ No modules. | [authorization\_type](#input\_authorization\_type) | The type of authentication that the Lambda Function URL uses. Set to 'AWS\_IAM' to restrict access to authenticated IAM users only. Set to 'NONE' to bypass IAM authentication and create a public endpoint. | `string` | `"NONE"` | no | | [build\_in\_docker](#input\_build\_in\_docker) | Whether to build dependencies in Docker | `bool` | `false` | no | | [cloudwatch\_logs\_kms\_key\_id](#input\_cloudwatch\_logs\_kms\_key\_id) | The ARN of the KMS Key to use when encrypting log data. | `string` | `null` | no | +| [cloudwatch\_logs\_log\_group\_class](#input\_cloudwatch\_logs\_log\_group\_class) | Specified the log class of the log group. Possible values are: `STANDARD` or `INFREQUENT_ACCESS` | `string` | `null` | no | | [cloudwatch\_logs\_retention\_in\_days](#input\_cloudwatch\_logs\_retention\_in\_days) | Specifies the number of days you want to retain log events in the specified log group. Possible values are: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653. | `number` | `null` | no | +| [cloudwatch\_logs\_skip\_destroy](#input\_cloudwatch\_logs\_skip\_destroy) | Whether to keep the log group (and any logs it may contain) at destroy time. | `bool` | `false` | no | | [cloudwatch\_logs\_tags](#input\_cloudwatch\_logs\_tags) | A map of tags to assign to the resource. | `map(string)` | `{}` | no | | [code\_signing\_config\_arn](#input\_code\_signing\_config\_arn) | Amazon Resource Name (ARN) for a Code Signing Configuration | `string` | `null` | no | | [compatible\_architectures](#input\_compatible\_architectures) | A list of Architectures Lambda layer is compatible with. Currently x86\_64 and arm64 can be specified. | `list(string)` | `null` | no | @@ -764,6 +768,7 @@ No modules. | [create\_layer](#input\_create\_layer) | Controls whether Lambda Layer resource should be created | `bool` | `false` | no | | [create\_package](#input\_create\_package) | Controls whether Lambda package should be created | `bool` | `true` | no | | [create\_role](#input\_create\_role) | Controls whether IAM role for Lambda Function should be created | `bool` | `true` | no | +| [create\_sam\_metadata](#input\_create\_sam\_metadata) | Controls whether the SAM metadata null resource should be created | `bool` | `false` | no | | [create\_unqualified\_alias\_allowed\_triggers](#input\_create\_unqualified\_alias\_allowed\_triggers) | Whether to allow triggers on unqualified alias pointing to $LATEST version | `bool` | `true` | no | | [create\_unqualified\_alias\_async\_event\_config](#input\_create\_unqualified\_alias\_async\_event\_config) | Whether to allow async event configuration on unqualified alias pointing to $LATEST version | `bool` | `true` | no | | [create\_unqualified\_alias\_lambda\_function\_url](#input\_create\_unqualified\_alias\_lambda\_function\_url) | Whether to use unqualified alias pointing to $LATEST version in Lambda Function URL | `bool` | `true` | no | @@ -784,6 +789,7 @@ No modules. | [file\_system\_arn](#input\_file\_system\_arn) | The Amazon Resource Name (ARN) of the Amazon EFS Access Point that provides access to the file system. | `string` | `null` | no | | [file\_system\_local\_mount\_path](#input\_file\_system\_local\_mount\_path) | The path where the function can access the file system, starting with /mnt/. | `string` | `null` | no | | [function\_name](#input\_function\_name) | A unique name for your Lambda Function | `string` | `""` | no | +| [function\_tags](#input\_function\_tags) | A map of tags to assign only to the lambda function | `map(string)` | `{}` | no | | [handler](#input\_handler) | Lambda Function entrypoint in your code | `string` | `""` | no | | [hash\_extra](#input\_hash\_extra) | The string to add into hashing function. Useful when building same source path for different functions. | `string` | `""` | no | | [ignore\_source\_code\_hash](#input\_ignore\_source\_code\_hash) | Whether to ignore changes to the function's source code hash. Set to true if you manage infrastructure and code deployments separately. | `bool` | `false` | no | @@ -791,14 +797,22 @@ No modules. | [image\_config\_entry\_point](#input\_image\_config\_entry\_point) | The ENTRYPOINT for the docker image | `list(string)` | `[]` | no | | [image\_config\_working\_directory](#input\_image\_config\_working\_directory) | The working directory for the docker image | `string` | `null` | no | | [image\_uri](#input\_image\_uri) | The ECR image URI containing the function's deployment package. | `string` | `null` | no | +| [include\_default\_tag](#input\_include\_default\_tag) | Set to false to not include the default tag in the tags map. | `bool` | `true` | no | +| [invoke\_mode](#input\_invoke\_mode) | Invoke mode of the Lambda Function URL. Valid values are BUFFERED (default) and RESPONSE\_STREAM. | `string` | `null` | no | +| [ipv6\_allowed\_for\_dual\_stack](#input\_ipv6\_allowed\_for\_dual\_stack) | Allows outbound IPv6 traffic on VPC functions that are connected to dual-stack subnets | `bool` | `null` | no | | [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN of KMS key to use by your Lambda Function | `string` | `null` | no | | [lambda\_at\_edge](#input\_lambda\_at\_edge) | Set this to true if using Lambda@Edge, to enable publishing, limit the timeout, and allow edgelambda.amazonaws.com to invoke the function | `bool` | `false` | no | +| [lambda\_at\_edge\_logs\_all\_regions](#input\_lambda\_at\_edge\_logs\_all\_regions) | Whether to specify a wildcard in IAM policy used by Lambda@Edge to allow logging in all regions | `bool` | `true` | no | | [lambda\_role](#input\_lambda\_role) | IAM role ARN attached to the Lambda Function. This governs both who / what can invoke your Lambda Function, as well as what resources our Lambda Function has access to. See Lambda Permission Model for more details. | `string` | `""` | no | | [layer\_name](#input\_layer\_name) | Name of Lambda Layer to create | `string` | `""` | no | | [layer\_skip\_destroy](#input\_layer\_skip\_destroy) | Whether to retain the old version of a previously deployed Lambda Layer. | `bool` | `false` | no | | [layers](#input\_layers) | List of Lambda Layer Version ARNs (maximum of 5) to attach to your Lambda Function. | `list(string)` | `null` | no | | [license\_info](#input\_license\_info) | License info for your Lambda Layer. Eg, MIT or full url of a license. | `string` | `""` | no | | [local\_existing\_package](#input\_local\_existing\_package) | The absolute path to an existing zip-file to use | `string` | `null` | no | +| [logging\_application\_log\_level](#input\_logging\_application\_log\_level) | The application log level of the Lambda Function. Valid values are "TRACE", "DEBUG", "INFO", "WARN", "ERROR", or "FATAL". | `string` | `"INFO"` | no | +| [logging\_log\_format](#input\_logging\_log\_format) | The log format of the Lambda Function. Valid values are "JSON" or "Text". | `string` | `"Text"` | no | +| [logging\_log\_group](#input\_logging\_log\_group) | The CloudWatch log group to send logs to. | `string` | `null` | no | +| [logging\_system\_log\_level](#input\_logging\_system\_log\_level) | The system log level of the Lambda Function. Valid values are "DEBUG", "INFO", or "WARN". | `string` | `"INFO"` | no | | [maximum\_event\_age\_in\_seconds](#input\_maximum\_event\_age\_in\_seconds) | Maximum age of a request that Lambda sends to a function for processing in seconds. Valid values between 60 and 21600. | `number` | `null` | no | | [maximum\_retry\_attempts](#input\_maximum\_retry\_attempts) | Maximum number of times to retry when the function returns an error. Valid values between 0 and 2. Defaults to 2. | `number` | `null` | no | | [memory\_size](#input\_memory\_size) | Amount of memory in MB your Lambda Function can use at runtime. Valid value between 128 MB to 10,240 MB (10 GB), in 64 MB increments. | `number` | `128` | no | @@ -810,15 +824,20 @@ No modules. | [policy\_json](#input\_policy\_json) | An additional policy document as JSON to attach to the Lambda Function role | `string` | `null` | no | | [policy\_jsons](#input\_policy\_jsons) | List of additional policy documents as JSON to attach to Lambda Function role | `list(string)` | `[]` | no | | [policy\_name](#input\_policy\_name) | IAM policy name. It override the default value, which is the same as role\_name | `string` | `null` | no | -| [policy\_path](#input\_policy\_path) | Path of policies to that should be added to IAM role for Lambda Function | `string` | `null` | no | | [policy\_statements](#input\_policy\_statements) | Map of dynamic policy statements to attach to Lambda Function role | `any` | `{}` | no | | [provisioned\_concurrent\_executions](#input\_provisioned\_concurrent\_executions) | Amount of capacity to allocate. Set to 1 or greater to enable, or set to 0 to disable provisioned concurrency. | `number` | `-1` | no | | [publish](#input\_publish) | Whether to publish creation/change as new Lambda Function Version. | `bool` | `false` | no | | [putin\_khuylo](#input\_putin\_khuylo) | Do you agree that Putin doesn't respect Ukrainian sovereignty and territorial integrity? More info: https://en.wikipedia.org/wiki/Putin_khuylo! | `bool` | `true` | no | +| [quiet\_archive\_local\_exec](#input\_quiet\_archive\_local\_exec) | Whether to disable archive local execution output | `bool` | `true` | no | | [recreate\_missing\_package](#input\_recreate\_missing\_package) | Whether to recreate missing Lambda package if it is missing locally or not | `bool` | `true` | no | +| [recursive\_loop](#input\_recursive\_loop) | Lambda function recursion configuration. Valid values are Allow or Terminate. | `string` | `null` | no | +| [region](#input\_region) | Region where the resource(s) will be managed. Defaults to the region set in the provider configuration | `string` | `null` | no | +| [replace\_security\_groups\_on\_destroy](#input\_replace\_security\_groups\_on\_destroy) | (Optional) When true, all security groups defined in vpc\_security\_group\_ids will be replaced with the default security group after the function is destroyed. Set the replacement\_security\_group\_ids variable to use a custom list of security groups for replacement instead. | `bool` | `null` | no | +| [replacement\_security\_group\_ids](#input\_replacement\_security\_group\_ids) | (Optional) List of security group IDs to assign to orphaned Lambda function network interfaces upon destruction. replace\_security\_groups\_on\_destroy must be set to true to use this attribute. | `list(string)` | `null` | no | | [reserved\_concurrent\_executions](#input\_reserved\_concurrent\_executions) | The amount of reserved concurrent executions for this Lambda Function. A value of 0 disables Lambda Function from being triggered and -1 removes any concurrency limitations. Defaults to Unreserved Concurrency Limits -1. | `number` | `-1` | no | | [role\_description](#input\_role\_description) | Description of IAM role to use for Lambda Function | `string` | `null` | no | | [role\_force\_detach\_policies](#input\_role\_force\_detach\_policies) | Specifies to force detaching any policies the IAM role has before destroying it. | `bool` | `true` | no | +| [role\_maximum\_session\_duration](#input\_role\_maximum\_session\_duration) | Maximum session duration, in seconds, for the IAM role | `number` | `3600` | no | | [role\_name](#input\_role\_name) | Name of IAM role to use for Lambda Function | `string` | `null` | no | | [role\_path](#input\_role\_path) | Path of IAM role to use for Lambda Function | `string` | `null` | no | | [role\_permissions\_boundary](#input\_role\_permissions\_boundary) | The ARN of the policy that is used to set the permissions boundary for the IAM role used by Lambda Function | `string` | `null` | no | @@ -827,16 +846,22 @@ No modules. | [s3\_acl](#input\_s3\_acl) | The canned ACL to apply. Valid values are private, public-read, public-read-write, aws-exec-read, authenticated-read, bucket-owner-read, and bucket-owner-full-control. Defaults to private. | `string` | `"private"` | no | | [s3\_bucket](#input\_s3\_bucket) | S3 bucket to store artifacts | `string` | `null` | no | | [s3\_existing\_package](#input\_s3\_existing\_package) | The S3 bucket object with keys bucket, key, version pointing to an existing zip-file to use | `map(string)` | `null` | no | +| [s3\_kms\_key\_id](#input\_s3\_kms\_key\_id) | Specifies a custom KMS key to use for S3 object encryption. | `string` | `null` | no | +| [s3\_object\_override\_default\_tags](#input\_s3\_object\_override\_default\_tags) | Whether to override the default\_tags from provider? NB: S3 objects support a maximum of 10 tags. | `bool` | `false` | no | | [s3\_object\_storage\_class](#input\_s3\_object\_storage\_class) | Specifies the desired Storage Class for the artifact uploaded to S3. Can be either STANDARD, REDUCED\_REDUNDANCY, ONEZONE\_IA, INTELLIGENT\_TIERING, or STANDARD\_IA. | `string` | `"ONEZONE_IA"` | no | | [s3\_object\_tags](#input\_s3\_object\_tags) | A map of tags to assign to S3 bucket object. | `map(string)` | `{}` | no | | [s3\_object\_tags\_only](#input\_s3\_object\_tags\_only) | Set to true to not merge tags with s3\_object\_tags. Useful to avoid breaching S3 Object 10 tag limit. | `bool` | `false` | no | | [s3\_prefix](#input\_s3\_prefix) | Directory name where artifacts should be stored in the S3 bucket. If unset, the path from `artifacts_dir` is used | `string` | `null` | no | | [s3\_server\_side\_encryption](#input\_s3\_server\_side\_encryption) | Specifies server-side encryption of the object in S3. Valid values are "AES256" and "aws:kms". | `string` | `null` | no | +| [skip\_destroy](#input\_skip\_destroy) | Set to true if you do not wish the function to be deleted at destroy time, and instead just remove the function from the Terraform state. Useful for Lambda@Edge functions attached to CloudFront distributions. | `bool` | `null` | no | +| [snap\_start](#input\_snap\_start) | (Optional) Snap start settings for low-latency startups | `bool` | `false` | no | | [source\_path](#input\_source\_path) | The absolute path to a local file or directory containing your Lambda source code | `any` | `null` | no | | [store\_on\_s3](#input\_store\_on\_s3) | Whether to store produced artifacts on S3 or locally. | `bool` | `false` | no | | [tags](#input\_tags) | A map of tags to assign to resources. | `map(string)` | `{}` | no | | [timeout](#input\_timeout) | The amount of time your Lambda Function has to run in seconds. | `number` | `3` | no | +| [timeouts](#input\_timeouts) | Define maximum timeout for creating, updating, and deleting Lambda Function resources | `map(string)` | `{}` | no | | [tracing\_mode](#input\_tracing\_mode) | Tracing mode of the Lambda Function. Valid value can be either PassThrough or Active. | `string` | `null` | no | +| [trigger\_on\_package\_timestamp](#input\_trigger\_on\_package\_timestamp) | Whether to recreate the Lambda package if the timestamp changes | `bool` | `true` | no | | [trusted\_entities](#input\_trusted\_entities) | List of additional trusted entities for assuming Lambda Function role (trust relationship) | `any` | `[]` | no | | [use\_existing\_cloudwatch\_log\_group](#input\_use\_existing\_cloudwatch\_log\_group) | Whether to use an existing CloudWatch log group or create new | `bool` | `false` | no | | [vpc\_security\_group\_ids](#input\_vpc\_security\_group\_ids) | List of security group ids when Lambda Function should run in the VPC. | `list(string)` | `null` | no | @@ -848,6 +873,7 @@ No modules. |------|-------------| | [lambda\_cloudwatch\_log\_group\_arn](#output\_lambda\_cloudwatch\_log\_group\_arn) | The ARN of the Cloudwatch Log Group | | [lambda\_cloudwatch\_log\_group\_name](#output\_lambda\_cloudwatch\_log\_group\_name) | The name of the Cloudwatch Log Group | +| [lambda\_event\_source\_mapping\_arn](#output\_lambda\_event\_source\_mapping\_arn) | The event source mapping ARN | | [lambda\_event\_source\_mapping\_function\_arn](#output\_lambda\_event\_source\_mapping\_function\_arn) | The the ARN of the Lambda function the event source mapping is sending events to | | [lambda\_event\_source\_mapping\_state](#output\_lambda\_event\_source\_mapping\_state) | The state of the event source mapping | | [lambda\_event\_source\_mapping\_state\_transition\_reason](#output\_lambda\_event\_source\_mapping\_state\_transition\_reason) | The reason the event source mapping is in its current state | @@ -859,6 +885,7 @@ No modules. | [lambda\_function\_last\_modified](#output\_lambda\_function\_last\_modified) | The date Lambda Function resource was last modified | | [lambda\_function\_name](#output\_lambda\_function\_name) | The name of the Lambda Function | | [lambda\_function\_qualified\_arn](#output\_lambda\_function\_qualified\_arn) | The ARN identifying your Lambda Function Version | +| [lambda\_function\_qualified\_invoke\_arn](#output\_lambda\_function\_qualified\_invoke\_arn) | The Invoke ARN identifying your Lambda Function Version | | [lambda\_function\_signing\_job\_arn](#output\_lambda\_function\_signing\_job\_arn) | ARN of the signing job | | [lambda\_function\_signing\_profile\_version\_arn](#output\_lambda\_function\_signing\_profile\_version\_arn) | ARN of the signing profile version | | [lambda\_function\_source\_code\_hash](#output\_lambda\_function\_source\_code\_hash) | Base64-encoded representation of raw SHA-256 sum of the zip file | @@ -876,7 +903,7 @@ No modules. | [lambda\_role\_unique\_id](#output\_lambda\_role\_unique\_id) | The unique id of the IAM role created for the Lambda Function | | [local\_filename](#output\_local\_filename) | The filename of zip archive deployed (if deployment was from local) | | [s3\_object](#output\_s3\_object) | The map with S3 object data of zip archive deployed (if deployment was from S3) | - + ## Development diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..f417c0ad --- /dev/null +++ b/examples/README.md @@ -0,0 +1,8 @@ +# Examples + +Please note - the examples provided serve two primary means: + +1. Show users working examples of the various ways in which the module can be configured and features supported +2. A means of testing/validating module changes + +Please do not mistake the examples provided as "best practices". It is up to users to consult the AWS service documentation for best practices, usage recommendations, etc. diff --git a/examples/alias/README.md b/examples/alias/README.md index 9f143789..6bcb2530 100644 --- a/examples/alias/README.md +++ b/examples/alias/README.md @@ -14,19 +14,20 @@ $ terraform apply Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. - + ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 3.19 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | | [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| +| [aws](#provider\_aws) | >= 6.0 | | [random](#provider\_random) | >= 2.0 | ## Modules @@ -44,6 +45,7 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Type | |------|------| | [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | +| [aws_organizations_organization.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/organizations_organization) | data source | ## Inputs @@ -81,4 +83,4 @@ No inputs. | [lambda\_role\_name](#output\_lambda\_role\_name) | The name of the IAM role created for the Lambda Function | | [local\_filename](#output\_local\_filename) | The filename of zip archive deployed (if deployment was from local) | | [s3\_object](#output\_s3\_object) | The map with S3 object data of zip archive deployed (if deployment was from S3) | - + diff --git a/examples/alias/main.tf b/examples/alias/main.tf index 15cf4e9d..5fed7678 100644 --- a/examples/alias/main.tf +++ b/examples/alias/main.tf @@ -2,13 +2,13 @@ provider "aws" { region = "eu-west-1" # Make it faster by skipping something - skip_get_ec2_platforms = true skip_metadata_api_check = true skip_region_validation = true skip_credentials_validation = true - skip_requesting_account_id = true } +data "aws_organizations_organization" "this" {} + resource "random_pet" "this" { length = 2 } @@ -25,10 +25,10 @@ module "lambda_function" { function_name = "${random_pet.this.id}-lambda" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" publish = true - source_path = "${path.module}/../fixtures/python3.8-app1" + source_path = "${path.module}/../fixtures/python-app1" hash_extra = "yo" create_async_event_config = true @@ -73,12 +73,17 @@ module "alias_no_refresh" { event_source_mapping = { sqs = { - service = "sqs" - event_source_arn = module.sqs_events.sqs_queue_arn + service = "sqs" + event_source_arn = module.sqs_events.sqs_queue_arn + maximum_concurrency = 10 } } allowed_triggers = { + Config = { + principal = "config.amazonaws.com" + principal_org_id = data.aws_organizations_organization.this.id + } AnotherAPIGatewayAny = { # keys should be unique service = "apigateway" source_arn = "arn:aws:execute-api:eu-west-1:135367859851:abcdedfgse/*/*/*" @@ -118,6 +123,10 @@ module "alias_existing" { } allowed_triggers = { + Config = { + principal = "config.amazonaws.com" + principal_org_id = data.aws_organizations_organization.this.id + } ThirdAPIGatewayAny = { service = "apigateway" source_arn = "arn:aws:execute-api:eu-west-1:135367859851:aqnku8akd0/*/*/*" diff --git a/examples/alias/versions.tf b/examples/alias/versions.tf index 6f0e3af3..d2f4f3e8 100644 --- a/examples/alias/versions.tf +++ b/examples/alias/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 3.19" + version = ">= 6.0" } random = { source = "hashicorp/random" diff --git a/examples/async/README.md b/examples/async/README.md index 00c057be..3bca0e68 100644 --- a/examples/async/README.md +++ b/examples/async/README.md @@ -14,20 +14,20 @@ $ terraform apply Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. - + ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 3.61 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | | [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 3.61 | +| [aws](#provider\_aws) | >= 6.0 | | [random](#provider\_random) | >= 2.0 | ## Modules @@ -72,4 +72,4 @@ No inputs. | [lambda\_role\_name](#output\_lambda\_role\_name) | The name of the IAM role created for the Lambda Function | | [local\_filename](#output\_local\_filename) | The filename of zip archive deployed (if deployment was from local) | | [s3\_object](#output\_s3\_object) | The map with S3 object data of zip archive deployed (if deployment was from S3) | - + diff --git a/examples/async/main.tf b/examples/async/main.tf index c2755359..ff1b361c 100644 --- a/examples/async/main.tf +++ b/examples/async/main.tf @@ -2,11 +2,9 @@ provider "aws" { region = "eu-west-1" # Make it faster by skipping something - skip_get_ec2_platforms = true skip_metadata_api_check = true skip_region_validation = true skip_credentials_validation = true - skip_requesting_account_id = true } resource "random_pet" "this" { @@ -18,10 +16,10 @@ module "lambda_function" { function_name = "${random_pet.this.id}-lambda-async" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" architectures = ["arm64"] - source_path = "${path.module}/../fixtures/python3.8-app1" + source_path = "${path.module}/../fixtures/python-app1" create_async_event_config = true attach_async_event_policy = true diff --git a/examples/async/versions.tf b/examples/async/versions.tf index 2a88692a..d2f4f3e8 100644 --- a/examples/async/versions.tf +++ b/examples/async/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 3.61" + version = ">= 6.0" } random = { source = "hashicorp/random" diff --git a/examples/build-package/README.md b/examples/build-package/README.md index ac20cd70..1b8701cd 100644 --- a/examples/build-package/README.md +++ b/examples/build-package/README.md @@ -2,6 +2,15 @@ Configuration in this directory creates deployment packages in a variety of combinations. +This example demonstrates various packaging scenarios including: +- Python packages with pip requirements +- Poetry-based Python packages +- Node.js packages with npm +- Docker-based builds +- Quiet packaging - suppressing Poetry/pip/npm output during builds using `quiet_archive_local_exec = true` + +Look into [Runtimes Examples](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/runtimes) for more ways to build and deploy AWS Lambda Functions using supported runtimes (Rust, Go, Java). + ## Usage To run this example you need to execute: @@ -14,13 +23,13 @@ $ terraform apply Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. - + ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 3.19 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | | [random](#requirement\_random) | >= 2.0 | ## Providers @@ -37,16 +46,23 @@ Note that this example may create resources which cost money. Run `terraform des | [lambda\_layer](#module\_lambda\_layer) | ../../ | n/a | | [lambda\_layer\_pip\_requirements](#module\_lambda\_layer\_pip\_requirements) | ../.. | n/a | | [lambda\_layer\_poetry](#module\_lambda\_layer\_poetry) | ../../ | n/a | +| [npm\_package\_with\_commands\_and\_patterns](#module\_npm\_package\_with\_commands\_and\_patterns) | ../../ | n/a | | [package\_dir](#module\_package\_dir) | ../../ | n/a | | [package\_dir\_pip\_dir](#module\_package\_dir\_pip\_dir) | ../../ | n/a | | [package\_dir\_poetry](#module\_package\_dir\_poetry) | ../../ | n/a | +| [package\_dir\_poetry\_no\_docker](#module\_package\_dir\_poetry\_no\_docker) | ../../ | n/a | +| [package\_dir\_poetry\_quiet](#module\_package\_dir\_poetry\_quiet) | ../../ | n/a | | [package\_dir\_with\_npm\_install](#module\_package\_dir\_with\_npm\_install) | ../../ | n/a | +| [package\_dir\_with\_npm\_install\_lock\_file](#module\_package\_dir\_with\_npm\_install\_lock\_file) | ../../ | n/a | | [package\_dir\_without\_npm\_install](#module\_package\_dir\_without\_npm\_install) | ../../ | n/a | | [package\_dir\_without\_pip\_install](#module\_package\_dir\_without\_pip\_install) | ../../ | n/a | | [package\_file](#module\_package\_file) | ../../ | n/a | | [package\_file\_with\_pip\_requirements](#module\_package\_file\_with\_pip\_requirements) | ../../ | n/a | +| [package\_src\_poetry](#module\_package\_src\_poetry) | ../../ | n/a | +| [package\_src\_poetry2](#module\_package\_src\_poetry2) | ../../ | n/a | | [package\_with\_commands\_and\_patterns](#module\_package\_with\_commands\_and\_patterns) | ../../ | n/a | | [package\_with\_docker](#module\_package\_with\_docker) | ../../ | n/a | +| [package\_with\_npm\_lock\_in\_docker](#module\_package\_with\_npm\_lock\_in\_docker) | ../../ | n/a | | [package\_with\_npm\_requirements\_in\_docker](#module\_package\_with\_npm\_requirements\_in\_docker) | ../../ | n/a | | [package\_with\_patterns](#module\_package\_with\_patterns) | ../../ | n/a | | [package\_with\_pip\_requirements\_in\_docker](#module\_package\_with\_pip\_requirements\_in\_docker) | ../../ | n/a | @@ -65,4 +81,4 @@ No inputs. ## Outputs No outputs. - + diff --git a/examples/build-package/main.tf b/examples/build-package/main.tf index 5ce7f9df..ec843b68 100644 --- a/examples/build-package/main.tf +++ b/examples/build-package/main.tf @@ -2,11 +2,9 @@ provider "aws" { region = "eu-west-1" # Make it faster by skipping something - skip_get_ec2_platforms = true skip_metadata_api_check = true skip_region_validation = true skip_credentials_validation = true - skip_requesting_account_id = true } resource "random_pet" "this" { @@ -24,8 +22,8 @@ module "package_dir" { create_function = false build_in_docker = true - runtime = "python3.8" - source_path = "${path.module}/../fixtures/python3.8-app1" + runtime = "python3.12" + source_path = "${path.module}/../fixtures/python-app1" artifacts_dir = "${path.root}/builds/package_dir/" } @@ -36,45 +34,119 @@ module "package_dir_pip_dir" { create_function = false build_in_docker = true - runtime = "python3.8" + runtime = "python3.12" source_path = [{ - path = "${path.module}/../fixtures/python3.8-app1" + path = "${path.module}/../fixtures/python-app1" pip_tmp_dir = "${path.cwd}/../fixtures" - pip_requirements = "${path.module}/../fixtures/python3.8-app1/requirements.txt" + pip_requirements = "${path.module}/../fixtures/python-app1/requirements.txt" }] artifacts_dir = "${path.root}/builds/package_dir_pip_dir/" } -# Create zip-archive of a single directory where "poetry export" & "pip install --no-deps" will also be executed +# Create zip-archive of a single directory where "poetry export" & "pip install --no-deps" will also be executed (using docker) module "package_dir_poetry" { source = "../../" create_function = false build_in_docker = true - runtime = "python3.9" - docker_image = "build-python3.9-poetry" - docker_file = "${path.module}/../fixtures/python3.9-app-poetry/docker/Dockerfile" + runtime = "python3.12" + docker_image = "build-python-poetry" + docker_file = "${path.module}/../fixtures/python-app-poetry/docker/Dockerfile" source_path = [ { - path = "${path.module}/../fixtures/python3.9-app-poetry" + path = "${path.module}/../fixtures/python-app-poetry" poetry_install = true } ] artifacts_dir = "${path.root}/builds/package_dir_poetry/" } +# Create zip-archive of a src directory where "poetry export" & "pip install --no-deps" will also be executed (using docker) +module "package_src_poetry" { + source = "../../" + + create_function = false + + build_in_docker = true + runtime = "python3.12" + docker_image = "build-python-poetry" + docker_file = "${path.module}/../fixtures/python-app-src-poetry/docker/Dockerfile" + + source_path = [ + "${path.module}/../fixtures/python-app-src-poetry/src", + { + path = "${path.module}/../fixtures/python-app-src-poetry/pyproject.toml" + poetry_install = true + } + ] + artifacts_dir = "${path.root}/builds/package_src_poetry/" +} + +# Create zip-archive of a src directory where "poetry export" & "pip install --no-deps" will also be executed (using docker) +module "package_src_poetry2" { + source = "../../" + + create_function = false + + build_in_docker = true + runtime = "python3.12" + docker_image = "build-python-poetry" + docker_file = "${path.module}/../fixtures/python-app-src-poetry/docker/Dockerfile" + + source_path = [ + "${path.module}/../fixtures/python-app-src-poetry/src", + "${path.module}/../fixtures/python-app-src-poetry/pyproject.toml" + ] + artifacts_dir = "${path.root}/builds/package_src_poetry2/" +} + +# Create zip-archive of a single directory where "poetry export" & "pip install --no-deps" will also be executed (not using docker) +module "package_dir_poetry_no_docker" { + source = "../../" + + create_function = false + + runtime = "python3.12" + + source_path = [ + { + path = "${path.module}/../fixtures/python-app-poetry" + poetry_install = true + } + ] + artifacts_dir = "${path.root}/builds/package_dir_poetry/" +} + +# Create zip-archive with Poetry dependencies and demonstrate quiet packaging output +module "package_dir_poetry_quiet" { + source = "../../" + + create_function = false + + runtime = "python3.12" + + source_path = [ + { + path = "${path.module}/../fixtures/python-app-poetry" + poetry_install = true + } + ] + artifacts_dir = "${path.root}/builds/package_dir_poetry_quiet/" + quiet_archive_local_exec = true # Suppress Poetry/pip output during packaging +} + # Create zip-archive of a single directory without running "pip install" (which is default for python runtime) module "package_dir_without_pip_install" { source = "../../" create_function = false - runtime = "python3.8" + runtime = "python3.12" source_path = [ { - path = "${path.module}/../fixtures/python3.8-app1" + path = "${path.module}/../fixtures/python-app1" pip_requirements = false # pip_requirements = true # Will run "pip install" with default requirements.txt } @@ -87,8 +159,8 @@ module "package_file" { create_function = false - runtime = "python3.8" - source_path = "${path.module}/../fixtures/python3.8-app1/index.py" + runtime = "python3.12" + source_path = "${path.module}/../fixtures/python-app1/index.py" } # Create zip-archive which contains: @@ -99,11 +171,11 @@ module "package_file_with_pip_requirements" { create_function = false - runtime = "python3.8" + runtime = "python3.12" source_path = [ - "${path.module}/../fixtures/python3.8-app1/index.py", + "${path.module}/../fixtures/python-app1/index.py", { - pip_requirements = "${path.module}/../fixtures/python3.8-app1/requirements.txt" + pip_requirements = "${path.module}/../fixtures/python-app1/requirements.txt" prefix_in_zip = "vendor" } ] @@ -119,12 +191,12 @@ module "package_with_pip_requirements_in_docker" { create_function = false - runtime = "python3.8" + runtime = "python3.12" source_path = [ - "${path.module}/../fixtures/python3.8-app1/index.py", - "${path.module}/../fixtures/python3.8-app1/dir1/dir2", + "${path.module}/../fixtures/python-app1/index.py", + "${path.module}/../fixtures/python-app1/dir1/dir2", { - pip_requirements = "${path.module}/../fixtures/python3.8-app1/requirements.txt" + pip_requirements = "${path.module}/../fixtures/python-app1/requirements.txt" } ] @@ -142,12 +214,12 @@ module "package_with_pip_requirements_in_docker_overriding_entrypoint" { create_function = false - runtime = "python3.8" + runtime = "python3.12" source_path = [ - "${path.module}/../fixtures/python3.8-app1/index.py", - "${path.module}/../fixtures/python3.8-app1/dir1/dir2", + "${path.module}/../fixtures/python-app1/index.py", + "${path.module}/../fixtures/python-app1/dir1/dir2", { - pip_requirements = "${path.module}/../fixtures/python3.8-app1/requirements.txt" + pip_requirements = "${path.module}/../fixtures/python-app1/requirements.txt" } ] hash_extra = "package_with_pip_requirements_in_docker_overriding_entrypoint" @@ -155,7 +227,7 @@ module "package_with_pip_requirements_in_docker_overriding_entrypoint" { build_in_docker = true docker_additional_options = [ "-e", "MY_ENV_VAR='My environment variable value'", - "-v", "${abspath(path.module)}/../fixtures/python3.8-app1/docker/entrypoint.sh:/entrypoint/entrypoint.sh:ro", + "-v", "${abspath(path.module)}/../fixtures/python-app1/docker/entrypoint.sh:/entrypoint/entrypoint.sh:ro", ] docker_entrypoint = "/entrypoint/entrypoint.sh" } @@ -171,14 +243,14 @@ module "package_with_commands_and_patterns" { create_function = false - runtime = "python3.8" + runtime = "python3.12" source_path = [ { - path = "${path.module}/../fixtures/python3.8-app1" + path = "${path.module}/../fixtures/python-app1" commands = [ ":zip", "cd `mktemp -d`", - "pip install --target=. -r ${abspath(path.module)}/../fixtures/python3.8-app1/requirements.txt", + "pip install --target=. -r ${abspath(path.module)}/../fixtures/python-app1/requirements.txt", ":zip . vendor/", ] patterns = [ @@ -190,6 +262,31 @@ module "package_with_commands_and_patterns" { ] } +# Some use cases might require the production packages are deployed while maintaining local node_modules folder +# This example saves the node_modules folder by moving it to an ignored directory +# After the zip file is created with production node_modules, the dev node_modules folder is restored +module "npm_package_with_commands_and_patterns" { + source = "../../" + + create_function = false + + runtime = "nodejs18.x" + source_path = [ + { + path = "${path.module}/../fixtures/node-app" + commands = [ + "[ ! -d node_modules ] || mv node_modules node_modules_temp", + "npm install --production", + ":zip", + "rm -rf node_modules", + "[ ! -d node_modules_temp ] || mv node_modules_temp node_modules", + ] + patterns = [ + "!node_modules_temp/.*" + ] + } + ] +} # Create zip-archive with various sources and patterns. # Note, that it is possible to write comments in patterns. module "package_with_patterns" { @@ -197,14 +294,14 @@ module "package_with_patterns" { create_function = false - runtime = "python3.8" + runtime = "python3.12" source_path = [ { - pip_requirements = "${path.module}/../fixtures/python3.8-app1/requirements.txt" + pip_requirements = "${path.module}/../fixtures/python-app1/requirements.txt" }, - "${path.module}/../fixtures/python3.8-app1/index.py", + "${path.module}/../fixtures/python-app1/index.py", { - path = "${path.module}/../fixtures/python3.8-app1/index.py" + path = "${path.module}/../fixtures/python-app1/index.py" patterns = < + ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 4.9 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | | [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.9 | +| [aws](#provider\_aws) | >= 6.0 | | [random](#provider\_random) | >= 2.0 | ## Modules @@ -35,7 +35,7 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Source | Version | |------|--------|---------| | [lambda](#module\_lambda) | ../../ | n/a | -| [s3\_bucket](#module\_s3\_bucket) | terraform-aws-modules/s3-bucket/aws | ~> 3.0 | +| [s3\_bucket](#module\_s3\_bucket) | terraform-aws-modules/s3-bucket/aws | ~> 5.0 | ## Resources @@ -59,4 +59,4 @@ No inputs. | [lambda\_function\_invoke\_arn](#output\_lambda\_function\_invoke\_arn) | The Invoke ARN of the Lambda Function | | [lambda\_function\_signing\_job\_arn](#output\_lambda\_function\_signing\_job\_arn) | ARN of the signing job | | [lambda\_function\_signing\_profile\_version\_arn](#output\_lambda\_function\_signing\_profile\_version\_arn) | ARN of the signing profile version | - + diff --git a/examples/code-signing/main.tf b/examples/code-signing/main.tf index 0ba5f120..8b8e9e3e 100644 --- a/examples/code-signing/main.tf +++ b/examples/code-signing/main.tf @@ -2,11 +2,9 @@ provider "aws" { region = "eu-west-1" # Make it faster by skipping something - skip_get_ec2_platforms = true skip_metadata_api_check = true skip_region_validation = true skip_credentials_validation = true - skip_requesting_account_id = true } ################################################################################ @@ -18,7 +16,7 @@ module "lambda" { function_name = random_pet.this.id handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" code_signing_config_arn = aws_lambda_code_signing_config.this.arn create_package = false @@ -35,7 +33,7 @@ module "lambda" { resource "aws_s3_object" "unsigned" { bucket = module.s3_bucket.s3_bucket_id key = "unsigned/existing_package.zip" - source = "${path.module}/../fixtures/python3.8-zip/existing_package.zip" + source = "${path.module}/../fixtures/python-zip/existing_package.zip" # Making sure that S3 versioning configuration is propagated properly depends_on = [ @@ -95,7 +93,7 @@ resource "random_pet" "this" { module "s3_bucket" { source = "terraform-aws-modules/s3-bucket/aws" - version = "~> 3.0" + version = "~> 5.0" bucket_prefix = "${random_pet.this.id}-" force_destroy = true diff --git a/examples/code-signing/versions.tf b/examples/code-signing/versions.tf index 629d346a..d2f4f3e8 100644 --- a/examples/code-signing/versions.tf +++ b/examples/code-signing/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.9" + version = ">= 6.0" } random = { source = "hashicorp/random" diff --git a/examples/complete/README.md b/examples/complete/README.md index c242b952..552ea09c 100644 --- a/examples/complete/README.md +++ b/examples/complete/README.md @@ -15,20 +15,20 @@ $ terraform apply Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. - + ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 4.9 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | | [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.9 | +| [aws](#provider\_aws) | >= 6.0 | | [random](#provider\_random) | >= 2.0 | ## Modules @@ -40,21 +40,26 @@ Note that this example may create resources which cost money. Run `terraform des | [lambda\_function](#module\_lambda\_function) | ../../ | n/a | | [lambda\_function\_existing\_package\_local](#module\_lambda\_function\_existing\_package\_local) | ../../ | n/a | | [lambda\_function\_for\_each](#module\_lambda\_function\_for\_each) | ../../ | n/a | +| [lambda\_function\_no\_create\_log\_group\_permission](#module\_lambda\_function\_no\_create\_log\_group\_permission) | ../../ | n/a | +| [lambda\_function\_with\_custom\_auto\_log\_group](#module\_lambda\_function\_with\_custom\_auto\_log\_group) | ../../ | n/a | +| [lambda\_function\_with\_custom\_log\_group](#module\_lambda\_function\_with\_custom\_log\_group) | ../../ | n/a | | [lambda\_function\_with\_package\_deploying\_externally](#module\_lambda\_function\_with\_package\_deploying\_externally) | ../../ | n/a | | [lambda\_layer\_local](#module\_lambda\_layer\_local) | ../../ | n/a | | [lambda\_layer\_s3](#module\_lambda\_layer\_s3) | ../../ | n/a | | [lambda\_layer\_with\_package\_deploying\_externally](#module\_lambda\_layer\_with\_package\_deploying\_externally) | ../../ | n/a | | [lambda\_with\_mixed\_trusted\_entities](#module\_lambda\_with\_mixed\_trusted\_entities) | ../../ | n/a | | [lambda\_with\_provisioned\_concurrency](#module\_lambda\_with\_provisioned\_concurrency) | ../../ | n/a | -| [s3\_bucket](#module\_s3\_bucket) | terraform-aws-modules/s3-bucket/aws | ~> 3.0 | +| [s3\_bucket](#module\_s3\_bucket) | terraform-aws-modules/s3-bucket/aws | ~> 5.0 | ## Resources | Name | Type | |------|------| +| [aws_cloudwatch_log_group.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | | [aws_sqs_queue.dlq](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource | | [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_organizations_organization.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/organizations_organization) | data source | ## Inputs @@ -72,6 +77,7 @@ No inputs. | [lambda\_function\_last\_modified](#output\_lambda\_function\_last\_modified) | The date Lambda Function resource was last modified | | [lambda\_function\_name](#output\_lambda\_function\_name) | The name of the Lambda Function | | [lambda\_function\_qualified\_arn](#output\_lambda\_function\_qualified\_arn) | The ARN identifying your Lambda Function Version | +| [lambda\_function\_qualified\_invoke\_arn](#output\_lambda\_function\_qualified\_invoke\_arn) | The Invoke ARN identifying your Lambda Function Version | | [lambda\_function\_source\_code\_hash](#output\_lambda\_function\_source\_code\_hash) | Base64-encoded representation of raw SHA-256 sum of the zip file | | [lambda\_function\_source\_code\_size](#output\_lambda\_function\_source\_code\_size) | The size in bytes of the function .zip file | | [lambda\_function\_url](#output\_lambda\_function\_url) | The URL of the Lambda Function URL | @@ -86,4 +92,4 @@ No inputs. | [lambda\_role\_name](#output\_lambda\_role\_name) | The name of the IAM role created for the Lambda Function | | [local\_filename](#output\_local\_filename) | The filename of zip archive deployed (if deployment was from local) | | [s3\_object](#output\_s3\_object) | The map with S3 object data of zip archive deployed (if deployment was from S3) | - + diff --git a/examples/complete/main.tf b/examples/complete/main.tf index a6ddd940..356d9f3e 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -2,15 +2,15 @@ provider "aws" { region = "eu-west-1" # Make it faster by skipping something - skip_get_ec2_platforms = true skip_metadata_api_check = true skip_region_validation = true skip_credentials_validation = true - skip_requesting_account_id = true } data "aws_caller_identity" "current" {} +data "aws_organizations_organization" "this" {} + #################################################### # Lambda Function (building locally, storing on S3, # set allowed triggers, set policies) @@ -22,17 +22,24 @@ module "lambda_function" { function_name = "${random_pet.this.id}-lambda1" description = "My awesome lambda function" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" ephemeral_storage_size = 10240 architectures = ["x86_64"] publish = true + # recursive_loop = "Allow" - source_path = "${path.module}/../fixtures/python3.8-app1" + source_path = "${path.module}/../fixtures/python-app1" store_on_s3 = true s3_bucket = module.s3_bucket.s3_bucket_id s3_prefix = "lambda-builds/" + s3_object_override_default_tags = true + s3_object_tags = { + S3ObjectName = "lambda1" + Override = "true" + } + artifacts_dir = "${path.root}/.terraform/lambda-builds/" layers = [ @@ -45,13 +52,18 @@ module "lambda_function" { Serverless = "Terraform" } - role_path = "/tf-managed/" - policy_path = "/tf-managed/" + cloudwatch_logs_log_group_class = "INFREQUENT_ACCESS" + + role_path = "/tf-managed/" attach_dead_letter_policy = true dead_letter_target_arn = aws_sqs_queue.dlq.arn allowed_triggers = { + Config = { + principal = "config.amazonaws.com" + principal_org_id = data.aws_organizations_organization.this.id + } APIGatewayAny = { service = "apigateway" source_arn = "arn:aws:execute-api:eu-west-1:${data.aws_caller_identity.current.account_id}:aqnku8akd0/*/*/*" @@ -79,6 +91,7 @@ module "lambda_function" { expose_headers = ["keep-alive", "date"] max_age = 86400 } + invoke_mode = "RESPONSE_STREAM" ###################### # Additional policies @@ -160,6 +173,16 @@ module "lambda_function" { } } + timeouts = { + create = "20m" + update = "20m" + delete = "20m" + } + + function_tags = { + Language = "python" + } + tags = { Module = "lambda1" } @@ -175,11 +198,11 @@ module "lambda_function_existing_package_local" { function_name = "${random_pet.this.id}-lambda-existing-package-local" description = "My awesome lambda function" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" publish = true create_package = false - local_existing_package = "${path.module}/../fixtures/python3.8-zip/existing_package.zip" + local_existing_package = "${path.module}/../fixtures/python-zip/existing_package.zip" # s3_existing_package = { # bucket = "humane-bear-bucket" # key = "builds/506df8bef5a4fb01883cce3673c9ff0ed88fb52e8583410e0cca7980a72211a0.zip" @@ -203,10 +226,10 @@ module "lambda_layer_local" { layer_name = "${random_pet.this.id}-layer-local" description = "My amazing lambda layer (deployed from local)" - compatible_runtimes = ["python3.8"] + compatible_runtimes = ["python3.12"] compatible_architectures = ["arm64"] - source_path = "${path.module}/../fixtures/python3.8-app1" + source_path = "${path.module}/../fixtures/python-app1" } #################################################### @@ -221,10 +244,10 @@ module "lambda_layer_with_package_deploying_externally" { layer_name = "${random_pet.this.id}-layer-local" description = "My amazing lambda layer (deployed from local)" - compatible_runtimes = ["python3.8"] + compatible_runtimes = ["python3.12"] create_package = false - local_existing_package = "../fixtures/python3.8-zip/existing_package.zip" + local_existing_package = "../fixtures/python-zip/existing_package.zip" ignore_source_code_hash = true } @@ -240,9 +263,9 @@ module "lambda_layer_s3" { layer_name = "${random_pet.this.id}-layer-s3" description = "My amazing lambda layer (deployed from S3)" - compatible_runtimes = ["python3.8"] + compatible_runtimes = ["python3.12"] - source_path = "${path.module}/../fixtures/python3.8-app1" + source_path = "${path.module}/../fixtures/python-app1" store_on_s3 = true s3_bucket = module.s3_bucket.s3_bucket_id @@ -260,9 +283,9 @@ module "lambda_at_edge" { function_name = "${random_pet.this.id}-lambda-at-edge" description = "My awesome lambda@edge function" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" - source_path = "${path.module}/../fixtures/python3.8-app1" + source_path = "${path.module}/../fixtures/python-app1" hash_extra = "this string should be included in hash function to produce different filename for the same source" # this is also a build trigger if this changes tags = { @@ -279,9 +302,9 @@ module "lambda_with_provisioned_concurrency" { function_name = "${random_pet.this.id}-lambda-provisioned" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" - source_path = "${path.module}/../fixtures/python3.8-app1" + source_path = "${path.module}/../fixtures/python-app1" publish = true hash_extra = "hash-extra-lambda-provisioned" @@ -298,9 +321,9 @@ module "lambda_with_mixed_trusted_entities" { function_name = "${random_pet.this.id}-lambda-mixed-trusted-entities" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" - source_path = "${path.module}/../fixtures/python3.8-app1" + source_path = "${path.module}/../fixtures/python-app1" trusted_entities = [ "appsync.amazonaws.com", @@ -329,14 +352,16 @@ module "lambda_function_for_each" { for_each = toset(["dev", "staging", "prod"]) + region = "us-east-1" + function_name = "my-${each.value}" description = "My awesome lambda function" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" publish = true create_package = false - local_existing_package = "${path.module}/../fixtures/python3.8-zip/existing_package.zip" + local_existing_package = "${path.module}/../fixtures/python-zip/existing_package.zip" } #################################################### @@ -349,14 +374,73 @@ module "lambda_function_with_package_deploying_externally" { function_name = "${random_pet.this.id}-lambda-with-package-deploying-externally" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" create_package = false - local_existing_package = "../fixtures/python3.8-zip/existing_package.zip" + local_existing_package = "../fixtures/python-zip/existing_package.zip" ignore_source_code_hash = true } +#################################################### +# Lambda Function no create log group permission +#################################################### + +module "lambda_function_no_create_log_group_permission" { + source = "../../" + + function_name = "${random_pet.this.id}-lambda-no-create-log-group-permission" + handler = "index.lambda_handler" + runtime = "python3.12" + + create_package = false + local_existing_package = "../fixtures/python-zip/existing_package.zip" + + attach_create_log_group_permission = false +} + +#################################################### +# Lambda Function with custom log group (existing) +#################################################### + +module "lambda_function_with_custom_log_group" { + source = "../../" + + function_name = "${random_pet.this.id}-lambda-with-custom-log-group" + handler = "index.lambda_handler" + runtime = "python3.12" + + create_package = false + local_existing_package = "../fixtures/python-zip/existing_package.zip" + + use_existing_cloudwatch_log_group = true + + logging_log_group = aws_cloudwatch_log_group.custom.name + logging_log_format = "JSON" + logging_application_log_level = "INFO" + logging_system_log_level = "DEBUG" +} + +#################################################################### +# Lambda Function with custom log group (automatically provisioned) +#################################################################### + +module "lambda_function_with_custom_auto_log_group" { + source = "../../" + + function_name = "${random_pet.this.id}-lambda-with-custom-auto-log-group" + handler = "index.lambda_handler" + runtime = "python3.12" + + create_package = false + local_existing_package = "../fixtures/python-zip/existing_package.zip" + + logging_log_group = "/example-auto/${random_pet.this.id}" + logging_log_format = "JSON" + logging_application_log_level = "INFO" + logging_system_log_level = "DEBUG" +} + ########### # Disabled ########### @@ -377,7 +461,7 @@ resource "random_pet" "this" { module "s3_bucket" { source = "terraform-aws-modules/s3-bucket/aws" - version = "~> 3.0" + version = "~> 5.0" bucket_prefix = "${random_pet.this.id}-" force_destroy = true @@ -396,3 +480,8 @@ module "s3_bucket" { resource "aws_sqs_queue" "dlq" { name = random_pet.this.id } + +resource "aws_cloudwatch_log_group" "custom" { + name = "/example/${random_pet.this.id}" + retention_in_days = 1 +} diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf index 5ac3d8ec..77c78702 100644 --- a/examples/complete/outputs.tf +++ b/examples/complete/outputs.tf @@ -24,6 +24,11 @@ output "lambda_function_qualified_arn" { value = module.lambda_function.lambda_function_qualified_arn } +output "lambda_function_qualified_invoke_arn" { + description = "The Invoke ARN identifying your Lambda Function Version" + value = module.lambda_function.lambda_function_qualified_invoke_arn +} + output "lambda_function_version" { description = "Latest published version of Lambda Function" value = module.lambda_function.lambda_function_version diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf index 629d346a..d2f4f3e8 100644 --- a/examples/complete/versions.tf +++ b/examples/complete/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.9" + version = ">= 6.0" } random = { source = "hashicorp/random" diff --git a/examples/container-image/README.md b/examples/container-image/README.md index 784cb8eb..8c2bf290 100644 --- a/examples/container-image/README.md +++ b/examples/container-image/README.md @@ -1,6 +1,6 @@ # AWS Lambda Function deployed from Docker Container Image example -Configuration in this directory creates AWS Lambda Function deployed with a Container Image. +Configuration in this directory creates several AWS Lambda Functions deployed from Container Images (using `modules/docker-build` and `terraform-aws-modules/terraform-aws-ecr`). ## Usage @@ -14,29 +14,33 @@ $ terraform apply Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. - + ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 3.19 | -| [docker](#requirement\_docker) | >= 2.12 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | +| [docker](#requirement\_docker) | >= 3.0 | | [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 3.19 | +| [aws](#provider\_aws) | >= 6.0 | | [random](#provider\_random) | >= 2.0 | ## Modules | Name | Source | Version | |------|--------|---------| -| [docker\_image](#module\_docker\_image) | ../../modules/docker-build | n/a | -| [lambda\_function\_from\_container\_image](#module\_lambda\_function\_from\_container\_image) | ../../ | n/a | +| [docker\_build](#module\_docker\_build) | ../../modules/docker-build | n/a | +| [docker\_build\_from\_ecr](#module\_docker\_build\_from\_ecr) | ../../modules/docker-build | n/a | +| [docker\_build\_multistage](#module\_docker\_build\_multistage) | ../../modules/docker-build | n/a | +| [ecr](#module\_ecr) | terraform-aws-modules/ecr/aws | n/a | +| [lambda\_function\_with\_docker\_build](#module\_lambda\_function\_with\_docker\_build) | ../../ | n/a | +| [lambda\_function\_with\_docker\_build\_from\_ecr](#module\_lambda\_function\_with\_docker\_build\_from\_ecr) | ../../ | n/a | ## Resources @@ -55,6 +59,8 @@ No inputs. | Name | Description | |------|-------------| +| [docker\_image\_files\_to\_hash](#output\_docker\_image\_files\_to\_hash) | List of files used to hash the docker image tag | +| [docker\_image\_id](#output\_docker\_image\_id) | The ID of the Docker image | | [docker\_image\_uri](#output\_docker\_image\_uri) | The ECR Docker image URI used to deploy Lambda Function | | [lambda\_cloudwatch\_log\_group\_arn](#output\_lambda\_cloudwatch\_log\_group\_arn) | The ARN of the Cloudwatch Log Group | | [lambda\_function\_arn](#output\_lambda\_function\_arn) | The ARN of the Lambda Function | @@ -74,4 +80,4 @@ No inputs. | [lambda\_layer\_version](#output\_lambda\_layer\_version) | The Lambda Layer version | | [lambda\_role\_arn](#output\_lambda\_role\_arn) | The ARN of the IAM role created for the Lambda Function | | [lambda\_role\_name](#output\_lambda\_role\_name) | The name of the IAM role created for the Lambda Function | - + diff --git a/examples/container-image/context/Dockerfile b/examples/container-image/context/Dockerfile index d6e7662a..64350ec5 100644 --- a/examples/container-image/context/Dockerfile +++ b/examples/container-image/context/Dockerfile @@ -1,8 +1,12 @@ # `--platform` argument is used to be able to build docker images when using another platform (e.g. Apple M1) -FROM --platform=linux/x86_64 scratch +FROM --platform=linux/x86_64 scratch AS first_stage ARG FOO ENV FOO $FOO COPY empty /empty + +FROM first_stage AS second_stage + +COPY empty /empty_two diff --git a/examples/container-image/main.tf b/examples/container-image/main.tf index f00c7c12..9e0ee33d 100644 --- a/examples/container-image/main.tf +++ b/examples/container-image/main.tf @@ -4,42 +4,69 @@ data "aws_caller_identity" "this" {} data "aws_ecr_authorization_token" "token" {} +locals { + source_path = "context" + path_include = ["**"] + path_exclude = ["**/__pycache__/**"] + files_include = setunion([for f in local.path_include : fileset(local.source_path, f)]...) + files_exclude = setunion([for f in local.path_exclude : fileset(local.source_path, f)]...) + files = sort(setsubtract(local.files_include, local.files_exclude)) + + dir_sha = sha1(join("", [for f in local.files : filesha1("${local.source_path}/${f}")])) +} + provider "aws" { region = "eu-west-1" # Make it faster by skipping something - skip_get_ec2_platforms = true skip_metadata_api_check = true skip_region_validation = true skip_credentials_validation = true - skip_requesting_account_id = true } provider "docker" { registry_auth { - address = format("%v.dkr.ecr.%v.amazonaws.com", data.aws_caller_identity.this.account_id, data.aws_region.current.name) + address = format("%v.dkr.ecr.%v.amazonaws.com", data.aws_caller_identity.this.account_id, data.aws_region.current.region) username = data.aws_ecr_authorization_token.token.user_name password = data.aws_ecr_authorization_token.token.password } } -module "lambda_function_from_container_image" { +module "lambda_function_with_docker_build" { + source = "../../" + + function_name = "${random_pet.this.id}-lambda-with-docker-build" + description = "My awesome lambda function with container image by modules/docker-build" + + create_package = false + + ################## + # Container Image + ################## + package_type = "Image" + architectures = ["arm64"] # ["x86_64"] + + image_uri = module.docker_build.image_uri +} + +module "lambda_function_with_docker_build_from_ecr" { source = "../../" - function_name = "${random_pet.this.id}-lambda-from-container-image" - description = "My awesome lambda function from container image" + function_name = "${random_pet.this.id}-lambda-with-docker-build-from-ecr" + description = "My awesome lambda function with container image by modules/docker-build and ECR repository created by terraform-aws-ecr module" create_package = false ################## # Container Image ################## - image_uri = module.docker_image.image_uri package_type = "Image" - architectures = ["x86_64"] + architectures = ["arm64"] # ["x86_64"] + + image_uri = module.docker_build_from_ecr.image_uri } -module "docker_image" { +module "docker_build" { source = "../../modules/docker-build" create_ecr_repo = true @@ -61,11 +88,87 @@ module "docker_image" { ] }) - image_tag = "2.0" - source_path = "context" + use_image_tag = false # If false, sha of the image will be used + + # use_image_tag = true + # image_tag = "2.0" + + source_path = local.source_path + platform = "linux/amd64" + build_args = { + FOO = "bar" + } + + triggers = { + dir_sha = local.dir_sha + } +} + +############################################ +# Docker Image and ECR by terraform-aws-ecr +############################################ + +module "docker_build_from_ecr" { + source = "../../modules/docker-build" + + ecr_repo = module.ecr.repository_name + + use_image_tag = false # If false, sha of the image will be used + + # use_image_tag = true + # image_tag = "2.0" + + source_path = local.source_path + platform = "linux/amd64" + build_args = { + FOO = "bar" + } + # Can also use buildx + builder = "default" + docker_file_path = "${local.source_path}/Dockerfile" + + triggers = { + dir_sha = local.dir_sha + } + + cache_from = ["${module.ecr.repository_url}:latest"] +} + +module "docker_build_multistage" { + source = "../../modules/docker-build" + + ecr_repo = module.ecr.repository_name + + use_image_tag = true + image_tag = "first_stage" + + source_path = local.source_path + platform = "linux/amd64" build_args = { FOO = "bar" } + builder = "default" + docker_file_path = "${local.source_path}/Dockerfile" + + # multi-stage builds + build_target = "first_stage" + + triggers = { + dir_sha = local.dir_sha + } + + cache_from = ["${module.ecr.repository_url}:latest"] +} + +module "ecr" { + source = "terraform-aws-modules/ecr/aws" + + repository_name = "${random_pet.this.id}-ecr" + repository_force_delete = true + + create_lifecycle_policy = false + + repository_lambda_read_access_arns = [module.lambda_function_with_docker_build_from_ecr.lambda_function_arn] } resource "random_pet" "this" { diff --git a/examples/container-image/outputs.tf b/examples/container-image/outputs.tf index a71816f8..7e6f0d2c 100644 --- a/examples/container-image/outputs.tf +++ b/examples/container-image/outputs.tf @@ -1,99 +1,109 @@ # Lambda Function output "lambda_function_arn" { description = "The ARN of the Lambda Function" - value = module.lambda_function_from_container_image.lambda_function_arn + value = module.lambda_function_with_docker_build.lambda_function_arn } output "lambda_function_arn_static" { description = "The static ARN of the Lambda Function. Use this to avoid cycle errors between resources (e.g., Step Functions)" - value = module.lambda_function_from_container_image.lambda_function_arn_static + value = module.lambda_function_with_docker_build.lambda_function_arn_static } output "lambda_function_invoke_arn" { description = "The Invoke ARN of the Lambda Function" - value = module.lambda_function_from_container_image.lambda_function_invoke_arn + value = module.lambda_function_with_docker_build.lambda_function_invoke_arn } output "lambda_function_name" { description = "The name of the Lambda Function" - value = module.lambda_function_from_container_image.lambda_function_name + value = module.lambda_function_with_docker_build.lambda_function_name } output "lambda_function_qualified_arn" { description = "The ARN identifying your Lambda Function Version" - value = module.lambda_function_from_container_image.lambda_function_qualified_arn + value = module.lambda_function_with_docker_build.lambda_function_qualified_arn } output "lambda_function_version" { description = "Latest published version of Lambda Function" - value = module.lambda_function_from_container_image.lambda_function_version + value = module.lambda_function_with_docker_build.lambda_function_version } output "lambda_function_last_modified" { description = "The date Lambda Function resource was last modified" - value = module.lambda_function_from_container_image.lambda_function_last_modified + value = module.lambda_function_with_docker_build.lambda_function_last_modified } output "lambda_function_kms_key_arn" { description = "The ARN for the KMS encryption key of Lambda Function" - value = module.lambda_function_from_container_image.lambda_function_kms_key_arn + value = module.lambda_function_with_docker_build.lambda_function_kms_key_arn } output "lambda_function_source_code_hash" { description = "Base64-encoded representation of raw SHA-256 sum of the zip file" - value = module.lambda_function_from_container_image.lambda_function_source_code_hash + value = module.lambda_function_with_docker_build.lambda_function_source_code_hash } output "lambda_function_source_code_size" { description = "The size in bytes of the function .zip file" - value = module.lambda_function_from_container_image.lambda_function_source_code_size + value = module.lambda_function_with_docker_build.lambda_function_source_code_size } # Lambda Layer output "lambda_layer_arn" { description = "The ARN of the Lambda Layer with version" - value = module.lambda_function_from_container_image.lambda_layer_arn + value = module.lambda_function_with_docker_build.lambda_layer_arn } output "lambda_layer_layer_arn" { description = "The ARN of the Lambda Layer without version" - value = module.lambda_function_from_container_image.lambda_layer_layer_arn + value = module.lambda_function_with_docker_build.lambda_layer_layer_arn } output "lambda_layer_created_date" { description = "The date Lambda Layer resource was created" - value = module.lambda_function_from_container_image.lambda_layer_created_date + value = module.lambda_function_with_docker_build.lambda_layer_created_date } output "lambda_layer_source_code_size" { description = "The size in bytes of the Lambda Layer .zip file" - value = module.lambda_function_from_container_image.lambda_layer_source_code_size + value = module.lambda_function_with_docker_build.lambda_layer_source_code_size } output "lambda_layer_version" { description = "The Lambda Layer version" - value = module.lambda_function_from_container_image.lambda_layer_version + value = module.lambda_function_with_docker_build.lambda_layer_version } # IAM Role output "lambda_role_arn" { description = "The ARN of the IAM role created for the Lambda Function" - value = module.lambda_function_from_container_image.lambda_role_arn + value = module.lambda_function_with_docker_build.lambda_role_arn } output "lambda_role_name" { description = "The name of the IAM role created for the Lambda Function" - value = module.lambda_function_from_container_image.lambda_role_name + value = module.lambda_function_with_docker_build.lambda_role_name } # CloudWatch Log Group output "lambda_cloudwatch_log_group_arn" { description = "The ARN of the Cloudwatch Log Group" - value = module.lambda_function_from_container_image.lambda_cloudwatch_log_group_arn + value = module.lambda_function_with_docker_build.lambda_cloudwatch_log_group_arn } -# Docker Image +# Docker Image by modules/docker-build output "docker_image_uri" { description = "The ECR Docker image URI used to deploy Lambda Function" - value = module.docker_image.image_uri + value = module.docker_build.image_uri +} + +output "docker_image_id" { + description = "The ID of the Docker image" + value = module.docker_build.image_id +} + +output "docker_image_files_to_hash" { + description = "List of files used to hash the docker image tag" + value = local.files } diff --git a/examples/container-image/versions.tf b/examples/container-image/versions.tf index 63a095cc..dbb8009c 100644 --- a/examples/container-image/versions.tf +++ b/examples/container-image/versions.tf @@ -1,14 +1,14 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 3.19" + version = ">= 6.0" } docker = { source = "kreuzwerker/docker" - version = ">= 2.12" + version = ">= 3.0" } random = { source = "hashicorp/random" diff --git a/examples/deploy/README.md b/examples/deploy/README.md index 5b0846f0..829a0468 100644 --- a/examples/deploy/README.md +++ b/examples/deploy/README.md @@ -14,20 +14,20 @@ $ terraform apply Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. - + ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 3.19 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | | [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 3.19 | +| [aws](#provider\_aws) | >= 6.0 | | [random](#provider\_random) | >= 2.0 | ## Modules @@ -63,4 +63,4 @@ No inputs. | [codedeploy\_iam\_role\_name](#output\_codedeploy\_iam\_role\_name) | Name of IAM role used by CodeDeploy | | [deploy\_script](#output\_deploy\_script) | Path to a deployment script | | [script](#output\_script) | Deployment script | - + diff --git a/examples/deploy/main.tf b/examples/deploy/main.tf index f9007fd8..ee8c8bed 100644 --- a/examples/deploy/main.tf +++ b/examples/deploy/main.tf @@ -2,11 +2,9 @@ provider "aws" { region = "eu-west-1" # Make it faster by skipping something - skip_get_ec2_platforms = true skip_metadata_api_check = true skip_region_validation = true skip_credentials_validation = true - skip_requesting_account_id = true } resource "random_pet" "this" { @@ -18,10 +16,10 @@ module "lambda_function" { function_name = "${random_pet.this.id}-lambda" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" publish = true - source_path = "${path.module}/../fixtures/python3.8-app1" + source_path = "${path.module}/../fixtures/python-app1" hash_extra = "yo1" } diff --git a/examples/deploy/versions.tf b/examples/deploy/versions.tf index 6f0e3af3..d2f4f3e8 100644 --- a/examples/deploy/versions.tf +++ b/examples/deploy/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 3.19" + version = ">= 6.0" } random = { source = "hashicorp/random" diff --git a/examples/event-source-mapping/README.md b/examples/event-source-mapping/README.md index 0f34132e..49d4fa75 100644 --- a/examples/event-source-mapping/README.md +++ b/examples/event-source-mapping/README.md @@ -13,3 +13,67 @@ $ terraform apply ``` Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | +| [random](#requirement\_random) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 6.0 | +| [random](#provider\_random) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [lambda\_function](#module\_lambda\_function) | ../../ | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_dynamodb_table.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table) | resource | +| [aws_kinesis_stream.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kinesis_stream) | resource | +| [aws_mq_broker.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/mq_broker) | resource | +| [aws_secretsmanager_secret.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret) | resource | +| [aws_secretsmanager_secret_version.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_version) | resource | +| [aws_sqs_queue.failure](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource | +| [aws_sqs_queue.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource | +| [random_password.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | +| [aws_organizations_organization.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/organizations_organization) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [lambda\_event\_source\_mapping\_arn](#output\_lambda\_event\_source\_mapping\_arn) | The event source mapping ARN | +| [lambda\_event\_source\_mapping\_function\_arn](#output\_lambda\_event\_source\_mapping\_function\_arn) | The the ARN of the Lambda function the event source mapping is sending events to | +| [lambda\_event\_source\_mapping\_state](#output\_lambda\_event\_source\_mapping\_state) | The state of the event source mapping | +| [lambda\_event\_source\_mapping\_state\_transition\_reason](#output\_lambda\_event\_source\_mapping\_state\_transition\_reason) | The reason the event source mapping is in its current state | +| [lambda\_event\_source\_mapping\_uuid](#output\_lambda\_event\_source\_mapping\_uuid) | The UUID of the created event source mapping | +| [lambda\_function\_arn](#output\_lambda\_function\_arn) | The ARN of the Lambda Function | +| [lambda\_function\_arn\_static](#output\_lambda\_function\_arn\_static) | The static ARN of the Lambda Function. Use this to avoid cycle errors between resources (e.g., Step Functions) | +| [lambda\_function\_invoke\_arn](#output\_lambda\_function\_invoke\_arn) | The Invoke ARN of the Lambda Function | +| [lambda\_function\_kms\_key\_arn](#output\_lambda\_function\_kms\_key\_arn) | The ARN for the KMS encryption key of Lambda Function | +| [lambda\_function\_last\_modified](#output\_lambda\_function\_last\_modified) | The date Lambda Function resource was last modified | +| [lambda\_function\_name](#output\_lambda\_function\_name) | The name of the Lambda Function | +| [lambda\_function\_qualified\_arn](#output\_lambda\_function\_qualified\_arn) | The ARN identifying your Lambda Function Version | +| [lambda\_function\_source\_code\_hash](#output\_lambda\_function\_source\_code\_hash) | Base64-encoded representation of raw SHA-256 sum of the zip file | +| [lambda\_function\_source\_code\_size](#output\_lambda\_function\_source\_code\_size) | The size in bytes of the function .zip file | +| [lambda\_function\_version](#output\_lambda\_function\_version) | Latest published version of Lambda Function | + diff --git a/examples/event-source-mapping/main.tf b/examples/event-source-mapping/main.tf index 62e57443..f76d30c8 100644 --- a/examples/event-source-mapping/main.tf +++ b/examples/event-source-mapping/main.tf @@ -2,11 +2,19 @@ provider "aws" { region = "eu-west-1" # Make it faster by skipping something - skip_get_ec2_platforms = true + skip_metadata_api_check = true skip_region_validation = true skip_credentials_validation = true - skip_requesting_account_id = true +} + +data "aws_availability_zones" "available" {} + +data "aws_organizations_organization" "this" {} + +locals { + vpc_cidr = "10.0.0.0/16" + azs = slice(data.aws_availability_zones.available.names, 0, 3) } #################################################### @@ -18,24 +26,40 @@ module "lambda_function" { function_name = "${random_pet.this.id}-lambda-event-source-mapping" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" - source_path = "${path.module}/../fixtures/python3.8-app1" + source_path = "${path.module}/../fixtures/python-app1/index.py" event_source_mapping = { sqs = { event_source_arn = aws_sqs_queue.this.arn function_response_types = ["ReportBatchItemFailures"] + scaling_config = { + maximum_concurrency = 20 + } + metrics_config = { + metrics = ["EventCount"] + } } dynamodb = { event_source_arn = aws_dynamodb_table.this.stream_arn starting_position = "LATEST" destination_arn_on_failure = aws_sqs_queue.failure.arn - filter_criteria = { - pattern = jsonencode({ - eventName : ["INSERT"] - }) - } + filter_criteria = [ + { + pattern = jsonencode({ + eventName : ["INSERT"] + }) + }, + { + pattern = jsonencode({ + data : { + Temperature : [{ numeric : [">", 0, "<=", 100] }] + Location : ["Oslo"] + } + }) + } + ] } kinesis = { event_source_arn = aws_kinesis_stream.this.arn @@ -62,6 +86,7 @@ module "lambda_function" { uri = "/" } ] + tags = { mapping = "amq" } } # self_managed_kafka = { # batch_size = 1 @@ -74,6 +99,11 @@ module "lambda_function" { # } # } # ] + # self_managed_kafka_event_source_config = [ + # { + # consumer_group_id = "example-consumer-group" + # } + # ] # source_access_configuration = [ # { # type = "SASL_SCRAM_512_AUTH", @@ -92,6 +122,10 @@ module "lambda_function" { } allowed_triggers = { + config = { + principal = "config.amazonaws.com" + principal_org_id = data.aws_organizations_organization.this.id + } sqs = { principal = "sqs.amazonaws.com" source_arn = aws_sqs_queue.this.arn @@ -149,6 +183,10 @@ module "lambda_function" { "arn:aws:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole", "arn:aws:iam::aws:policy/service-role/AWSLambdaKinesisExecutionRole", ] + + tags = { + example = "event-source-mapping" + } } ################## @@ -201,21 +239,26 @@ resource "aws_kinesis_stream" "this" { } # Amazon MQ -data "aws_vpc" "default" { - default = true -} +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.0" + + name = random_pet.this.id + cidr = local.vpc_cidr + + azs = local.azs + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k)] -data "aws_security_group" "default" { - vpc_id = data.aws_vpc.default.id - name = "default" + enable_nat_gateway = false } resource "aws_mq_broker" "this" { broker_name = random_pet.this.id engine_type = "RabbitMQ" - engine_version = "3.8.11" + engine_version = "3.12.13" host_instance_type = "mq.t3.micro" - security_groups = [data.aws_security_group.default.id] + security_groups = [module.vpc.default_security_group_id] + subnet_ids = slice(module.vpc.public_subnets, 0, 1) user { username = random_pet.this.id diff --git a/examples/event-source-mapping/outputs.tf b/examples/event-source-mapping/outputs.tf index 5b69eeb5..764a91c5 100644 --- a/examples/event-source-mapping/outputs.tf +++ b/examples/event-source-mapping/outputs.tf @@ -69,3 +69,8 @@ output "lambda_event_source_mapping_uuid" { description = "The UUID of the created event source mapping" value = module.lambda_function.lambda_event_source_mapping_uuid } + +output "lambda_event_source_mapping_arn" { + description = "The event source mapping ARN" + value = module.lambda_function.lambda_event_source_mapping_arn +} diff --git a/examples/event-source-mapping/versions.tf b/examples/event-source-mapping/versions.tf index d77dd0b7..d2f4f3e8 100644 --- a/examples/event-source-mapping/versions.tf +++ b/examples/event-source-mapping/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 3.69" + version = ">= 6.0" } random = { source = "hashicorp/random" diff --git a/examples/fixtures/nodejs14.x-app2/index.js b/examples/fixtures/nodejs14.x-app2/index.js new file mode 100644 index 00000000..97968e4a --- /dev/null +++ b/examples/fixtures/nodejs14.x-app2/index.js @@ -0,0 +1,16 @@ +'use strict'; + +module.exports.hello = async (event) => { + console.log(event); + return { + statusCode: 200, + body: JSON.stringify( + { + message: `Go Serverless.tf! Your Nodejs function executed successfully!`, + input: event, + }, + null, + 2 + ), + }; +}; diff --git a/examples/fixtures/nodejs14.x-app2/package-lock.json b/examples/fixtures/nodejs14.x-app2/package-lock.json new file mode 100644 index 00000000..adf88ca6 --- /dev/null +++ b/examples/fixtures/nodejs14.x-app2/package-lock.json @@ -0,0 +1,83 @@ +{ + "name": "nodejs14.x-app1", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "axo": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/axo/-/axo-0.0.2.tgz", + "integrity": "sha512-8CC4Mb+OhK97UEng0PgiqUDNZjzVcWDsV+G2vLYCQn1jEL7y6VqiRVlZlRu+aA/ckSznmNzW6X1I6nj2As/haQ==" + }, + "eventemitter3": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz", + "integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==" + }, + "extendible": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/extendible/-/extendible-0.1.1.tgz", + "integrity": "sha512-AglckQA0TJV8/ZmhQcNmaaFcFFPXFIoZbfuoQOlGDK7Jh/roWotYzJ7ik1FBBCHBr8n7CgTR8lXXPAN8Rfb7rw==" + }, + "failure": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/failure/-/failure-1.1.1.tgz", + "integrity": "sha512-lzrrk0NUfjVeU3jLmfU01zP5bfg4XVFxHREYGvgJowaCqHLSQtqIGENH/CU+oSs6yfYObdSM7b9UY/3p2VJOSg==" + }, + "hang": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hang/-/hang-1.0.0.tgz", + "integrity": "sha512-vtBz98Bt/Tbm03cZO5Ymc7ZL8ead/jIx9T5Wg/xuz+9BXPAJNJSdGQW63LoaesogUQKTpHyal339hxTaTf/APg==" + }, + "loads": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/loads/-/loads-0.0.4.tgz", + "integrity": "sha512-XjPzzYIHkuMNqYyvh6AECQAHi682nyKO9TMdMYnaz7QbPDI/KIeSIjRhAlXIbRMPYAgtLUYgPlD3mtKZ4Q8SYA==", + "requires": { + "failure": "1.1.x", + "one-time": "0.0.x", + "xhr-response": "1.0.x", + "xhr-status": "1.0.x" + } + }, + "node-http-xhr": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/node-http-xhr/-/node-http-xhr-1.2.1.tgz", + "integrity": "sha512-eRKOQNY8V2BNp/P8A2A+eVwprVFI64ciunsBimQ4WBb1m841vn7ksDRGlmWBCyE/tLRoPwvH/sUig9krKMehwA==" + }, + "one-time": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", + "integrity": "sha512-qAMrwuk2xLEutlASoiPiAMW3EN3K96Ka/ilSXYr6qR1zSVXw2j7+yDSqGTC4T9apfLYxM3tLLjKvgPdAUK7kYQ==" + }, + "requests": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/requests/-/requests-0.2.1.tgz", + "integrity": "sha512-SlvQm7cl4z285qs5ZrthHjr4TI0Ngb0pzX4jLzSOr7rsA4AlFj+0qhkzF3zKsVBFuU+HseU+iUz4qBA6bV087Q==", + "requires": { + "axo": "0.0.x", + "eventemitter3": "~2.0.2", + "extendible": "0.1.x", + "hang": "1.0.x", + "loads": "0.0.x", + "node-http-xhr": "~1.2.1", + "xhr-send": "1.0.x" + } + }, + "xhr-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xhr-response/-/xhr-response-1.0.1.tgz", + "integrity": "sha512-m2FlVRCl3VqDcpc8UaWZJpwuHpFR2vYeXv6ipXU2Uuu4vNKFYVEFI0emuJN370Fge+JCbiAnS+JJmSoWVmWrjQ==" + }, + "xhr-send": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/xhr-send/-/xhr-send-1.0.0.tgz", + "integrity": "sha512-789EG4qW6Z0nPvG74AV3WWQCnBG5HxJXNiBsnEivZ8OpbvVA0amH0+g+MNT99o5kt/XLdRezm5KS1wJzcGJztw==" + }, + "xhr-status": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xhr-status/-/xhr-status-1.0.1.tgz", + "integrity": "sha512-VF0WSqtmkf56OmF26LCWsWvRb1a+WYGdHDoQnPPCVUQTM8CVUAOBcUDsm7nP7SQcgEEdrvF4DmhEADuXdGieyw==" + } + } +} diff --git a/examples/fixtures/nodejs14.x-app2/package.json b/examples/fixtures/nodejs14.x-app2/package.json new file mode 100644 index 00000000..b332ac7e --- /dev/null +++ b/examples/fixtures/nodejs14.x-app2/package.json @@ -0,0 +1,8 @@ +{ + "name": "nodejs14.x-app1", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "requests": "^0.2.0" + } +} diff --git a/examples/fixtures/python3.9-app-poetry/docker/Dockerfile b/examples/fixtures/python-app-poetry/docker/Dockerfile similarity index 74% rename from examples/fixtures/python3.9-app-poetry/docker/Dockerfile rename to examples/fixtures/python-app-poetry/docker/Dockerfile index 9d19957b..9b6c9ed4 100644 --- a/examples/fixtures/python3.9-app-poetry/docker/Dockerfile +++ b/examples/fixtures/python-app-poetry/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM public.ecr.aws/sam/build-python3.9 +FROM public.ecr.aws/sam/build-python3.12 LABEL maintainer="Betajob AS" \ description="Patched AWS Lambda build container" diff --git a/examples/fixtures/python3.8-app1/ignore_please.txt b/examples/fixtures/python-app-poetry/ignore_please.txt similarity index 100% rename from examples/fixtures/python3.8-app1/ignore_please.txt rename to examples/fixtures/python-app-poetry/ignore_please.txt diff --git a/examples/fixtures/python3.8-app1/index.py b/examples/fixtures/python-app-poetry/index.py similarity index 100% rename from examples/fixtures/python3.8-app1/index.py rename to examples/fixtures/python-app-poetry/index.py diff --git a/examples/fixtures/python3.9-app-poetry/poetry.lock b/examples/fixtures/python-app-poetry/poetry.lock similarity index 100% rename from examples/fixtures/python3.9-app-poetry/poetry.lock rename to examples/fixtures/python-app-poetry/poetry.lock diff --git a/examples/fixtures/python3.9-app-poetry/poetry.toml b/examples/fixtures/python-app-poetry/poetry.toml similarity index 100% rename from examples/fixtures/python3.9-app-poetry/poetry.toml rename to examples/fixtures/python-app-poetry/poetry.toml diff --git a/examples/fixtures/python3.9-app-poetry/pyproject.toml b/examples/fixtures/python-app-poetry/pyproject.toml similarity index 90% rename from examples/fixtures/python3.9-app-poetry/pyproject.toml rename to examples/fixtures/python-app-poetry/pyproject.toml index c09d58aa..ea289d4a 100644 --- a/examples/fixtures/python3.9-app-poetry/pyproject.toml +++ b/examples/fixtures/python-app-poetry/pyproject.toml @@ -1,5 +1,5 @@ [tool.poetry] -name = "python3.9-app-poetry" +name = "python-app-poetry" version = "0.1.0" description = "" authors = ["Your Name "] diff --git a/examples/fixtures/python-app-src-poetry/README.md b/examples/fixtures/python-app-src-poetry/README.md new file mode 100644 index 00000000..e69de29b diff --git a/examples/fixtures/python-app-src-poetry/poetry.lock b/examples/fixtures/python-app-src-poetry/poetry.lock new file mode 100644 index 00000000..5db627a1 --- /dev/null +++ b/examples/fixtures/python-app-src-poetry/poetry.lock @@ -0,0 +1,31 @@ +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "colorful" +version = "0.5.5" +description = "Terminal string styling done right, in Python." +optional = false +python-versions = "*" +files = [ + {file = "colorful-0.5.5-py2.py3-none-any.whl", hash = "sha256:62c187e27c1433db9463ff93b1451898d1e7e23a7e553583fd9daeb6325182e4"}, + {file = "colorful-0.5.5.tar.gz", hash = "sha256:66f8c1264b2a26f7293b96a03bb7a76c4bc8b9634369a0bffdcd12d618056a1d"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[metadata] +lock-version = "2.0" +python-versions = "^3.9" +content-hash = "fdeeb736094152bb24127be160ee7e572f919f877d43589ad98e1e885dc6427d" diff --git a/examples/fixtures/python-app-src-poetry/pyproject.toml b/examples/fixtures/python-app-src-poetry/pyproject.toml new file mode 100644 index 00000000..7ceca4c4 --- /dev/null +++ b/examples/fixtures/python-app-src-poetry/pyproject.toml @@ -0,0 +1,16 @@ +[tool.poetry] +name = "python-app-src-poetry" +version = "0.1.0" +description = "" +authors = ["Your Name "] +readme = "README.md" +packages = [{include = "python_app_src_poetry", from = "src"}] + +[tool.poetry.dependencies] +python = "^3.9" +colorful = "^0.5.5" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/examples/fixtures/python-app-src-poetry/src/python_app_src_poetry/__init__.py b/examples/fixtures/python-app-src-poetry/src/python_app_src_poetry/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/fixtures/python-app-src-poetry/tests/__init__.py b/examples/fixtures/python-app-src-poetry/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/fixtures/python3.8-app1/dir1/dir2/ignore2.txt b/examples/fixtures/python-app1/dir1/dir2/ignore2.txt similarity index 100% rename from examples/fixtures/python3.8-app1/dir1/dir2/ignore2.txt rename to examples/fixtures/python-app1/dir1/dir2/ignore2.txt diff --git a/examples/fixtures/python3.8-app1/docker/Dockerfile b/examples/fixtures/python-app1/docker/Dockerfile similarity index 87% rename from examples/fixtures/python3.8-app1/docker/Dockerfile rename to examples/fixtures/python-app1/docker/Dockerfile index aeab9fee..fa0eb25f 100644 --- a/examples/fixtures/python3.8-app1/docker/Dockerfile +++ b/examples/fixtures/python-app1/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM public.ecr.aws/sam/build-python3.8 as build +FROM public.ecr.aws/sam/build-python3.12 as build LABEL maintainer="Betajob AS" \ description="Patched AWS Lambda build container" @@ -20,7 +20,7 @@ RUN \ && rpmbuild -ba SPECS/automake.spec --nocheck \ && yum install -y RPMS/noarch/* -FROM public.ecr.aws/sam/build-python3.8 +FROM public.ecr.aws/sam/build-python3.12 COPY --from=build /root/rpmbuild/RPMS/noarch/*.rpm . RUN yum install -y *.rpm \ && rm *.rpm diff --git a/examples/fixtures/python3.8-app1/docker/automake-1.13-to-1.16-spec.patch b/examples/fixtures/python-app1/docker/automake-1.13-to-1.16-spec.patch similarity index 100% rename from examples/fixtures/python3.8-app1/docker/automake-1.13-to-1.16-spec.patch rename to examples/fixtures/python-app1/docker/automake-1.13-to-1.16-spec.patch diff --git a/examples/fixtures/python3.8-app1/docker/entrypoint.sh b/examples/fixtures/python-app1/docker/entrypoint.sh similarity index 100% rename from examples/fixtures/python3.8-app1/docker/entrypoint.sh rename to examples/fixtures/python-app1/docker/entrypoint.sh diff --git a/examples/fixtures/python3.9-app-poetry/ignore_please.txt b/examples/fixtures/python-app1/ignore_please.txt similarity index 100% rename from examples/fixtures/python3.9-app-poetry/ignore_please.txt rename to examples/fixtures/python-app1/ignore_please.txt diff --git a/examples/fixtures/python3.9-app-poetry/index.py b/examples/fixtures/python-app1/index.py similarity index 100% rename from examples/fixtures/python3.9-app-poetry/index.py rename to examples/fixtures/python-app1/index.py diff --git a/examples/fixtures/python3.8-app1/requirements.txt b/examples/fixtures/python-app1/requirements.txt similarity index 100% rename from examples/fixtures/python3.8-app1/requirements.txt rename to examples/fixtures/python-app1/requirements.txt diff --git a/examples/fixtures/python-app2/index.py b/examples/fixtures/python-app2/index.py new file mode 100644 index 00000000..3ec1af75 --- /dev/null +++ b/examples/fixtures/python-app2/index.py @@ -0,0 +1,25 @@ +import logging +import boto3 +import os +from uuid import uuid4 + +# See https://docs.aws.amazon.com/lambda/latest/dg/python-logging.html +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +logging.getLogger("boto3").setLevel(logging.DEBUG) +logging.getLogger("botocore").setLevel(logging.DEBUG) + +bucketName = os.environ["BUCKET_NAME"] +regionName = os.environ["REGION_NAME"] + + +def lambda_handler(event, context): + client = boto3.client("s3", regionName) + response = client.put_object( + Bucket=bucketName, Key=str(uuid4()), Body=bytearray("Hello, World!", "utf-8") + ) + + logger.info(response) + + return response diff --git a/examples/fixtures/python-function.zip b/examples/fixtures/python-function.zip new file mode 100644 index 00000000..d713a912 Binary files /dev/null and b/examples/fixtures/python-function.zip differ diff --git a/examples/fixtures/python-zip/existing_package.zip b/examples/fixtures/python-zip/existing_package.zip new file mode 100644 index 00000000..d713a912 Binary files /dev/null and b/examples/fixtures/python-zip/existing_package.zip differ diff --git a/examples/fixtures/python3.8-app2/index.py b/examples/fixtures/python3.8-app2/index.py deleted file mode 100644 index 0ba33055..00000000 --- a/examples/fixtures/python3.8-app2/index.py +++ /dev/null @@ -1,26 +0,0 @@ -import logging -import boto3 -import os -from uuid import uuid4 - -# See https://docs.aws.amazon.com/lambda/latest/dg/python-logging.html -logger = logging.getLogger() -logger.setLevel(logging.INFO) - -logging.getLogger('boto3').setLevel(logging.DEBUG) -logging.getLogger('botocore').setLevel(logging.DEBUG) - -bucketName = os.environ['BUCKET_NAME'] -regionName = os.environ['REGION_NAME'] - -def lambda_handler(event, context): - client = boto3.client('s3', regionName) - response = client.put_object( - Bucket=bucketName, - Key=str(uuid4()), - Body=bytearray("Hello, World!", 'utf-8') - ) - - logger.info(response) - - return response diff --git a/examples/fixtures/runtimes/dotnet8/.gitignore b/examples/fixtures/runtimes/dotnet8/.gitignore new file mode 100644 index 00000000..bc78471d --- /dev/null +++ b/examples/fixtures/runtimes/dotnet8/.gitignore @@ -0,0 +1,484 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from `dotnet new gitignore` + +# dotenv files +.env + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea/ + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Vim temporary swap files +*.swp diff --git a/examples/fixtures/runtimes/dotnet8/Program.cs b/examples/fixtures/runtimes/dotnet8/Program.cs new file mode 100644 index 00000000..0b1448d7 --- /dev/null +++ b/examples/fixtures/runtimes/dotnet8/Program.cs @@ -0,0 +1,16 @@ +using System.Text.Json; +using Amazon.Lambda.Core; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.SystemTextJson; + +[assembly: LambdaSerializer(typeof(DefaultLambdaJsonSerializer))] + +var handler = (JsonElement input, ILambdaContext context) => +{ + context.Logger.LogInformation($"Processing input: {input}"); + return "Hello from serverless.tf!!!"; +}; + +await LambdaBootstrapBuilder.Create(handler, new DefaultLambdaJsonSerializer()) + .Build() + .RunAsync(); diff --git a/examples/fixtures/runtimes/dotnet8/Program.csproj b/examples/fixtures/runtimes/dotnet8/Program.csproj new file mode 100644 index 00000000..a1f71466 --- /dev/null +++ b/examples/fixtures/runtimes/dotnet8/Program.csproj @@ -0,0 +1,18 @@ + + + net8.0 + bootstrap + Exe + true + false + enable + enable + true + + + + + + + + diff --git a/examples/fixtures/runtimes/go/.gitignore b/examples/fixtures/runtimes/go/.gitignore new file mode 100644 index 00000000..2da3a426 --- /dev/null +++ b/examples/fixtures/runtimes/go/.gitignore @@ -0,0 +1,2 @@ +go.sum +bootstrap diff --git a/examples/fixtures/runtimes/go/go.mod b/examples/fixtures/runtimes/go/go.mod new file mode 100644 index 00000000..c572c2e4 --- /dev/null +++ b/examples/fixtures/runtimes/go/go.mod @@ -0,0 +1,5 @@ +module main + +go 1.22.6 + +require github.com/aws/aws-lambda-go v1.47.0 // indirect diff --git a/examples/fixtures/runtimes/go/main.go b/examples/fixtures/runtimes/go/main.go new file mode 100644 index 00000000..6a5defa1 --- /dev/null +++ b/examples/fixtures/runtimes/go/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "context" + "fmt" + "github.com/aws/aws-lambda-go/lambda" +) + +type MyEvent struct { + Name string `json:"name"` +} + +func HandleRequest(ctx context.Context, event *MyEvent) (*string, error) { + if event == nil { + return nil, fmt.Errorf("received nil event") + } + message := fmt.Sprintf("Hello %s! serverless.tf was here!", event.Name) + return &message, nil +} + +func main() { + lambda.Start(HandleRequest) +} diff --git a/examples/fixtures/runtimes/java21/.gitignore b/examples/fixtures/runtimes/java21/.gitignore new file mode 100644 index 00000000..67bcc2f7 --- /dev/null +++ b/examples/fixtures/runtimes/java21/.gitignore @@ -0,0 +1,2 @@ +.gradle/ +build/ diff --git a/examples/fixtures/runtimes/java21/build.gradle b/examples/fixtures/runtimes/java21/build.gradle new file mode 100644 index 00000000..53f6f6ee --- /dev/null +++ b/examples/fixtures/runtimes/java21/build.gradle @@ -0,0 +1,40 @@ +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'com.amazonaws:aws-lambda-java-core:1.2.1' + implementation 'org.slf4j:slf4j-nop:2.0.6' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2' +} + +test { + useJUnitPlatform() +} + +// Using terraform-aws-lambda module, there is no need to make Zip archive by Gradle. Terraform AWS module will make it for you. +// task buildZip(type: Zip) { +// from compileJava +// from processResources +// into('lib') { +// from configurations.runtimeClasspath +// } +// } + +task copyFiles(type: Copy) { + into("$buildDir/output") + + from sourceSets.main.output + + into('lib') { + from configurations.runtimeClasspath + } +} + +build.dependsOn copyFiles diff --git a/examples/fixtures/runtimes/java21/src/main/java/example/Handler.java b/examples/fixtures/runtimes/java21/src/main/java/example/Handler.java new file mode 100644 index 00000000..08b14d81 --- /dev/null +++ b/examples/fixtures/runtimes/java21/src/main/java/example/Handler.java @@ -0,0 +1,19 @@ +package example; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.LambdaLogger; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import java.util.Map; + +// Handler value: example.Handler +public class Handler implements RequestHandler, String>{ + + @Override + public String handleRequest(Map event, Context context) + { + LambdaLogger logger = context.getLogger(); + logger.log("EVENT TYPE: " + event.getClass()); + return "Hello from serverless.tf!!!"; + } +} diff --git a/examples/fixtures/runtimes/rust/.gitignore b/examples/fixtures/runtimes/rust/.gitignore new file mode 100644 index 00000000..96ef6c0b --- /dev/null +++ b/examples/fixtures/runtimes/rust/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/examples/fixtures/runtimes/rust/Cargo.toml b/examples/fixtures/runtimes/rust/Cargo.toml new file mode 100644 index 00000000..781f306b --- /dev/null +++ b/examples/fixtures/runtimes/rust/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "rust-app1" +version = "0.1.0" +edition = "2021" + +# Starting in Rust 1.62 you can use `cargo add` to add dependencies +# to your project. +# +# If you're using an older Rust version, +# download cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to install the `add` subcommand. +# +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +lambda_http = "0.13.0" + +tokio = { version = "1", features = ["macros"] } diff --git a/examples/fixtures/runtimes/rust/src/main.rs b/examples/fixtures/runtimes/rust/src/main.rs new file mode 100644 index 00000000..4432a31c --- /dev/null +++ b/examples/fixtures/runtimes/rust/src/main.rs @@ -0,0 +1,30 @@ +use lambda_http::{run, service_fn, tracing, Body, Error, Request, RequestExt, Response}; + +/// This is the main body for the function. +/// Write your code inside it. +/// There are some code example in the following URLs: +/// - https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples +async fn function_handler(event: Request) -> Result, Error> { + // Extract some useful information from the request + let who = event + .query_string_parameters_ref() + .and_then(|params| params.first("name")) + .unwrap_or("world"); + let message = format!("Hello {who}, this is an AWS Lambda HTTP request. serverless.tf was here!"); + + // Return something that implements IntoResponse. + // It will be serialized to the right response event automatically by the runtime + let resp = Response::builder() + .status(200) + .header("content-type", "text/html") + .body(message.into()) + .map_err(Box::new)?; + Ok(resp) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing::init_default_subscriber(); + + run(service_fn(function_handler)).await +} diff --git a/examples/multiple-regions/README.md b/examples/multiple-regions/README.md index 6528a8db..ed4c573a 100644 --- a/examples/multiple-regions/README.md +++ b/examples/multiple-regions/README.md @@ -15,21 +15,21 @@ $ terraform apply Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. - + ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 3.19 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | | [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 3.19 | -| [aws.us-east-1](#provider\_aws.us-east-1) | >= 3.19 | +| [aws](#provider\_aws) | >= 6.0 | +| [aws.us-east-1](#provider\_aws.us-east-1) | >= 6.0 | | [random](#provider\_random) | >= 2.0 | ## Modules @@ -75,4 +75,4 @@ No inputs. | [lambda\_role\_name](#output\_lambda\_role\_name) | The name of the IAM role created for the Lambda Function | | [local\_filename](#output\_local\_filename) | The filename of zip archive deployed (if deployment was from local) | | [s3\_object](#output\_s3\_object) | The map with S3 object data of zip archive deployed (if deployment was from S3) | - + diff --git a/examples/multiple-regions/main.tf b/examples/multiple-regions/main.tf index a5fb1dfb..d30e1c2a 100644 --- a/examples/multiple-regions/main.tf +++ b/examples/multiple-regions/main.tf @@ -2,11 +2,9 @@ provider "aws" { region = "eu-west-1" # Make it faster by skipping something - skip_get_ec2_platforms = true skip_metadata_api_check = true skip_region_validation = true skip_credentials_validation = true - skip_requesting_account_id = true } provider "aws" { @@ -14,11 +12,9 @@ provider "aws" { alias = "us-east-1" # Make it faster by skipping something - skip_get_ec2_platforms = true skip_metadata_api_check = true skip_region_validation = true skip_credentials_validation = true - skip_requesting_account_id = true } ################################ @@ -31,10 +27,10 @@ module "lambda_function" { function_name = "${random_pet.this.id}-lambda1" description = "My awesome lambda function" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" publish = true - source_path = "${path.module}/../fixtures/python3.8-app1" + source_path = "${path.module}/../fixtures/python-app1" attach_dead_letter_policy = true dead_letter_target_arn = aws_sqs_queue.dlq.arn @@ -128,10 +124,10 @@ module "lambda_function_another_region" { function_name = "${random_pet.this.id}-lambda1" description = "Copy of my awesome lambda function" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" publish = true - source_path = "${path.module}/../fixtures/python3.8-app1" + source_path = "${path.module}/../fixtures/python-app1" attach_dead_letter_policy = true dead_letter_target_arn = aws_sqs_queue.dlq_us_east_1.arn diff --git a/examples/multiple-regions/versions.tf b/examples/multiple-regions/versions.tf index 6f0e3af3..d2f4f3e8 100644 --- a/examples/multiple-regions/versions.tf +++ b/examples/multiple-regions/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 3.19" + version = ">= 6.0" } random = { source = "hashicorp/random" diff --git a/examples/runtimes/README.md b/examples/runtimes/README.md new file mode 100644 index 00000000..95a28a08 --- /dev/null +++ b/examples/runtimes/README.md @@ -0,0 +1,70 @@ +# Runtimes Examples + +Configuration in this directory creates deployment packages for [various runtimes and programming languages (Rust, Go, Java, .NET)](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html). + +Each runtime is executable by calling created Lambda Functions at the end. + +Look into [Build Package Examples](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/build-package) for more ways to build package (regardless of the runtime). + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | +| [http](#requirement\_http) | >= 3.0 | +| [random](#requirement\_random) | >= 3.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 6.0 | +| [http](#provider\_http) | >= 3.0 | +| [random](#provider\_random) | >= 3.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [dotnet8\_lambda\_function](#module\_dotnet8\_lambda\_function) | ../../ | n/a | +| [go\_lambda\_function](#module\_go\_lambda\_function) | ../../ | n/a | +| [java21\_lambda\_function](#module\_java21\_lambda\_function) | ../../ | n/a | +| [rust\_lambda\_function](#module\_rust\_lambda\_function) | ../../ | n/a | + +## Resources + +| Name | Type | +|------|------| +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | +| [aws_lambda_invocation.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/lambda_invocation) | data source | +| [http_http.this](https://registry.terraform.io/providers/hashicorp/http/latest/docs/data-sources/http) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [dotnet8\_lambda\_function\_url](#output\_dotnet8\_lambda\_function\_url) | The URL of the Lambda Function in .NET 8 | +| [go\_lambda\_function\_url](#output\_go\_lambda\_function\_url) | The URL of the Lambda Function in Go | +| [java21\_lambda\_function\_arn](#output\_java21\_lambda\_function\_arn) | The ARN of the Lambda Function in Java 21 | +| [lambda\_function\_result](#output\_lambda\_function\_result) | The results of the Lambda Function calls | +| [lambda\_function\_status\_codes](#output\_lambda\_function\_status\_codes) | The status codes of the Lambda Function calls | +| [rust\_lambda\_function\_url](#output\_rust\_lambda\_function\_url) | The URL of the Lambda Function in Rust | + diff --git a/examples/runtimes/checks.tf b/examples/runtimes/checks.tf new file mode 100644 index 00000000..ce95562f --- /dev/null +++ b/examples/runtimes/checks.tf @@ -0,0 +1,38 @@ +locals { + successful_response_keyword = "serverless.tf" +} + +data "http" "this" { + for_each = { + rust = module.rust_lambda_function.lambda_function_url, + go = module.go_lambda_function.lambda_function_url, + dotnet8 = module.dotnet8_lambda_function.lambda_function_url, + } + + url = each.value + + lifecycle { + postcondition { + condition = length(regexall(local.successful_response_keyword, self.response_body)) > 0 + error_message = "${each.key}: ${local.successful_response_keyword} should be in the response." + } + } +} + +# I don't know how to make Java21 example to work with Lambda Function URL, so using Lambda Function invocation instead +data "aws_lambda_invocation" "this" { + for_each = { + java21 = module.java21_lambda_function.lambda_function_name, + } + + function_name = each.value + + input = jsonencode({}) + + lifecycle { + postcondition { + condition = length(regexall(local.successful_response_keyword, jsondecode(self.result))) > 0 + error_message = "${each.key}: ${local.successful_response_keyword} should be in the response." + } + } +} diff --git a/examples/runtimes/main.tf b/examples/runtimes/main.tf new file mode 100644 index 00000000..960b0375 --- /dev/null +++ b/examples/runtimes/main.tf @@ -0,0 +1,127 @@ +provider "aws" { + region = "eu-west-1" +} + +module "rust_lambda_function" { + source = "../../" + + function_name = "${random_pet.this.id}-rust" + + attach_cloudwatch_logs_policy = false + cloudwatch_logs_retention_in_days = 1 + + create_lambda_function_url = true + + handler = "bootstrap" + runtime = "provided.al2023" + architectures = ["arm64"] # x86_64 (empty); arm64 (cargo lambda build --arm64) + + trigger_on_package_timestamp = false + + source_path = [ + { + path = "${path.module}/../fixtures/runtimes/rust" + commands = [ + # https://www.cargo-lambda.info/ + "cargo lambda build --release --arm64", + "cd target/lambda/rust-app1", + ":zip", + ] + patterns = [ + "!.*", + "bootstrap", + ] + } + ] +} + +module "go_lambda_function" { + source = "../../" + + function_name = "${random_pet.this.id}-go" + + attach_cloudwatch_logs_policy = false + cloudwatch_logs_retention_in_days = 1 + + create_lambda_function_url = true + + handler = "bootstrap" + runtime = "provided.al2023" + architectures = ["arm64"] # x86_64 (GOARCH=amd64); arm64 (GOARCH=arm64) + + trigger_on_package_timestamp = false + + source_path = [ + { + path = "${path.module}/../fixtures/runtimes/go" + commands = [ + "GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o bootstrap main.go", + ":zip", + ] + patterns = [ + "!.*", + "bootstrap", + ] + } + ] +} + +module "java21_lambda_function" { + source = "../../" + + function_name = "${random_pet.this.id}-java21" + + attach_cloudwatch_logs_policy = false + cloudwatch_logs_retention_in_days = 1 + + handler = "example.Handler" + runtime = "java21" + architectures = ["arm64"] # x86_64 or arm64 + timeout = 30 + + trigger_on_package_timestamp = false + + source_path = [ + { + path = "${path.module}/../fixtures/runtimes/java21" + commands = [ + "gradle build -i", + "cd build/output", + ":zip", + ] + } + ] +} + +module "dotnet8_lambda_function" { + source = "../../" + + function_name = "${random_pet.this.id}-dotnet8" + + attach_cloudwatch_logs_policy = false + cloudwatch_logs_retention_in_days = 1 + + create_lambda_function_url = true + + handler = "bootstrap" + runtime = "dotnet8" + architectures = ["arm64"] # x86_64 (--runtime linux-x64) or arm64 (--runtime linux-arm64) + timeout = 30 + + trigger_on_package_timestamp = false + + source_path = [ + { + path = "${path.module}/../fixtures/runtimes/dotnet8" + commands = [ + "dotnet publish --framework net8.0 --configuration Relesase --runtime linux-arm64 --output ./publish", + "cd publish", + ":zip", + ] + } + ] +} + +resource "random_pet" "this" { + length = 2 +} diff --git a/examples/runtimes/outputs.tf b/examples/runtimes/outputs.tf new file mode 100644 index 00000000..f3583367 --- /dev/null +++ b/examples/runtimes/outputs.tf @@ -0,0 +1,29 @@ +output "rust_lambda_function_url" { + description = "The URL of the Lambda Function in Rust" + value = module.rust_lambda_function.lambda_function_url +} + +output "go_lambda_function_url" { + description = "The URL of the Lambda Function in Go" + value = module.go_lambda_function.lambda_function_url +} + +output "java21_lambda_function_arn" { + description = "The ARN of the Lambda Function in Java 21" + value = module.java21_lambda_function.lambda_function_arn +} + +output "dotnet8_lambda_function_url" { + description = "The URL of the Lambda Function in .NET 8" + value = module.dotnet8_lambda_function.lambda_function_url +} + +output "lambda_function_result" { + description = "The results of the Lambda Function calls" + value = { for k, v in data.aws_lambda_invocation.this : k => jsondecode(v.result) } +} + +output "lambda_function_status_codes" { + description = "The status codes of the Lambda Function calls" + value = { for k, v in data.http.this : k => v.status_code } +} diff --git a/examples/runtimes/variables.tf b/examples/runtimes/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/runtimes/versions.tf b/examples/runtimes/versions.tf new file mode 100644 index 00000000..36f51034 --- /dev/null +++ b/examples/runtimes/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.0" + } + random = { + source = "hashicorp/random" + version = ">= 3.0" + } + http = { + source = "hashicorp/http" + version = ">= 3.0" + } + } +} diff --git a/examples/simple-cicd/.gitignore b/examples/simple-cicd/.gitignore new file mode 100644 index 00000000..a57582cc --- /dev/null +++ b/examples/simple-cicd/.gitignore @@ -0,0 +1 @@ +/src diff --git a/examples/simple-cicd/README.md b/examples/simple-cicd/README.md new file mode 100644 index 00000000..8afef1be --- /dev/null +++ b/examples/simple-cicd/README.md @@ -0,0 +1,53 @@ +# Simple CI/CD example + +Configuration in this directory creates AWS Lambda Function as it would run in a context of CICD executions, where the Terraform working directory is empty and there is no `builds` directory, that: + +- `terraform plan` doesn't trigger a diff if the source code of the lambda function didn't change. +- `terraform plan` does trigger a diff if the source code of the lambda function has changed. +- `terraform apply` works if the code has changed. + +## Usage + +To run this example you need to execute: + +```bash +./test.sh +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | +| [random](#requirement\_random) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [random](#provider\_random) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [lambda\_function](#module\_lambda\_function) | ../../ | n/a | + +## Resources + +| Name | Type | +|------|------| +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | + +## Inputs + +No inputs. + +## Outputs + +No outputs. + diff --git a/examples/simple-cicd/main.tf b/examples/simple-cicd/main.tf new file mode 100644 index 00000000..deefc9aa --- /dev/null +++ b/examples/simple-cicd/main.tf @@ -0,0 +1,25 @@ +provider "aws" { + region = "eu-west-1" + + # Make it faster by skipping something + skip_metadata_api_check = true + skip_region_validation = true + skip_credentials_validation = true +} + +resource "random_pet" "this" { + length = 2 +} + +module "lambda_function" { + source = "../../" + + function_name = "${random_pet.this.id}-lambda-simple" + handler = "index.lambda_handler" + runtime = "python3.12" + + source_path = [ + "${path.module}/src/python-app1", + ] + trigger_on_package_timestamp = false +} diff --git a/examples/simple-cicd/outputs.tf b/examples/simple-cicd/outputs.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/simple-cicd/test.sh b/examples/simple-cicd/test.sh new file mode 100755 index 00000000..64cfbf57 --- /dev/null +++ b/examples/simple-cicd/test.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +# vim:ts=4:sw=4:noet + +set -eo pipefail + +trap ctrl_c INT + +ctrl_c() { + echo "** Trapped CTRL-C" + exit 1 +} + +failed=0 + +:echo() { + local color=${2:-"33;1"} + echo -e "\e[${color}m$1\e[0m" +} + +:note() { + :echo "$1" "35;1" +} + +:case() { + if [ $? -ne 0 ] + then failed=1 + fi + + if [ "$failed" -eq 1 ] + then :echo "SKIPPED: $1"; return 1 + else echo; :echo "CASE: $1" + fi +} + +:check_diff() { + expected="$1" + + set +e + terraform plan -detailed-exitcode + status=$? + set -e + # ${status} possible values: + # 0 - Succeeded, diff is empty (no changes) + # 1 - Errored + # 2 - Succeeded, there is a diff + if [ "${status}" -ne "${expected}" ]; then + case "${expected}" in + 0) + :echo "Error: we don't expect any diff here!" + return 1 + ;; + 2) + echo "Error: we DO expect some diff here!" + return 1 + ;; + esac + fi +} + +terraform=$(which terraform) +terraform() { + $terraform "$@" < <(yes yes) +} + +:note "Preparing ..." +rm -rf src +mkdir -p src +cp -r "../fixtures/python-app1" src +terraform init +:echo "Destroy / Remove ZIP files" +terraform destroy +rm -rf builds 2>/dev/null || true + +############################################################# +# Part 1: Check that CICD environment won't detect any diff # +############################################################# + +:echo +:note "Starting Part 1: Check that CICD environment won't detect any diff" + +:case "Apply / No diff" && { + terraform apply + :check_diff 0 +} + +:case "Remove 'builds' dir / No diff" && { + rm -rf builds + :check_diff 0 +} + +############################################################################### +# Part 2: Check that CICD environment will detect diff if lambda code changes # +############################################################################### + +:echo +:note "Starting Part 2: Check that CICD environment will detect diff if lambda code changes" + +:note "Change the source code / Remove 'builds' dir" +echo "" >> src/python-app1/index.py +rm -rf builds + +:case "Plan / Expect diff" && { + terraform plan + :check_diff 2 +} + +:case "Apply / No diff" && { + terraform apply + :check_diff 0 +} + +:note "Remove 'builds' dir" +rm -rf builds + +:case "Plan / No diff" && { + terraform plan + :check_diff 0 +} + +#:case "Destroy / Remove ZIP files" && { +# terraform plan -destroy +# terraform destroy -auto-approve +# rm builds/*.zip +#} + +:note "All tests have passed successfully." diff --git a/examples/simple-cicd/variables.tf b/examples/simple-cicd/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/simple-cicd/versions.tf b/examples/simple-cicd/versions.tf new file mode 100644 index 00000000..d2f4f3e8 --- /dev/null +++ b/examples/simple-cicd/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.0" + } + random = { + source = "hashicorp/random" + version = ">= 2.0" + } + } +} diff --git a/examples/simple/README.md b/examples/simple/README.md index 77e5c967..b9a4f785 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -14,13 +14,13 @@ $ terraform apply Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. - + ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 3.19 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | | [random](#requirement\_random) | >= 2.0 | ## Providers @@ -69,4 +69,4 @@ No inputs. | [lambda\_role\_name](#output\_lambda\_role\_name) | The name of the IAM role created for the Lambda Function | | [local\_filename](#output\_local\_filename) | The filename of zip archive deployed (if deployment was from local) | | [s3\_object](#output\_s3\_object) | The map with S3 object data of zip archive deployed (if deployment was from S3) | - + diff --git a/examples/simple/main.tf b/examples/simple/main.tf index 3bff4e4d..20c51910 100644 --- a/examples/simple/main.tf +++ b/examples/simple/main.tf @@ -3,11 +3,9 @@ provider "aws" { # region = "us-east-1" # Make it faster by skipping something - skip_get_ec2_platforms = true skip_metadata_api_check = true skip_region_validation = true skip_credentials_validation = true - skip_requesting_account_id = true } resource "random_pet" "this" { @@ -19,12 +17,12 @@ resource "random_pet" "this" { # # function_name = "${random_pet.this.id}-lambda-edge" # handler = "index.lambda_handler" -# runtime = "python3.8" +# runtime = "python3.12" # lambda_at_edge = true # # attach_cloudwatch_logs_policy = true # -# source_path = "${path.module}/../fixtures/python3.8-app1/" +# source_path = "${path.module}/../fixtures/python-app1/" #} #resource "aws_cloudwatch_log_group" "this" { @@ -38,7 +36,9 @@ module "lambda_function" { function_name = "${random_pet.this.id}-lambda-simple" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" + + # role_maximum_session_duration = 7200 # attach_cloudwatch_logs_policy = false @@ -54,6 +54,8 @@ module "lambda_function" { # create_package = false # local_existing_package = data.null_data_source.downloaded_package.outputs["filename"] + # snap_start = true + # policy_json = < + ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 2.67 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | | [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 2.67 | +| [aws](#provider\_aws) | >= 6.0 | | [random](#provider\_random) | >= 2.0 | ## Modules @@ -73,4 +73,4 @@ No inputs. | [lambda\_role\_name](#output\_lambda\_role\_name) | The name of the IAM role created for the Lambda Function | | [local\_filename](#output\_local\_filename) | The filename of zip archive deployed (if deployment was from local) | | [s3\_object](#output\_s3\_object) | The map with S3 object data of zip archive deployed (if deployment was from S3) | - + diff --git a/examples/triggers/main.tf b/examples/triggers/main.tf index b207c075..8317a81a 100644 --- a/examples/triggers/main.tf +++ b/examples/triggers/main.tf @@ -2,11 +2,9 @@ provider "aws" { region = "eu-west-1" # Make it faster by skipping something - skip_get_ec2_platforms = true skip_metadata_api_check = true skip_region_validation = true skip_credentials_validation = true - skip_requesting_account_id = true } ########################################## @@ -19,11 +17,11 @@ module "lambda_function" { function_name = "${random_pet.this.id}-lambda-triggers" description = "My awesome lambda function" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" publish = true create_package = false - local_existing_package = "${path.module}/../fixtures/python3.8-zip/existing_package.zip" + local_existing_package = "${path.module}/../fixtures/python-zip/existing_package.zip" allowed_triggers = { ScanAmiRule = { diff --git a/examples/triggers/versions.tf b/examples/triggers/versions.tf index 13f523bb..d2f4f3e8 100644 --- a/examples/triggers/versions.tf +++ b/examples/triggers/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 2.67" + version = ">= 6.0" } random = { source = "hashicorp/random" diff --git a/examples/with-efs/README.md b/examples/with-efs/README.md index 3b83e41f..ce9cc15e 100644 --- a/examples/with-efs/README.md +++ b/examples/with-efs/README.md @@ -15,20 +15,20 @@ $ terraform apply Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. - + ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 3.19 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | | [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 3.19 | +| [aws](#provider\_aws) | >= 6.0 | | [random](#provider\_random) | >= 2.0 | ## Modules @@ -36,7 +36,7 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Source | Version | |------|--------|---------| | [lambda\_function\_with\_efs](#module\_lambda\_function\_with\_efs) | ../../ | n/a | -| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | ## Resources @@ -75,4 +75,4 @@ No inputs. | [lambda\_role\_name](#output\_lambda\_role\_name) | The name of the IAM role created for the Lambda Function | | [local\_filename](#output\_local\_filename) | The filename of zip archive deployed (if deployment was from local) | | [s3\_object](#output\_s3\_object) | The map with S3 object data of zip archive deployed (if deployment was from S3) | - + diff --git a/examples/with-efs/main.tf b/examples/with-efs/main.tf index 98d3a89a..90a0abed 100644 --- a/examples/with-efs/main.tf +++ b/examples/with-efs/main.tf @@ -2,11 +2,9 @@ provider "aws" { region = "eu-west-1" # Make it faster by skipping something - skip_get_ec2_platforms = true skip_metadata_api_check = true skip_region_validation = true skip_credentials_validation = true - skip_requesting_account_id = true } resource "random_pet" "this" { @@ -19,9 +17,9 @@ module "lambda_function_with_efs" { function_name = "${random_pet.this.id}-lambda-in-vpc" description = "My awesome lambda function" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" - source_path = "${path.module}/../fixtures/python3.8-app1" + source_path = "${path.module}/../fixtures/python-app1" vpc_subnet_ids = module.vpc.intra_subnets vpc_security_group_ids = [module.vpc.default_security_group_id] @@ -45,7 +43,8 @@ module "lambda_function_with_efs" { ###### module "vpc" { - source = "terraform-aws-modules/vpc/aws" + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.0" name = random_pet.this.id cidr = "10.10.0.0/16" diff --git a/examples/with-efs/versions.tf b/examples/with-efs/versions.tf index 6f0e3af3..d2f4f3e8 100644 --- a/examples/with-efs/versions.tf +++ b/examples/with-efs/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 3.19" + version = ">= 6.0" } random = { source = "hashicorp/random" diff --git a/examples/with-vpc-s3-endpoint/README.md b/examples/with-vpc-s3-endpoint/README.md index e2e43a6e..f84ba32c 100644 --- a/examples/with-vpc-s3-endpoint/README.md +++ b/examples/with-vpc-s3-endpoint/README.md @@ -16,20 +16,20 @@ $ terraform apply Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. - + ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.14 | -| [aws](#requirement\_aws) | >= 4.33 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | | [random](#requirement\_random) | >= 3.4 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.33 | +| [aws](#provider\_aws) | >= 6.0 | | [random](#provider\_random) | >= 3.4 | ## Modules @@ -38,10 +38,10 @@ Note that this example may create resources which cost money. Run `terraform des |------|--------|---------| | [kms](#module\_kms) | terraform-aws-modules/kms/aws | ~> 1.0 | | [lambda\_s3\_write](#module\_lambda\_s3\_write) | ../../ | n/a | -| [s3\_bucket](#module\_s3\_bucket) | terraform-aws-modules/s3-bucket/aws | ~> 3.0 | +| [s3\_bucket](#module\_s3\_bucket) | terraform-aws-modules/s3-bucket/aws | ~> 5.0 | | [security\_group\_lambda](#module\_security\_group\_lambda) | terraform-aws-modules/security-group/aws | ~> 4.0 | -| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 3.0 | -| [vpc\_endpoints](#module\_vpc\_endpoints) | terraform-aws-modules/vpc/aws//modules/vpc-endpoints | ~> 3.0 | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | +| [vpc\_endpoints](#module\_vpc\_endpoints) | terraform-aws-modules/vpc/aws//modules/vpc-endpoints | ~> 5.0 | ## Resources @@ -81,4 +81,4 @@ No inputs. | [lambda\_role\_name](#output\_lambda\_role\_name) | The name of the IAM role created for the Lambda Function | | [local\_filename](#output\_local\_filename) | The filename of zip archive deployed (if deployment was from local) | | [s3\_object](#output\_s3\_object) | The map with S3 object data of zip archive deployed (if deployment was from S3) | - + diff --git a/examples/with-vpc-s3-endpoint/main.tf b/examples/with-vpc-s3-endpoint/main.tf index e574a06a..29de6eba 100644 --- a/examples/with-vpc-s3-endpoint/main.tf +++ b/examples/with-vpc-s3-endpoint/main.tf @@ -2,11 +2,9 @@ provider "aws" { region = "eu-west-1" # Make it faster by skipping something - skip_get_ec2_platforms = true skip_metadata_api_check = true skip_region_validation = true skip_credentials_validation = true - skip_requesting_account_id = true } data "aws_region" "current" {} @@ -22,13 +20,13 @@ module "lambda_s3_write" { function_name = random_pet.this.id handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" - source_path = "${path.module}/../fixtures/python3.8-app2" + source_path = "${path.module}/../fixtures/python-app2" environment_variables = { BUCKET_NAME = module.s3_bucket.s3_bucket_id - REGION_NAME = data.aws_region.current.name + REGION_NAME = data.aws_region.current.region } # Let the module create a role for us @@ -56,17 +54,17 @@ resource "random_pet" "this" { } data "aws_ec2_managed_prefix_list" "this" { - name = "com.amazonaws.${data.aws_region.current.name}.s3" + name = "com.amazonaws.${data.aws_region.current.region}.s3" } module "vpc" { source = "terraform-aws-modules/vpc/aws" - version = "~> 3.0" + version = "~> 5.0" name = random_pet.this.id cidr = "10.0.0.0/16" - azs = ["${data.aws_region.current.name}a", "${data.aws_region.current.name}b", "${data.aws_region.current.name}c"] + azs = ["${data.aws_region.current.region}a", "${data.aws_region.current.region}b", "${data.aws_region.current.region}c"] # Intra subnets are designed to have no Internet access via NAT Gateway. intra_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] @@ -103,7 +101,7 @@ module "vpc" { module "vpc_endpoints" { source = "terraform-aws-modules/vpc/aws//modules/vpc-endpoints" - version = "~> 3.0" + version = "~> 5.0" vpc_id = module.vpc.vpc_id @@ -162,7 +160,7 @@ module "kms" { module "s3_bucket" { source = "terraform-aws-modules/s3-bucket/aws" - version = "~> 3.0" + version = "~> 5.0" bucket_prefix = "${random_pet.this.id}-" force_destroy = true diff --git a/examples/with-vpc-s3-endpoint/versions.tf b/examples/with-vpc-s3-endpoint/versions.tf index 6941462b..7f27783c 100644 --- a/examples/with-vpc-s3-endpoint/versions.tf +++ b/examples/with-vpc-s3-endpoint/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.14" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.33" + version = ">= 6.0" } random = { source = "hashicorp/random" diff --git a/examples/with-vpc/README.md b/examples/with-vpc/README.md index af792807..e1808811 100644 --- a/examples/with-vpc/README.md +++ b/examples/with-vpc/README.md @@ -16,13 +16,13 @@ $ terraform apply Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. - + ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 3.19 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | | [random](#requirement\_random) | >= 2.0 | ## Providers @@ -36,7 +36,7 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Source | Version | |------|--------|---------| | [lambda\_function\_in\_vpc](#module\_lambda\_function\_in\_vpc) | ../../ | n/a | -| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | ## Resources @@ -72,4 +72,4 @@ No inputs. | [lambda\_role\_name](#output\_lambda\_role\_name) | The name of the IAM role created for the Lambda Function | | [local\_filename](#output\_local\_filename) | The filename of zip archive deployed (if deployment was from local) | | [s3\_object](#output\_s3\_object) | The map with S3 object data of zip archive deployed (if deployment was from S3) | - + diff --git a/examples/with-vpc/main.tf b/examples/with-vpc/main.tf index b791fc5e..d373d724 100644 --- a/examples/with-vpc/main.tf +++ b/examples/with-vpc/main.tf @@ -2,11 +2,9 @@ provider "aws" { region = "eu-west-1" # Make it faster by skipping something - skip_get_ec2_platforms = true skip_metadata_api_check = true skip_region_validation = true skip_credentials_validation = true - skip_requesting_account_id = true } resource "random_pet" "this" { @@ -19,17 +17,20 @@ module "lambda_function_in_vpc" { function_name = "${random_pet.this.id}-lambda-in-vpc" description = "My awesome lambda function" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" - source_path = "${path.module}/../fixtures/python3.8-app1" + source_path = "${path.module}/../fixtures/python-app1" - vpc_subnet_ids = module.vpc.intra_subnets - vpc_security_group_ids = [module.vpc.default_security_group_id] - attach_network_policy = true + vpc_subnet_ids = module.vpc.intra_subnets + vpc_security_group_ids = [module.vpc.default_security_group_id] + attach_network_policy = true + replace_security_groups_on_destroy = true + replacement_security_group_ids = [module.vpc.default_security_group_id] } module "vpc" { - source = "terraform-aws-modules/vpc/aws" + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.0" name = random_pet.this.id cidr = "10.10.0.0/16" diff --git a/examples/with-vpc/versions.tf b/examples/with-vpc/versions.tf index 6f0e3af3..d2f4f3e8 100644 --- a/examples/with-vpc/versions.tf +++ b/examples/with-vpc/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 3.19" + version = ">= 6.0" } random = { source = "hashicorp/random" diff --git a/iam.tf b/iam.tf index 91feaa9e..8b0440e1 100644 --- a/iam.tf +++ b/iam.tf @@ -5,7 +5,7 @@ locals { # The region part of the LogGroup ARN is then replaced with a wildcard (*) so Lambda@Edge is able to log in every region log_group_arn_regional = try(data.aws_cloudwatch_log_group.lambda[0].arn, aws_cloudwatch_log_group.lambda[0].arn, "") log_group_name = try(data.aws_cloudwatch_log_group.lambda[0].name, aws_cloudwatch_log_group.lambda[0].name, "") - log_group_arn = local.create_role && var.lambda_at_edge ? format("arn:%s:%s:%s:%s:%s", data.aws_arn.log_group_arn[0].partition, data.aws_arn.log_group_arn[0].service, "*", data.aws_arn.log_group_arn[0].account, data.aws_arn.log_group_arn[0].resource) : local.log_group_arn_regional + log_group_arn = local.create_role && var.lambda_at_edge ? format("arn:%s:%s:%s:%s:%s", data.aws_arn.log_group_arn[0].partition, data.aws_arn.log_group_arn[0].service, var.lambda_at_edge_logs_all_regions ? "*" : "us-east-1", data.aws_arn.log_group_arn[0].account, data.aws_arn.log_group_arn[0].resource) : local.log_group_arn_regional # Defaulting to "*" (an invalid character for an IAM Role name) will cause an error when # attempting to plan if the role_name and function_name are not set. This is a workaround @@ -100,6 +100,7 @@ resource "aws_iam_role" "lambda" { force_detach_policies = var.role_force_detach_policies permissions_boundary = var.role_permissions_boundary assume_role_policy = data.aws_iam_policy_document.assume_role[0].json + max_session_duration = var.role_maximum_session_duration tags = merge(var.tags, var.role_tags) } @@ -121,7 +122,7 @@ data "aws_iam_policy_document" "logs" { effect = "Allow" actions = compact([ - !var.use_existing_cloudwatch_log_group ? "logs:CreateLogGroup" : "", + !var.use_existing_cloudwatch_log_group && var.attach_create_log_group_permission ? "logs:CreateLogGroup" : "", "logs:CreateLogStream", "logs:PutLogEvents" ]) @@ -130,20 +131,12 @@ data "aws_iam_policy_document" "logs" { } } -resource "aws_iam_policy" "logs" { +resource "aws_iam_role_policy" "logs" { count = local.create_role && var.attach_cloudwatch_logs_policy ? 1 : 0 name = "${local.policy_name}-logs" - path = var.policy_path + role = aws_iam_role.lambda[0].name policy = data.aws_iam_policy_document.logs[0].json - tags = var.tags -} - -resource "aws_iam_role_policy_attachment" "logs" { - count = local.create_role && var.attach_cloudwatch_logs_policy ? 1 : 0 - - role = aws_iam_role.lambda[0].name - policy_arn = aws_iam_policy.logs[0].arn } ##################### @@ -167,20 +160,12 @@ data "aws_iam_policy_document" "dead_letter" { } } -resource "aws_iam_policy" "dead_letter" { +resource "aws_iam_role_policy" "dead_letter" { count = local.create_role && var.attach_dead_letter_policy ? 1 : 0 name = "${local.policy_name}-dl" - path = var.policy_path + role = aws_iam_role.lambda[0].name policy = data.aws_iam_policy_document.dead_letter[0].json - tags = var.tags -} - -resource "aws_iam_role_policy_attachment" "dead_letter" { - count = local.create_role && var.attach_dead_letter_policy ? 1 : 0 - - role = aws_iam_role.lambda[0].name - policy_arn = aws_iam_policy.dead_letter[0].arn } ###### @@ -194,20 +179,12 @@ data "aws_iam_policy" "vpc" { arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AWSLambdaENIManagementAccess" } -resource "aws_iam_policy" "vpc" { +resource "aws_iam_role_policy" "vpc" { count = local.create_role && var.attach_network_policy ? 1 : 0 name = "${local.policy_name}-vpc" - path = var.policy_path + role = aws_iam_role.lambda[0].name policy = data.aws_iam_policy.vpc[0].policy - tags = var.tags -} - -resource "aws_iam_role_policy_attachment" "vpc" { - count = local.create_role && var.attach_network_policy ? 1 : 0 - - role = aws_iam_role.lambda[0].name - policy_arn = aws_iam_policy.vpc[0].arn } ##################### @@ -221,20 +198,12 @@ data "aws_iam_policy" "tracing" { arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AWSXRayDaemonWriteAccess" } -resource "aws_iam_policy" "tracing" { +resource "aws_iam_role_policy" "tracing" { count = local.create_role && var.attach_tracing_policy ? 1 : 0 name = "${local.policy_name}-tracing" - path = var.policy_path + role = aws_iam_role.lambda[0].name policy = data.aws_iam_policy.tracing[0].policy - tags = var.tags -} - -resource "aws_iam_role_policy_attachment" "tracing" { - count = local.create_role && var.attach_tracing_policy ? 1 : 0 - - role = aws_iam_role.lambda[0].name - policy_arn = aws_iam_policy.tracing[0].arn } ############################### @@ -258,60 +227,36 @@ data "aws_iam_policy_document" "async" { } } -resource "aws_iam_policy" "async" { +resource "aws_iam_role_policy" "async" { count = local.create_role && var.attach_async_event_policy ? 1 : 0 name = "${local.policy_name}-async" - path = var.policy_path + role = aws_iam_role.lambda[0].name policy = data.aws_iam_policy_document.async[0].json - tags = var.tags -} - -resource "aws_iam_role_policy_attachment" "async" { - count = local.create_role && var.attach_async_event_policy ? 1 : 0 - - role = aws_iam_role.lambda[0].name - policy_arn = aws_iam_policy.async[0].arn } ########################### # Additional policy (JSON) ########################### -resource "aws_iam_policy" "additional_json" { +resource "aws_iam_role_policy" "additional_json" { count = local.create_role && var.attach_policy_json ? 1 : 0 name = local.policy_name - path = var.policy_path + role = aws_iam_role.lambda[0].name policy = var.policy_json - tags = var.tags -} - -resource "aws_iam_role_policy_attachment" "additional_json" { - count = local.create_role && var.attach_policy_json ? 1 : 0 - - role = aws_iam_role.lambda[0].name - policy_arn = aws_iam_policy.additional_json[0].arn } ##################################### # Additional policies (list of JSON) ##################################### -resource "aws_iam_policy" "additional_jsons" { +resource "aws_iam_role_policy" "additional_jsons" { count = local.create_role && var.attach_policy_jsons ? var.number_of_policy_jsons : 0 name = "${local.policy_name}-${count.index}" - path = var.policy_path + role = aws_iam_role.lambda[0].name policy = var.policy_jsons[count.index] - tags = var.tags -} - -resource "aws_iam_role_policy_attachment" "additional_jsons" { - count = local.create_role && var.attach_policy_jsons ? var.number_of_policy_jsons : 0 - - role = aws_iam_role.lambda[0].name - policy_arn = aws_iam_policy.additional_jsons[count.index].arn } ########################### @@ -382,18 +327,10 @@ data "aws_iam_policy_document" "additional_inline" { } } -resource "aws_iam_policy" "additional_inline" { +resource "aws_iam_role_policy" "additional_inline" { count = local.create_role && var.attach_policy_statements ? 1 : 0 name = "${local.policy_name}-inline" - path = var.policy_path + role = aws_iam_role.lambda[0].name policy = data.aws_iam_policy_document.additional_inline[0].json - tags = var.tags -} - -resource "aws_iam_role_policy_attachment" "additional_inline" { - count = local.create_role && var.attach_policy_statements ? 1 : 0 - - role = aws_iam_role.lambda[0].name - policy_arn = aws_iam_policy.additional_inline[0].arn } diff --git a/main.tf b/main.tf index 2223152c..cc7d011a 100644 --- a/main.tf +++ b/main.tf @@ -24,21 +24,26 @@ locals { resource "aws_lambda_function" "this" { count = local.create && var.create_function && !var.create_layer ? 1 : 0 - function_name = var.function_name - description = var.description - role = var.create_role ? aws_iam_role.lambda[0].arn : var.lambda_role - handler = var.package_type != "Zip" ? null : var.handler - memory_size = var.memory_size - reserved_concurrent_executions = var.reserved_concurrent_executions - runtime = var.package_type != "Zip" ? null : var.runtime - layers = var.layers - timeout = var.lambda_at_edge ? min(var.timeout, 30) : var.timeout - publish = var.lambda_at_edge ? true : var.publish - kms_key_arn = var.kms_key_arn - image_uri = var.image_uri - package_type = var.package_type - architectures = var.architectures - code_signing_config_arn = var.code_signing_config_arn + region = var.region + + function_name = var.function_name + description = var.description + role = var.create_role ? aws_iam_role.lambda[0].arn : var.lambda_role + handler = var.package_type != "Zip" ? null : var.handler + memory_size = var.memory_size + reserved_concurrent_executions = var.reserved_concurrent_executions + runtime = var.package_type != "Zip" ? null : var.runtime + layers = var.layers + timeout = var.lambda_at_edge ? min(var.timeout, 30) : var.timeout + publish = (var.lambda_at_edge || var.snap_start) ? true : var.publish + kms_key_arn = var.kms_key_arn + image_uri = var.image_uri + package_type = var.package_type + architectures = var.architectures + code_signing_config_arn = var.code_signing_config_arn + replace_security_groups_on_destroy = var.replace_security_groups_on_destroy + replacement_security_group_ids = var.replacement_security_group_ids + skip_destroy = var.skip_destroy /* ephemeral_storage is not supported in gov-cloud region, so it should be set to `null` */ dynamic "ephemeral_storage" { @@ -89,8 +94,9 @@ resource "aws_lambda_function" "this" { dynamic "vpc_config" { for_each = var.vpc_subnet_ids != null && var.vpc_security_group_ids != null ? [true] : [] content { - security_group_ids = var.vpc_security_group_ids - subnet_ids = var.vpc_subnet_ids + security_group_ids = var.vpc_security_group_ids + subnet_ids = var.vpc_subnet_ids + ipv6_allowed_for_dual_stack = var.ipv6_allowed_for_dual_stack } } @@ -102,7 +108,42 @@ resource "aws_lambda_function" "this" { } } - tags = var.tags + dynamic "snap_start" { + for_each = var.snap_start ? [true] : [] + + content { + apply_on = "PublishedVersions" + } + } + + dynamic "logging_config" { + # Dont create logging config on gov cloud as it is not avaible. + # See https://github.com/hashicorp/terraform-provider-aws/issues/34810 + for_each = data.aws_partition.current.partition == "aws" ? [true] : [] + + content { + log_group = var.logging_log_group + log_format = var.logging_log_format + application_log_level = var.logging_log_format == "Text" ? null : var.logging_application_log_level + system_log_level = var.logging_log_format == "Text" ? null : var.logging_system_log_level + } + } + + dynamic "timeouts" { + for_each = length(var.timeouts) > 0 ? [true] : [] + + content { + create = try(var.timeouts.create, null) + update = try(var.timeouts.update, null) + delete = try(var.timeouts.delete, null) + } + } + + tags = merge( + var.include_default_tag ? { terraform-aws-modules = "lambda" } : {}, + var.tags, + var.function_tags + ) depends_on = [ null_resource.archive, @@ -115,27 +156,29 @@ resource "aws_lambda_function" "this" { aws_cloudwatch_log_group.lambda, # Before the lambda is created the execution role with all its policies should be ready - aws_iam_role_policy_attachment.additional_inline, - aws_iam_role_policy_attachment.additional_json, - aws_iam_role_policy_attachment.additional_jsons, + aws_iam_role_policy.additional_inline, + aws_iam_role_policy.additional_json, + aws_iam_role_policy.additional_jsons, + aws_iam_role_policy.async, + aws_iam_role_policy.dead_letter, + aws_iam_role_policy.logs, + aws_iam_role_policy.tracing, + aws_iam_role_policy.vpc, aws_iam_role_policy_attachment.additional_many, aws_iam_role_policy_attachment.additional_one, - aws_iam_role_policy_attachment.async, - aws_iam_role_policy_attachment.logs, - aws_iam_role_policy_attachment.dead_letter, - aws_iam_role_policy_attachment.vpc, - aws_iam_role_policy_attachment.tracing, ] } resource "aws_lambda_layer_version" "this" { count = local.create && var.create_layer ? 1 : 0 + region = var.region + layer_name = var.layer_name description = var.description license_info = var.license_info - compatible_runtimes = length(var.compatible_runtimes) > 0 ? var.compatible_runtimes : [var.runtime] + compatible_runtimes = length(var.compatible_runtimes) > 0 ? var.compatible_runtimes : (var.runtime == "" ? null : [var.runtime]) compatible_architectures = var.compatible_architectures skip_destroy = var.layer_skip_destroy @@ -152,6 +195,8 @@ resource "aws_lambda_layer_version" "this" { resource "aws_s3_object" "lambda_package" { count = local.create && var.store_on_s3 && var.create_package ? 1 : 0 + region = var.region + bucket = var.s3_bucket acl = var.s3_acl key = local.s3_key @@ -159,24 +204,41 @@ resource "aws_s3_object" "lambda_package" { storage_class = var.s3_object_storage_class server_side_encryption = var.s3_server_side_encryption + kms_key_id = var.s3_kms_key_id tags = var.s3_object_tags_only ? var.s3_object_tags : merge(var.tags, var.s3_object_tags) + dynamic "override_provider" { + for_each = var.s3_object_override_default_tags ? [true] : [] + + content { + default_tags { + tags = {} + } + } + } + depends_on = [null_resource.archive] } data "aws_cloudwatch_log_group" "lambda" { count = local.create && var.create_function && !var.create_layer && var.use_existing_cloudwatch_log_group ? 1 : 0 - name = "/aws/lambda/${var.lambda_at_edge ? "us-east-1." : ""}${var.function_name}" + region = var.region + + name = coalesce(var.logging_log_group, "/aws/lambda/${var.lambda_at_edge ? "us-east-1." : ""}${var.function_name}") } resource "aws_cloudwatch_log_group" "lambda" { count = local.create && var.create_function && !var.create_layer && !var.use_existing_cloudwatch_log_group ? 1 : 0 - name = "/aws/lambda/${var.lambda_at_edge ? "us-east-1." : ""}${var.function_name}" + region = var.region + + name = coalesce(var.logging_log_group, "/aws/lambda/${var.lambda_at_edge ? "us-east-1." : ""}${var.function_name}") retention_in_days = var.cloudwatch_logs_retention_in_days kms_key_id = var.cloudwatch_logs_kms_key_id + skip_destroy = var.cloudwatch_logs_skip_destroy + log_group_class = var.cloudwatch_logs_log_group_class tags = merge(var.tags, var.cloudwatch_logs_tags) } @@ -184,6 +246,8 @@ resource "aws_cloudwatch_log_group" "lambda" { resource "aws_lambda_provisioned_concurrency_config" "current_version" { count = local.create && var.create_function && !var.create_layer && var.provisioned_concurrent_executions > -1 ? 1 : 0 + region = var.region + function_name = aws_lambda_function.this[0].function_name qualifier = aws_lambda_function.this[0].version @@ -197,6 +261,8 @@ locals { resource "aws_lambda_function_event_invoke_config" "this" { for_each = { for k, v in local.qualifiers : k => v if v != null && local.create && var.create_function && !var.create_layer && var.create_async_event_config } + region = var.region + function_name = aws_lambda_function.this[0].function_name qualifier = each.key == "current_version" ? aws_lambda_function.this[0].version : null @@ -226,34 +292,52 @@ resource "aws_lambda_function_event_invoke_config" "this" { resource "aws_lambda_permission" "current_version_triggers" { for_each = { for k, v in var.allowed_triggers : k => v if local.create && var.create_function && !var.create_layer && var.create_current_version_allowed_triggers } + region = var.region + function_name = aws_lambda_function.this[0].function_name qualifier = aws_lambda_function.this[0].version - statement_id = try(each.value.statement_id, each.key) - action = try(each.value.action, "lambda:InvokeFunction") - principal = try(each.value.principal, format("%s.amazonaws.com", try(each.value.service, ""))) - source_arn = try(each.value.source_arn, null) - source_account = try(each.value.source_account, null) - event_source_token = try(each.value.event_source_token, null) + statement_id_prefix = try(each.value.statement_id, each.key) + action = try(each.value.action, "lambda:InvokeFunction") + principal = try(each.value.principal, format("%s.amazonaws.com", try(each.value.service, ""))) + principal_org_id = try(each.value.principal_org_id, null) + source_arn = try(each.value.source_arn, null) + source_account = try(each.value.source_account, null) + event_source_token = try(each.value.event_source_token, null) + function_url_auth_type = try(each.value.function_url_auth_type, null) + + lifecycle { + create_before_destroy = true + } } # Error: Error adding new Lambda Permission for lambda: InvalidParameterValueException: We currently do not support adding policies for $LATEST. resource "aws_lambda_permission" "unqualified_alias_triggers" { for_each = { for k, v in var.allowed_triggers : k => v if local.create && var.create_function && !var.create_layer && var.create_unqualified_alias_allowed_triggers } + region = var.region + function_name = aws_lambda_function.this[0].function_name - statement_id = try(each.value.statement_id, each.key) - action = try(each.value.action, "lambda:InvokeFunction") - principal = try(each.value.principal, format("%s.amazonaws.com", try(each.value.service, ""))) - source_arn = try(each.value.source_arn, null) - source_account = try(each.value.source_account, null) - event_source_token = try(each.value.event_source_token, null) + statement_id_prefix = try(each.value.statement_id, each.key) + action = try(each.value.action, "lambda:InvokeFunction") + principal = try(each.value.principal, format("%s.amazonaws.com", try(each.value.service, ""))) + principal_org_id = try(each.value.principal_org_id, null) + source_arn = try(each.value.source_arn, null) + source_account = try(each.value.source_account, null) + event_source_token = try(each.value.event_source_token, null) + function_url_auth_type = try(each.value.function_url_auth_type, null) + + lifecycle { + create_before_destroy = true + } } resource "aws_lambda_event_source_mapping" "this" { for_each = { for k, v in var.event_source_mapping : k => v if local.create && var.create_function && !var.create_layer && var.create_unqualified_alias_allowed_triggers } + region = var.region + function_name = aws_lambda_function.this[0].arn event_source_arn = try(each.value.event_source_arn, null) @@ -270,6 +354,7 @@ resource "aws_lambda_event_source_mapping" "this" { topics = try(each.value.topics, null) queues = try(each.value.queues, null) function_response_types = try(each.value.function_response_types, null) + tumbling_window_in_seconds = try(each.value.tumbling_window_in_seconds, null) dynamic "destination_config" { for_each = try(each.value.destination_arn_on_failure, null) != null ? [true] : [] @@ -280,6 +365,14 @@ resource "aws_lambda_event_source_mapping" "this" { } } + dynamic "scaling_config" { + for_each = try([each.value.scaling_config], []) + content { + maximum_concurrency = try(scaling_config.value.maximum_concurrency, null) + } + } + + dynamic "self_managed_event_source" { for_each = try(each.value.self_managed_event_source, []) content { @@ -287,6 +380,19 @@ resource "aws_lambda_event_source_mapping" "this" { } } + dynamic "self_managed_kafka_event_source_config" { + for_each = try(each.value.self_managed_kafka_event_source_config, []) + content { + consumer_group_id = self_managed_kafka_event_source_config.value.consumer_group_id + } + } + dynamic "amazon_managed_kafka_event_source_config" { + for_each = try(each.value.amazon_managed_kafka_event_source_config, []) + content { + consumer_group_id = amazon_managed_kafka_event_source_config.value.consumer_group_id + } + } + dynamic "source_access_configuration" { for_each = try(each.value.source_access_configuration, []) content { @@ -299,21 +405,56 @@ resource "aws_lambda_event_source_mapping" "this" { for_each = try(each.value.filter_criteria, null) != null ? [true] : [] content { - filter { - pattern = try(each.value["filter_criteria"].pattern, null) + dynamic "filter" { + for_each = try(flatten([each.value.filter_criteria]), []) + + content { + pattern = try(filter.value.pattern, null) + } } } } + + dynamic "document_db_event_source_config" { + for_each = try(each.value.document_db_event_source_config, []) + + content { + database_name = document_db_event_source_config.value.database_name + collection_name = try(document_db_event_source_config.value.collection_name, null) + full_document = try(document_db_event_source_config.value.full_document, null) + } + } + + dynamic "metrics_config" { + for_each = try([each.value.metrics_config], []) + + content { + metrics = metrics_config.value.metrics + } + } + + dynamic "provisioned_poller_config" { + for_each = try([each.value.provisioned_poller_config], []) + content { + maximum_pollers = try(provisioned_poller_config.value.maximum_pollers, null) + minimum_pollers = try(provisioned_poller_config.value.minimum_pollers, null) + } + } + + tags = merge(var.tags, try(each.value.tags, {})) } resource "aws_lambda_function_url" "this" { count = local.create && var.create_function && !var.create_layer && var.create_lambda_function_url ? 1 : 0 + region = var.region + function_name = aws_lambda_function.this[0].function_name # Error: error creating Lambda Function URL: ValidationException qualifier = var.create_unqualified_alias_lambda_function_url ? null : aws_lambda_function.this[0].version authorization_type = var.authorization_type + invoke_mode = var.invoke_mode dynamic "cors" { for_each = length(keys(var.cors)) == 0 ? [] : [var.cors] @@ -329,11 +470,20 @@ resource "aws_lambda_function_url" "this" { } } +resource "aws_lambda_function_recursion_config" "this" { + count = local.create && var.create_function && !var.create_layer && var.recursive_loop == "Allow" ? 1 : 0 + + region = var.region + + function_name = aws_lambda_function.this[0].function_name + recursive_loop = var.recursive_loop +} + # This resource contains the extra information required by SAM CLI to provide the testing capabilities # to the TF application. The required data is where SAM CLI can find the Lambda function source code # and what are the resources that contain the building logic. resource "null_resource" "sam_metadata_aws_lambda_function" { - count = local.create && var.create_package && var.create_function && !var.create_layer ? 1 : 0 + count = local.create && var.create_sam_metadata && var.create_package && var.create_function && !var.create_layer ? 1 : 0 triggers = { # This is a way to let SAM CLI correlates between the Lambda function resource, and this metadata @@ -361,7 +511,7 @@ resource "null_resource" "sam_metadata_aws_lambda_function" { # to the TF application. The required data is where SAM CLI can find the Lambda layer source code # and what are the resources that contain the building logic. resource "null_resource" "sam_metadata_aws_lambda_layer_version" { - count = local.create && var.create_package && var.create_layer ? 1 : 0 + count = local.create && var.create_sam_metadata && var.create_package && var.create_layer ? 1 : 0 triggers = { # This is a way to let SAM CLI correlates between the Lambda layer resource, and this metadata diff --git a/modules/alias/README.md b/modules/alias/README.md index 9466f51c..5ee31171 100644 --- a/modules/alias/README.md +++ b/modules/alias/README.md @@ -17,7 +17,7 @@ module "lambda_function" { function_name = "my-lambda1" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" source_path = "../src/lambda-function1" } @@ -110,19 +110,19 @@ module "lambda" { * [Alias](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/alias) - Create Lambda function and aliases in various combinations with all supported features. - + ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 3.35 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 3.35 | +| [aws](#provider\_aws) | >= 6.0 | ## Modules @@ -177,7 +177,7 @@ No modules. | [lambda\_alias\_function\_version](#output\_lambda\_alias\_function\_version) | Lambda function version which the alias uses | | [lambda\_alias\_invoke\_arn](#output\_lambda\_alias\_invoke\_arn) | The ARN to be used for invoking Lambda Function from API Gateway | | [lambda\_alias\_name](#output\_lambda\_alias\_name) | The name of the Lambda Function Alias | - + ## Authors diff --git a/modules/alias/main.tf b/modules/alias/main.tf index 324d979b..e57079a2 100644 --- a/modules/alias/main.tf +++ b/modules/alias/main.tf @@ -91,6 +91,7 @@ resource "aws_lambda_permission" "version_triggers" { statement_id = try(each.value.statement_id, each.key) action = try(each.value.action, "lambda:InvokeFunction") principal = try(each.value.principal, format("%s.amazonaws.com", try(each.value.service, ""))) + principal_org_id = try(each.value.principal_org_id, null) source_arn = try(each.value.source_arn, null) source_account = try(each.value.source_account, null) event_source_token = try(each.value.event_source_token, null) @@ -105,6 +106,7 @@ resource "aws_lambda_permission" "qualified_alias_triggers" { statement_id = try(each.value.statement_id, each.key) action = try(each.value.action, "lambda:InvokeFunction") principal = try(each.value.principal, format("%s.amazonaws.com", try(each.value.service, ""))) + principal_org_id = try(each.value.principal_org_id, null) source_arn = try(each.value.source_arn, null) source_account = try(each.value.source_account, null) event_source_token = try(each.value.event_source_token, null) @@ -139,6 +141,13 @@ resource "aws_lambda_event_source_mapping" "this" { } } + dynamic "scaling_config" { + for_each = try([each.value.scaling_config], []) + content { + maximum_concurrency = try(scaling_config.value.maximum_concurrency, null) + } + } + dynamic "self_managed_event_source" { for_each = try(each.value.self_managed_event_source, []) content { @@ -146,6 +155,20 @@ resource "aws_lambda_event_source_mapping" "this" { } } + dynamic "self_managed_kafka_event_source_config" { + for_each = try(each.value.self_managed_kafka_event_source_config, []) + content { + consumer_group_id = try(self_managed_kafka_event_source_config.value.consumer_group_id, null) + } + } + + dynamic "amazon_managed_kafka_event_source_config" { + for_each = try(each.value.amazon_managed_kafka_event_source_config, []) + content { + consumer_group_id = try(amazon_managed_kafka_event_source_config.value.consumer_group_id, null) + } + } + dynamic "source_access_configuration" { for_each = try(each.value.source_access_configuration, []) content { @@ -158,8 +181,12 @@ resource "aws_lambda_event_source_mapping" "this" { for_each = try(each.value.filter_criteria, null) != null ? [true] : [] content { - filter { - pattern = try(each.value["filter_criteria"].pattern, null) + dynamic "filter" { + for_each = try(flatten([each.value.filter_criteria]), []) + + content { + pattern = try(filter.value.pattern, null) + } } } } diff --git a/modules/alias/versions.tf b/modules/alias/versions.tf index d56fc0e8..db13b0a8 100644 --- a/modules/alias/versions.tf +++ b/modules/alias/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 3.35" + version = ">= 6.0" } } } diff --git a/modules/deploy/README.md b/modules/deploy/README.md index 7064c86f..b5d535c9 100644 --- a/modules/deploy/README.md +++ b/modules/deploy/README.md @@ -22,7 +22,7 @@ module "lambda_function" { function_name = "my-lambda1" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" source_path = "../src/lambda-function1" } @@ -95,13 +95,13 @@ module "lambda" { * [Deploy](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/deploy) - Creates Lambda Function, Alias, and all resources required to create deployments using AWS CodeDeploy. - + ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 3.35 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | | [local](#requirement\_local) | >= 1.0 | | [null](#requirement\_null) | >= 2.0 | @@ -109,7 +109,7 @@ module "lambda" { | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 3.35 | +| [aws](#provider\_aws) | >= 6.0 | | [local](#provider\_local) | >= 1.0 | | [null](#provider\_null) | >= 2.0 | @@ -137,6 +137,7 @@ No modules. | [aws_iam_role.codedeploy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_role) | data source | | [aws_lambda_alias.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/lambda_alias) | data source | | [aws_lambda_function.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/lambda_function) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | ## Inputs @@ -151,10 +152,10 @@ No modules. | [attach\_hooks\_policy](#input\_attach\_hooks\_policy) | Whether to attach Invoke policy to CodeDeploy role when before allow traffic or after allow traffic hooks are defined. | `bool` | `true` | no | | [attach\_triggers\_policy](#input\_attach\_triggers\_policy) | Whether to attach SNS policy to CodeDeploy role when triggers are defined | `bool` | `false` | no | | [auto\_rollback\_enabled](#input\_auto\_rollback\_enabled) | Indicates whether a defined automatic rollback configuration is currently enabled for this Deployment Group. | `bool` | `true` | no | -| [auto\_rollback\_events](#input\_auto\_rollback\_events) | List of event types that trigger a rollback. Supported types are DEPLOYMENT\_FAILURE and DEPLOYMENT\_STOP\_ON\_ALARM. | `list(string)` |
[
"DEPLOYMENT_STOP_ON_ALARM"
]
| no | +| [auto\_rollback\_events](#input\_auto\_rollback\_events) | List of event types that trigger a rollback. Supported types are DEPLOYMENT\_FAILURE and DEPLOYMENT\_STOP\_ON\_ALARM. | `list(string)` |
[
"DEPLOYMENT_STOP_ON_ALARM"
]
| no | | [aws\_cli\_command](#input\_aws\_cli\_command) | Command to run as AWS CLI. May include extra arguments like region and profile. | `string` | `"aws"` | no | | [before\_allow\_traffic\_hook\_arn](#input\_before\_allow\_traffic\_hook\_arn) | ARN of Lambda function to execute before allow traffic during deployment. This function should be named CodeDeployHook\_, to match the managed AWSCodeDeployForLambda policy, unless you're using a custom role | `string` | `""` | no | -| [codedeploy\_principals](#input\_codedeploy\_principals) | List of CodeDeploy service principals to allow. The list can include global or regional endpoints. | `list(string)` |
[
"codedeploy.amazonaws.com"
]
| no | +| [codedeploy\_principals](#input\_codedeploy\_principals) | List of CodeDeploy service principals to allow. The list can include global or regional endpoints. | `list(string)` |
[
"codedeploy.amazonaws.com"
]
| no | | [codedeploy\_role\_name](#input\_codedeploy\_role\_name) | IAM role name to create or use by CodeDeploy | `string` | `""` | no | | [create](#input\_create) | Controls whether resources should be created | `bool` | `true` | no | | [create\_app](#input\_create\_app) | Whether to create new AWS CodeDeploy app | `bool` | `false` | no | @@ -168,7 +169,7 @@ No modules. | [force\_deploy](#input\_force\_deploy) | Force deployment every time (even when nothing changes) | `bool` | `false` | no | | [function\_name](#input\_function\_name) | The name of the Lambda function to deploy | `string` | `""` | no | | [get\_deployment\_sleep\_timer](#input\_get\_deployment\_sleep\_timer) | Adds additional sleep time to get-deployment command to avoid the service throttling | `number` | `5` | no | -| [interpreter](#input\_interpreter) | List of interpreter arguments used to execute deploy script, first arg is path | `list(string)` |
[
"/bin/bash",
"-c"
]
| no | +| [interpreter](#input\_interpreter) | List of interpreter arguments used to execute deploy script, first arg is path | `list(string)` |
[
"/bin/bash",
"-c"
]
| no | | [run\_deployment](#input\_run\_deployment) | Run AWS CLI command to start the deployment | `bool` | `false` | no | | [save\_deploy\_script](#input\_save\_deploy\_script) | Save deploy script locally | `bool` | `false` | no | | [tags](#input\_tags) | A map of tags to assign to resources. | `map(string)` | `{}` | no | @@ -191,7 +192,7 @@ No modules. | [codedeploy\_iam\_role\_name](#output\_codedeploy\_iam\_role\_name) | Name of IAM role used by CodeDeploy | | [deploy\_script](#output\_deploy\_script) | Path to a deployment script | | [script](#output\_script) | Deployment script | - + ## Authors diff --git a/modules/deploy/main.tf b/modules/deploy/main.tf index f82ca372..d88c0894 100644 --- a/modules/deploy/main.tf +++ b/modules/deploy/main.tf @@ -16,7 +16,7 @@ locals { Name = var.function_name Alias = var.alias_name CurrentVersion = var.current_version != "" ? var.current_version : local.current_version - TargetVersion : var.target_version + TargetVersion = var.target_version } } } @@ -86,6 +86,8 @@ EOF } +data "aws_partition" "current" {} + data "aws_lambda_alias" "this" { count = var.create && var.create_deployment ? 1 : 0 @@ -209,7 +211,7 @@ resource "aws_iam_role_policy_attachment" "codedeploy" { count = var.create && var.create_codedeploy_role ? 1 : 0 role = try(aws_iam_role.codedeploy[0].id, "") - policy_arn = "arn:aws:iam::aws:policy/service-role/AWSCodeDeployRoleForLambda" + policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AWSCodeDeployRoleForLambda" } data "aws_iam_policy_document" "hooks" { diff --git a/modules/deploy/versions.tf b/modules/deploy/versions.tf index deb3cfd9..ddb64c76 100644 --- a/modules/deploy/versions.tf +++ b/modules/deploy/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 3.35" + version = ">= 6.0" } local = { source = "hashicorp/local" diff --git a/modules/docker-build/README.md b/modules/docker-build/README.md index b835a325..8d9d8315 100644 --- a/modules/docker-build/README.md +++ b/modules/docker-build/README.md @@ -2,6 +2,8 @@ Terraform module that builds Docker image from `Dockerfile` and pushes it to ECR repository. Lambda can deploy container images from private ECR. +If you need to create ECR resources in flexible way, you should use [terraform-aws-ecr module](https://github.com/terraform-aws-modules/terraform-aws-ecr/). See `examples/container-image` for related examples. + This Terraform module is the part of [serverless.tf framework](https://github.com/antonbabenko/serverless.tf), which aims to simplify all operations when working with the serverless in Terraform. ## Usage @@ -34,7 +36,10 @@ module "docker_image" { create_ecr_repo = true ecr_repo = "my-cool-ecr-repo" - image_tag = "1.0" + + use_image_tag = true + image_tag = "1.0" + source_path = "context" build_args = { FOO = "bar" @@ -44,25 +49,25 @@ module "docker_image" { ## Examples -* [Container Image](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/container-image) - Creates Docker Image and deploy Lambda Function using it. +* [Container Image](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/container-image) - Creates Docker Image, ECR resository and deploys it Lambda Function. - + ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 4.22 | -| [docker](#requirement\_docker) | >= 2.12 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | +| [docker](#requirement\_docker) | >= 3.5.0 | | [null](#requirement\_null) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.22 | -| [docker](#provider\_docker) | >= 2.12 | +| [aws](#provider\_aws) | >= 6.0 | +| [docker](#provider\_docker) | >= 3.5.0 | | [null](#provider\_null) | >= 2.0 | ## Modules @@ -75,6 +80,7 @@ No modules. |------|------| | [aws_ecr_lifecycle_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecr_lifecycle_policy) | resource | | [aws_ecr_repository.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecr_repository) | resource | +| [docker_image.this](https://registry.terraform.io/providers/kreuzwerker/docker/latest/docs/resources/image) | resource | | [docker_registry_image.this](https://registry.terraform.io/providers/kreuzwerker/docker/latest/docs/resources/registry_image) | resource | | [null_resource.sam_metadata_docker_registry_image](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | | [aws_caller_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | @@ -85,25 +91,35 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [build\_args](#input\_build\_args) | A map of Docker build arguments. | `map(string)` | `{}` | no | +| [build\_target](#input\_build\_target) | Set the target build stage to build | `string` | `null` | no | +| [builder](#input\_builder) | The buildx builder to use for the Docker build. | `string` | `null` | no | +| [cache\_from](#input\_cache\_from) | List of images to consider as cache sources when building the image. | `list(string)` | `[]` | no | | [create\_ecr\_repo](#input\_create\_ecr\_repo) | Controls whether ECR repository for Lambda image should be created | `bool` | `false` | no | +| [create\_sam\_metadata](#input\_create\_sam\_metadata) | Controls whether the SAM metadata null resource should be created | `bool` | `false` | no | | [docker\_file\_path](#input\_docker\_file\_path) | Path to Dockerfile in source package | `string` | `"Dockerfile"` | no | | [ecr\_address](#input\_ecr\_address) | Address of ECR repository for cross-account container image pulling (optional). Option `create_ecr_repo` must be `false` | `string` | `null` | no | | [ecr\_force\_delete](#input\_ecr\_force\_delete) | If true, will delete the repository even if it contains images. | `bool` | `true` | no | | [ecr\_repo](#input\_ecr\_repo) | Name of ECR repository to use or to create | `string` | `null` | no | | [ecr\_repo\_lifecycle\_policy](#input\_ecr\_repo\_lifecycle\_policy) | A JSON formatted ECR lifecycle policy to automate the cleaning up of unused images. | `string` | `null` | no | | [ecr\_repo\_tags](#input\_ecr\_repo\_tags) | A map of tags to assign to ECR repository | `map(string)` | `{}` | no | +| [force\_remove](#input\_force\_remove) | Whether to remove image forcibly when the resource is destroyed. | `bool` | `false` | no | | [image\_tag](#input\_image\_tag) | Image tag to use. If not specified current timestamp in format 'YYYYMMDDhhmmss' will be used. This can lead to unnecessary rebuilds. | `string` | `null` | no | | [image\_tag\_mutability](#input\_image\_tag\_mutability) | The tag mutability setting for the repository. Must be one of: `MUTABLE` or `IMMUTABLE` | `string` | `"MUTABLE"` | no | +| [keep\_locally](#input\_keep\_locally) | Whether to delete the Docker image locally on destroy operation. | `bool` | `false` | no | | [keep\_remotely](#input\_keep\_remotely) | Whether to keep Docker image in the remote registry on destroy operation. | `bool` | `false` | no | +| [platform](#input\_platform) | The target architecture platform to build the image for. | `string` | `null` | no | | [scan\_on\_push](#input\_scan\_on\_push) | Indicates whether images are scanned after being pushed to the repository | `bool` | `false` | no | | [source\_path](#input\_source\_path) | Path to folder containing application code | `string` | `null` | no | +| [triggers](#input\_triggers) | A map of arbitrary strings that, when changed, will force the docker\_image resource to be replaced. This can be used to rebuild an image when contents of source code folders change | `map(string)` | `{}` | no | +| [use\_image\_tag](#input\_use\_image\_tag) | Controls whether to use image tag in ECR repository URI or not. Disable this to deploy latest image using ID (sha256:...) | `bool` | `true` | no | ## Outputs | Name | Description | |------|-------------| +| [image\_id](#output\_image\_id) | The ID of the Docker image | | [image\_uri](#output\_image\_uri) | The ECR image URI for deploying lambda | - + ## Authors diff --git a/modules/docker-build/main.tf b/modules/docker-build/main.tf index c96cfbee..559060cb 100644 --- a/modules/docker-build/main.tf +++ b/modules/docker-build/main.tf @@ -3,22 +3,36 @@ data "aws_region" "current" {} data "aws_caller_identity" "this" {} locals { - ecr_address = coalesce(var.ecr_address, format("%v.dkr.ecr.%v.amazonaws.com", data.aws_caller_identity.this.account_id, data.aws_region.current.name)) + ecr_address = coalesce(var.ecr_address, format("%v.dkr.ecr.%v.amazonaws.com", data.aws_caller_identity.this.account_id, data.aws_region.current.region)) ecr_repo = var.create_ecr_repo ? aws_ecr_repository.this[0].id : var.ecr_repo - image_tag = coalesce(var.image_tag, formatdate("YYYYMMDDhhmmss", timestamp())) - ecr_image_name = format("%v/%v:%v", local.ecr_address, local.ecr_repo, local.image_tag) + image_tag = var.use_image_tag ? coalesce(var.image_tag, formatdate("YYYYMMDDhhmmss", timestamp())) : null + ecr_image_name = var.use_image_tag ? format("%v/%v:%v", local.ecr_address, local.ecr_repo, local.image_tag) : format("%v/%v", local.ecr_address, local.ecr_repo) } -resource "docker_registry_image" "this" { +resource "docker_image" "this" { name = local.ecr_image_name build { context = var.source_path dockerfile = var.docker_file_path build_args = var.build_args + builder = var.builder + target = var.build_target + platform = var.platform + cache_from = var.cache_from } + force_remove = var.force_remove + keep_locally = var.keep_locally + triggers = var.triggers +} + +resource "docker_registry_image" "this" { + name = docker_image.this.name + keep_remotely = var.keep_remotely + + triggers = length(var.triggers) == 0 ? { image_id = docker_image.this.image_id } : var.triggers } resource "aws_ecr_repository" "this" { @@ -46,6 +60,8 @@ resource "aws_ecr_lifecycle_policy" "this" { # to the TF application. This resource will maintain the metadata information about the image type lambda # functions. It will contain the information required to build the docker image locally. resource "null_resource" "sam_metadata_docker_registry_image" { + count = var.create_sam_metadata ? 1 : 0 + triggers = { resource_type = "IMAGE_LAMBDA_FUNCTION" docker_context = var.source_path diff --git a/modules/docker-build/outputs.tf b/modules/docker-build/outputs.tf index 05c9063a..5b268b54 100644 --- a/modules/docker-build/outputs.tf +++ b/modules/docker-build/outputs.tf @@ -1,4 +1,9 @@ output "image_uri" { description = "The ECR image URI for deploying lambda" - value = docker_registry_image.this.name + value = var.use_image_tag ? docker_registry_image.this.name : format("%v@%v", docker_registry_image.this.name, docker_registry_image.this.id) +} + +output "image_id" { + description = "The ID of the Docker image" + value = docker_registry_image.this.id } diff --git a/modules/docker-build/variables.tf b/modules/docker-build/variables.tf index 4fadbff1..110ce554 100644 --- a/modules/docker-build/variables.tf +++ b/modules/docker-build/variables.tf @@ -4,6 +4,18 @@ variable "create_ecr_repo" { default = false } +variable "create_sam_metadata" { + description = "Controls whether the SAM metadata null resource should be created" + type = bool + default = false +} + +variable "use_image_tag" { + description = "Controls whether to use image tag in ECR repository URI or not. Disable this to deploy latest image using ID (sha256:...)" + type = bool + default = true +} + variable "ecr_address" { description = "Address of ECR repository for cross-account container image pulling (optional). Option `create_ecr_repo` must be `false`" type = string @@ -59,12 +71,24 @@ variable "ecr_repo_tags" { default = {} } +variable "builder" { + description = "The buildx builder to use for the Docker build." + type = string + default = null +} + variable "build_args" { description = "A map of Docker build arguments." type = map(string) default = {} } +variable "build_target" { + description = "Set the target build stage to build" + type = string + default = null +} + variable "ecr_repo_lifecycle_policy" { description = "A JSON formatted ECR lifecycle policy to automate the cleaning up of unused images." type = string @@ -76,3 +100,33 @@ variable "keep_remotely" { type = bool default = false } + +variable "platform" { + description = "The target architecture platform to build the image for." + type = string + default = null +} + +variable "force_remove" { + description = "Whether to remove image forcibly when the resource is destroyed." + type = bool + default = false +} + +variable "keep_locally" { + description = "Whether to delete the Docker image locally on destroy operation." + type = bool + default = false +} + +variable "triggers" { + description = "A map of arbitrary strings that, when changed, will force the docker_image resource to be replaced. This can be used to rebuild an image when contents of source code folders change" + type = map(string) + default = {} +} + +variable "cache_from" { + description = "List of images to consider as cache sources when building the image." + type = list(string) + default = [] +} diff --git a/modules/docker-build/versions.tf b/modules/docker-build/versions.tf index 3bfde6d0..b203b635 100644 --- a/modules/docker-build/versions.tf +++ b/modules/docker-build/versions.tf @@ -1,14 +1,14 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.22" + version = ">= 6.0" } docker = { source = "kreuzwerker/docker" - version = ">= 2.12" + version = ">= 3.5.0" } null = { source = "hashicorp/null" diff --git a/outputs.tf b/outputs.tf index cec0293b..93624833 100644 --- a/outputs.tf +++ b/outputs.tf @@ -6,7 +6,7 @@ output "lambda_function_arn" { output "lambda_function_arn_static" { description = "The static ARN of the Lambda Function. Use this to avoid cycle errors between resources (e.g., Step Functions)" - value = local.create && var.create_function && !var.create_layer ? "arn:aws:lambda:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:function:${var.function_name}" : "" + value = local.create && var.create_function && !var.create_layer ? "arn:${data.aws_partition.current.partition}:lambda:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:function:${var.function_name}" : "" } output "lambda_function_invoke_arn" { @@ -24,6 +24,11 @@ output "lambda_function_qualified_arn" { value = try(aws_lambda_function.this[0].qualified_arn, "") } +output "lambda_function_qualified_invoke_arn" { + description = "The Invoke ARN identifying your Lambda Function Version" + value = try(aws_lambda_function.this[0].qualified_invoke_arn, "") +} + output "lambda_function_version" { description = "Latest published version of Lambda Function" value = try(aws_lambda_function.this[0].version, "") @@ -97,6 +102,11 @@ output "lambda_layer_version" { } # Lambda Event Source Mapping +output "lambda_event_source_mapping_arn" { + description = "The event source mapping ARN" + value = { for k, v in aws_lambda_event_source_mapping.this : k => v.arn } +} + output "lambda_event_source_mapping_function_arn" { description = "The the ARN of the Lambda function the event source mapping is sending events to" value = { for k, v in aws_lambda_event_source_mapping.this : k => v.function_arn } diff --git a/package.py b/package.py index 48a3c4bd..3261a282 100644 --- a/package.py +++ b/package.py @@ -29,8 +29,8 @@ PY37 = sys.version_info >= (3, 7) PY36 = sys.version_info >= (3, 6) -WINDOWS = platform.system() == 'Windows' -OSX = platform.system() == 'Darwin' +WINDOWS = platform.system() == "Windows" +OSX = platform.system() == "Darwin" ################################################################################ # Logging @@ -41,29 +41,29 @@ log_handler = None log = logging.getLogger() -cmd_log = logging.getLogger('cmd') +cmd_log = logging.getLogger("cmd") def configure_logging(use_tf_stderr=False): global log_handler - logging.addLevelName(DEBUG2, 'DEBUG2') - logging.addLevelName(DEBUG3, 'DEBUG3') - logging.addLevelName(DUMP_ENV, 'DUMP_ENV') + logging.addLevelName(DEBUG2, "DEBUG2") + logging.addLevelName(DEBUG3, "DEBUG3") + logging.addLevelName(DUMP_ENV, "DUMP_ENV") class LogFormatter(logging.Formatter): - default_format = '%(message)s' + default_format = "%(message)s" formats = { - 'root': default_format, - 'build': default_format, - 'prepare': '[{}] %(name)s: %(message)s'.format(os.getpid()), - 'cmd': '> %(message)s', - '': '%(name)s: %(message)s' + "root": default_format, + "build": default_format, + "prepare": "[{}] %(name)s: %(message)s".format(os.getpid()), + "cmd": "> %(message)s", + "": "%(name)s: %(message)s", } def formatMessage(self, record): - prefix = record.name.rsplit('.') - self._style._fmt = self.formats.get(prefix[0], self.formats['']) + prefix = record.name.rsplit(".") + self._style._fmt = self.formats.get(prefix[0], self.formats[""]) return super().formatMessage(record) tf_stderr_fd = 5 @@ -71,7 +71,7 @@ def formatMessage(self, record): if use_tf_stderr: try: if os.isatty(tf_stderr_fd): - log_stream = os.fdopen(tf_stderr_fd, mode='w') + log_stream = os.fdopen(tf_stderr_fd, mode="w") except OSError: pass @@ -84,20 +84,22 @@ def formatMessage(self, record): def dump_env(): if log.isEnabledFor(DUMP_ENV): - log.debug('ENV: %s', json.dumps(dict(os.environ), indent=2)) + log.debug("ENV: %s", json.dumps(dict(os.environ), indent=2)) ################################################################################ # Backports + def shlex_join(split_command): """Return a shell-escaped string from *split_command*.""" - return ' '.join(shlex.quote(arg) for arg in split_command) + return " ".join(shlex.quote(arg) for arg in split_command) ################################################################################ # Common functions + def abort(message): """Exits with an error message.""" log.error(message) @@ -109,7 +111,7 @@ def cd(path, silent=False): """Changes the working directory.""" cwd = os.getcwd() if not silent: - cmd_log.info('cd %s', shlex.quote(path)) + cmd_log.info("cd %s", shlex.quote(path)) try: os.chdir(path) yield @@ -120,13 +122,14 @@ def cd(path, silent=False): @contextmanager def tempdir(dir=None): """Creates a temporary directory and then deletes it afterwards.""" - prefix = 'terraform-aws-lambda-' + prefix = "terraform-aws-lambda-" path = tempfile.mkdtemp(prefix=prefix, dir=dir) - cmd_log.info('mktemp -d %sXXXXXXXX # %s', prefix, shlex.quote(path)) + abs_path = os.path.abspath(path) + cmd_log.info("mktemp -d %sXXXXXXXX # %s", prefix, shlex.quote(abs_path)) try: - yield path + yield abs_path finally: - shutil.rmtree(path) + shutil.rmtree(abs_path) def list_files(top_path, log=None): @@ -135,7 +138,7 @@ def list_files(top_path, log=None): """ if log: - log = log.getChild('ls') + log = log.getChild("ls") results = [] @@ -155,10 +158,14 @@ def list_files(top_path, log=None): def dataclass(name): - typ = type(name, (dict,), { - '__getattr__': lambda self, x: self.get(x), - '__init__': lambda self, **k: self.update(k), - }) + typ = type( + name, + (dict,), + { + "__getattr__": lambda self, x: self.get(x), + "__init__": lambda self, **k: self.update(k), + }, + ) return typ @@ -174,14 +181,19 @@ def decode_json(k, v): pass return v - return dataclass(name)(**dict((( - k, datatree(k, **v) if isinstance(v, dict) else decode_json(k, v)) - for k, v in fields.items()))) + return dataclass(name)( + **dict( + ( + (k, datatree(k, **v) if isinstance(v, dict) else decode_json(k, v)) + for k, v in fields.items() + ) + ) + ) def timestamp_now_ns(): timestamp = datetime.datetime.now().timestamp() - timestamp = int(timestamp * 10 ** 7) * 10 ** 2 + timestamp = int(timestamp * 10**7) * 10**2 return timestamp @@ -200,9 +212,9 @@ def yesno_bool(val): if val.isnumeric(): return bool(int(val)) val = val.lower() - if val in ('true', 'yes', 'y'): + if val in ("true", "yes", "y"): return True - elif val in ('false', 'no', 'n'): + elif val in ("false", "no", "n"): return False else: raise ValueError("Unsupported value: %s" % val) @@ -212,6 +224,7 @@ def yesno_bool(val): ################################################################################ # Packaging functions + def emit_dir_content(base_dir): for root, dirs, files in os.walk(base_dir, followlinks=True): # Sort directories and files to ensure they are always processed in the same order @@ -223,14 +236,13 @@ def emit_dir_content(base_dir): yield os.path.normpath(os.path.join(root, name)) -def generate_content_hash(source_paths, - hash_func=hashlib.sha256, log=None): +def generate_content_hash(source_paths, hash_func=hashlib.sha256, log=None): """ Generate a content hash of the source paths. """ if log: - log = log.getChild('hash') + log = log.getChild("hash") hash_obj = hash_func() @@ -260,48 +272,58 @@ def update_hash(hash_obj, file_root, file_path): relative_path = os.path.join(file_root, file_path) hash_obj.update(relative_path.encode()) - with open(relative_path, 'rb') as open_file: - while True: - data = open_file.read(1024 * 8) - if not data: - break - hash_obj.update(data) + try: + with open(relative_path, "rb") as open_file: + while True: + data = open_file.read(1024 * 8) + if not data: + break + hash_obj.update(data) + # ignore broken symlinks content to don't fail on `terraform destroy` command + except FileNotFoundError: + pass class ZipWriteStream: """""" - def __init__(self, zip_filename, - compress_type=zipfile.ZIP_DEFLATED, - compresslevel=None, - timestamp=None): - + def __init__( + self, + zip_filename, + compress_type=zipfile.ZIP_DEFLATED, + compresslevel=None, + timestamp=None, + quiet=False, + ): self.timestamp = timestamp self.filename = zip_filename + self.quiet = quiet if not (self.filename and isinstance(self.filename, str)): - raise ValueError('Zip file path must be provided') + raise ValueError("Zip file path must be provided") self._tmp_filename = None self._compress_type = compress_type self._compresslevel = compresslevel self._zip = None - self._log = logging.getLogger('zip') + self._log = logging.getLogger("zip") def open(self): if self._tmp_filename: raise zipfile.BadZipFile("ZipStream object can't be reused") self._ensure_base_path(self.filename) - self._tmp_filename = '{}.tmp'.format(self.filename) - self._log.info("creating '%s' archive", self.filename) - self._zip = zipfile.ZipFile(self._tmp_filename, "w", - self._compress_type) + self._tmp_filename = "{}.tmp".format(self.filename) + if not self.quiet: + self._log.info("creating '%s' archive", self.filename) + self._zip = zipfile.ZipFile(self._tmp_filename, "w", self._compress_type) return self def close(self, failed=False): self._zip.close() self._zip = None + if not os.path.exists(self._tmp_filename): + return if failed: os.unlink(self._tmp_filename) else: @@ -322,7 +344,7 @@ def _ensure_open(self): return True if self._tmp_filename: raise zipfile.BadZipFile("ZipWriteStream object can't be reused") - raise zipfile.BadZipFile('ZipWriteStream should be opened first') + raise zipfile.BadZipFile("ZipWriteStream should be opened first") def _ensure_base_path(self, zip_filename): archive_dir = os.path.dirname(zip_filename) @@ -337,7 +359,8 @@ def write_dirs(self, *base_dirs, prefix=None, timestamp=None): """ self._ensure_open() for base_dir in base_dirs: - self._log.info("adding content of directory: %s", base_dir) + if not self.quiet: + self._log.info("adding content of directory: %s", base_dir) for path in emit_dir_content(base_dir): arcname = os.path.relpath(path, base_dir) self._write_file(path, prefix, arcname, timestamp) @@ -363,10 +386,11 @@ def _write_file(self, file_path, prefix=None, name=None, timestamp=None): if prefix: arcname = os.path.join(prefix, arcname) zinfo = self._make_zinfo_from_file(file_path, arcname) - if zinfo.is_dir(): - self._log.info("adding: %s/", arcname) - else: - self._log.info("adding: %s", arcname) + if not self.quiet: + if zinfo.is_dir(): + self._log.info("adding: %s/", arcname) + else: + self._log.info("adding: %s", arcname) if timestamp is None: timestamp = self.timestamp date_time = self._timestamp_to_date_time(timestamp) @@ -381,15 +405,13 @@ def write_file_obj(self, file_path, data, prefix=None, timestamp=None): self._ensure_open() raise NotImplementedError - def _write_zinfo(self, zinfo, filename, - compress_type=None, compresslevel=None): + def _write_zinfo(self, zinfo, filename, compress_type=None, compresslevel=None): self._ensure_open() zip = self._zip if not zip.fp: - raise ValueError( - "Attempt to write to ZIP archive that was already closed") + raise ValueError("Attempt to write to ZIP archive that was already closed") if zip._writing: raise ValueError( "Can't write to ZIP archive while an open writing handle exists" @@ -427,7 +449,7 @@ def _write_zinfo(self, zinfo, filename, zip.fp.write(zinfo.FileHeader(False)) zip.start_dir = zip.fp.tell() else: - with open(filename, "rb") as src, zip.open(zinfo, 'w') as dest: + with open(filename, "rb") as src, zip.open(zinfo, "w") as dest: shutil.copyfileobj(src, dest, 1024 * 8) def _make_zinfo_from_file(self, filename, arcname=None): @@ -438,8 +460,7 @@ def _make_zinfo_from_file(self, filename, arcname=None): zinfo_func = self._zinfo_from_file strict_timestamps = True - return zinfo_func(filename, arcname, - strict_timestamps=strict_timestamps) + return zinfo_func(filename, arcname, strict_timestamps=strict_timestamps) @staticmethod def _update_zinfo(zinfo, date_time): @@ -474,7 +495,7 @@ def _zinfo_from_file(filename, arcname=None, *, strict_timestamps=True): while arcname[0] in (os.sep, os.altsep): arcname = arcname[1:] if isdir: - arcname += '/' + arcname += "/" zinfo = zipfile.ZipInfo(arcname, date_time) zinfo.external_attr = (st.st_mode & 0xFFFF) << 16 # Unix attributes if isdir: @@ -494,7 +515,7 @@ def str_int_to_timestamp(s): return min_zip_ts deg = len(str(int(s))) - 9 if deg < 0: - ts = ts * 10 ** deg + ts = ts * 10**deg return ts date_time = None @@ -510,23 +531,26 @@ def str_int_to_timestamp(s): date_time = datetime.datetime.fromtimestamp(timestamp).timetuple() date_time = date_time[:6] if date_time[0] < 1980: - raise ValueError('ZIP does not support timestamps before 1980') + raise ValueError("ZIP does not support timestamps before 1980") return date_time ################################################################################ # Building + def patterns_list(args, patterns): _filter = str.strip if args.pattern_comments: + def _filter(x): x = x.strip() p = re.search("^(.*?)[ \t]*(?:[ \t]{2}#.*)?$", x).group(1).rstrip() - if p.startswith('#'): + if p.startswith("#"): return if p: return p + if isinstance(patterns, str): return list(filter(None, map(_filter, patterns.splitlines()))) return patterns @@ -539,13 +563,13 @@ def __init__(self, args): self._args = args self._rules = None self._excludes = set() - self._log = logging.getLogger('zip') + self._log = logging.getLogger("zip") def compile(self, patterns): rules = [] for p in patterns_list(self._args, patterns): self._log.debug("filter pattern: %s", p) - if p.startswith('!'): + if p.startswith("!"): r = re.compile(p[1:]) rules.append((operator.not_, r)) else: @@ -553,6 +577,10 @@ def compile(self, patterns): rules.append((None, r)) self._rules = rules + def reset(self): + self._log.debug("reset filter patterns") + self._rules = None + def filter(self, path, prefix=None): path = os.path.normpath(path) if prefix: @@ -586,13 +614,13 @@ def emit_dir(dpath, opath): if apply(dpath): yield opath else: - self._log.debug('skip: %s', dpath) + self._log.debug("skip: %s", dpath) def emit_file(fpath, opath): if apply(fpath): yield opath else: - self._log.debug('skip: %s', fpath) + self._log.debug("skip: %s", fpath) if os.path.isfile(path): name = os.path.basename(path) @@ -638,7 +666,7 @@ def __init__(self, args, log=None): def hash(self, extra_paths): if not self._source_paths: - raise ValueError('BuildPlanManager.plan() should be called first') + raise ValueError("BuildPlanManager.plan() should be called first") content_hash_paths = self._source_paths + extra_paths @@ -646,8 +674,7 @@ def hash(self, extra_paths): # runtime value, build command, and content of the build paths # because they can have an effect on the resulting archive. self._log.debug("Computing content hash on files...") - content_hash = generate_content_hash(content_hash_paths, - log=self._log) + content_hash = generate_content_hash(content_hash_paths, log=self._log) return content_hash def plan(self, source_path, query): @@ -657,43 +684,52 @@ def plan(self, source_path, query): source_paths = [] build_plan = [] + build_step = [] + + def step(*x): + build_step.append(x) - step = lambda *x: build_plan.append(x) - hash = source_paths.append + def hash(path): + source_paths.append(path) def pip_requirements_step(path, prefix=None, required=False, tmp_dir=None): command = runtime requirements = path if os.path.isdir(path): - requirements = os.path.join(path, 'requirements.txt') + requirements = os.path.join(path, "requirements.txt") if not os.path.isfile(requirements): if required: - raise RuntimeError( - 'File not found: {}'.format(requirements)) + raise RuntimeError("File not found: {}".format(requirements)) else: if not query.docker and not shutil.which(command): raise RuntimeError( "Python interpreter version equal " "to defined lambda runtime ({}) should be " - "available in system PATH".format(command)) + "available in system PATH".format(command) + ) - step('pip', runtime, requirements, prefix, tmp_dir) + step("pip", runtime, requirements, prefix, tmp_dir) hash(requirements) - def poetry_install_step(path, prefix=None, required=False): + def poetry_install_step( + path, poetry_export_extra_args=[], prefix=None, required=False, tmp_dir=None + ): pyproject_file = path if os.path.isdir(path): pyproject_file = os.path.join(path, "pyproject.toml") if get_build_system_from_pyproject_toml(pyproject_file) != "poetry": if required: - raise RuntimeError("poetry configuration not found: {}".format(pyproject_file)) + raise RuntimeError( + "poetry configuration not found: {}".format(pyproject_file) + ) else: - step("poetry", runtime, path, prefix) + step("poetry", runtime, path, poetry_export_extra_args, prefix, tmp_dir) hash(pyproject_file) - poetry_lock_file = os.path.join(path, "poetry.lock") + pyproject_path = os.path.dirname(pyproject_file) + poetry_lock_file = os.path.join(pyproject_path, "poetry.lock") if os.path.isfile(poetry_lock_file): hash(poetry_lock_file) - poetry_toml_file = os.path.join(path, "poetry.toml") + poetry_toml_file = os.path.join(pyproject_path, "poetry.toml") if os.path.isfile(poetry_toml_file): hash(poetry_toml_file) @@ -701,18 +737,26 @@ def npm_requirements_step(path, prefix=None, required=False, tmp_dir=None): command = "npm" requirements = path if os.path.isdir(path): - requirements = os.path.join(path, 'package.json') + requirements = os.path.join(path, "package.json") + npm_lock_file = os.path.join(path, "package-lock.json") + else: + npm_lock_file = os.path.join(os.path.dirname(path), "package-lock.json") + + if os.path.isfile(npm_lock_file): + hash(npm_lock_file) + log.info("Added npm lock file: %s", npm_lock_file) + if not os.path.isfile(requirements): if required: - raise RuntimeError( - 'File not found: {}'.format(requirements)) + raise RuntimeError("File not found: {}".format(requirements)) else: if not query.docker and not shutil.which(command): raise RuntimeError( "Nodejs package manager ({}) should be " - "available in system PATH".format(command)) + "available in system PATH".format(command) + ) - step('npm', runtime, requirements, prefix, tmp_dir) + step("npm", runtime, requirements, prefix, tmp_dir) hash(requirements) def commands_step(path, commands): @@ -724,95 +768,121 @@ def commands_step(path, commands): if path: path = os.path.normpath(path) + step("set:workdir", path) + batch = [] for c in commands: if isinstance(c, str): - if c.startswith(':zip'): + if c.startswith(":zip"): if path: hash(path) - else: - # If path doesn't defined for a block with - # commands it will be set to Terraform's - # current working directory - # NB: cwd may vary when using Terraform 0.14+ like: - # `terraform -chdir=...` - path = query.paths.cwd if batch: - step('sh', path, '\n'.join(batch)) + step("sh", "\n".join(batch)) batch.clear() c = shlex.split(c) - if len(c) == 3: + n = len(c) + if n == 3: _, _path, prefix = c prefix = prefix.strip() - _path = os.path.normpath(os.path.join(path, _path)) - step('zip:embedded', _path, prefix) - elif len(c) == 2: - prefix = None + _path = os.path.normpath(_path) + step("zip:embedded", _path, prefix) + elif n == 2: _, _path = c - step('zip:embedded', _path, prefix) - elif len(c) == 1: - prefix = None - step('zip:embedded', path, prefix) + _path = os.path.normpath(_path) + step("zip:embedded", _path) + elif n == 1: + step("zip:embedded") else: raise ValueError( ":zip invalid call signature, use: " - "':zip [path [prefix_in_zip]]'") + "':zip [path [prefix_in_zip]]'" + ) else: batch.append(c) + if batch: + step("sh", "\n".join(batch)) + batch.clear() for claim in claims: if isinstance(claim, str): path = claim if not os.path.exists(path): - abort('Could not locate source_path "{path}". Paths are relative to directory where `terraform plan` is being run ("{pwd}")'.format( - path=path, - pwd=os.getcwd() - )) + abort( + 'Could not locate source_path "{path}". Paths are relative to directory where `terraform plan` is being run ("{pwd}")'.format( + path=path, pwd=os.getcwd() + ) + ) runtime = query.runtime - if runtime.startswith('python'): - pip_requirements_step( - os.path.join(path, 'requirements.txt')) + if runtime.startswith("python"): + pip_requirements_step(os.path.join(path, "requirements.txt")) poetry_install_step(path) - elif runtime.startswith('nodejs'): - npm_requirements_step( - os.path.join(path, 'package.json')) - step('zip', path, None) + elif runtime.startswith("nodejs"): + npm_requirements_step(os.path.join(path, "package.json")) + step("zip", path, None) hash(path) elif isinstance(claim, dict): - path = claim.get('path') - patterns = claim.get('patterns') - commands = claim.get('commands') + path = claim.get("path") + patterns = claim.get("patterns") + commands = claim.get("commands") if patterns: - step('set:filter', patterns_list(self._args, patterns)) + step("set:filter", patterns_list(self._args, patterns)) if commands: commands_step(path, commands) else: - prefix = claim.get('prefix_in_zip') - pip_requirements = claim.get('pip_requirements') + prefix = claim.get("prefix_in_zip") + pip_requirements = claim.get("pip_requirements") poetry_install = claim.get("poetry_install") - npm_requirements = claim.get('npm_package_json') - runtime = claim.get('runtime', query.runtime) + poetry_export_extra_args = claim.get("poetry_export_extra_args", []) + npm_requirements = claim.get( + "npm_requirements", claim.get("npm_package_json") + ) + runtime = claim.get("runtime", query.runtime) - if pip_requirements and runtime.startswith('python'): + if pip_requirements and runtime.startswith("python"): if isinstance(pip_requirements, bool) and path: - pip_requirements_step(path, prefix, required=True, tmp_dir=claim.get('pip_tmp_dir')) + pip_requirements_step( + path, + prefix, + required=True, + tmp_dir=claim.get("pip_tmp_dir"), + ) else: - pip_requirements_step(pip_requirements, prefix, - required=True, tmp_dir=claim.get('pip_tmp_dir')) + pip_requirements_step( + pip_requirements, + prefix, + required=True, + tmp_dir=claim.get("pip_tmp_dir"), + ) if poetry_install and runtime.startswith("python"): if path: - poetry_install_step(path, prefix, required=True) - - if npm_requirements and runtime.startswith('nodejs'): + poetry_install_step( + path, + prefix=prefix, + poetry_export_extra_args=poetry_export_extra_args, + required=True, + tmp_dir=claim.get("poetry_tmp_dir"), + ) + + if npm_requirements and runtime.startswith("nodejs"): if isinstance(npm_requirements, bool) and path: - npm_requirements_step(path, prefix, required=True, tmp_dir=claim.get('npm_tmp_dir')) + npm_requirements_step( + path, + prefix, + required=True, + tmp_dir=claim.get("npm_tmp_dir"), + ) else: - npm_requirements_step(npm_requirements, prefix, - required=True, tmp_dir=claim.get('npm_tmp_dir')) + npm_requirements_step( + npm_requirements, + prefix, + required=True, + tmp_dir=claim.get("npm_tmp_dir"), + ) if path: - step('zip', path, prefix) + path = os.path.normpath(path) + step("zip", path, prefix) if patterns: # Take patterns into account when computing hash pf = ZipContentFilter(args=self._args) @@ -822,89 +892,166 @@ def commands_step(path, commands): hash(path_from_pattern) else: hash(path) - - if patterns: - step('clear:filter') else: - raise ValueError( - 'Unsupported source_path item: {}'.format(claim)) + raise ValueError("Unsupported source_path item: {}".format(claim)) + + if build_step: + build_plan.append(build_step) + build_step = [] self._source_paths = source_paths return build_plan def execute(self, build_plan, zip_stream, query): + sh_log = logging.getLogger("sh") + + tf_work_dir = os.getcwd() + zs = zip_stream sh_work_dir = None pf = None - for action in build_plan: - cmd = action[0] - if cmd.startswith('zip'): - ts = 0 if cmd == 'zip:embedded' else None - source_path, prefix = action[1:] - if sh_work_dir: - if source_path != sh_work_dir: - if not os.path.isfile(source_path): - source_path = sh_work_dir - if os.path.isdir(source_path): - if pf: - self._zip_write_with_filter(zs, pf, source_path, prefix, - timestamp=ts) + for step in build_plan: + # init step + sh_work_dir = tf_work_dir + if pf: + pf.reset() + pf = None + + log.debug("STEPDIR: %s", sh_work_dir) + + # execute step actions + for action in step: + cmd = action[0] + if cmd.startswith("zip"): + ts = 0 if cmd == "zip:embedded" else None + + source_path, prefix = None, None + n = len(action) + if n == 2: + source_path = action[1] + elif n == 3: + source_path, prefix = action[1:] + + if source_path: + if not os.path.isabs(source_path): + source_path = os.path.normpath( + os.path.join(sh_work_dir, source_path) + ) else: - zs.write_dirs(source_path, prefix=prefix, timestamp=ts) - else: - zs.write_file(source_path, prefix=prefix, timestamp=ts) - elif cmd == 'pip': - runtime, pip_requirements, prefix, tmp_dir = action[1:] - with install_pip_requirements(query, pip_requirements, tmp_dir) as rd: - if rd: - if pf: - self._zip_write_with_filter(zs, pf, rd, prefix, timestamp=0) - else: - # XXX: timestamp=0 - what actually do with it? - zs.write_dirs(rd, prefix=prefix, timestamp=0) - elif cmd == "poetry": - runtime, path, prefix = action[1:] - with install_poetry_dependencies(query, path) as rd: - if rd: - if pf: - self._zip_write_with_filter(zs, pf, rd, prefix, timestamp=0) - else: - # XXX: timestamp=0 - what actually do with it? - zs.write_dirs(rd, prefix=prefix, timestamp=0) - elif cmd == 'npm': - runtime, npm_requirements, prefix, tmp_dir = action[1:] - with install_npm_requirements(query, npm_requirements, tmp_dir) as rd: - if rd: + source_path = sh_work_dir + if os.path.isdir(source_path): if pf: - self._zip_write_with_filter(zs, pf, rd, prefix, - timestamp=0) + self._zip_write_with_filter( + zs, pf, source_path, prefix, timestamp=ts + ) else: - # XXX: timestamp=0 - what actually do with it? - zs.write_dirs(rd, prefix=prefix, timestamp=0) - elif cmd == 'sh': - r, w = os.pipe() - side_ch = os.fdopen(r) - path, script = action[1:] - script = "{}\npwd >&{}".format(script, w) - - p = subprocess.Popen(script, shell=True, cwd=path, - pass_fds=(w,)) - os.close(w) - sh_work_dir = side_ch.read().strip() - p.wait() - log.info('WD: %s', sh_work_dir) - side_ch.close() - elif cmd == 'set:filter': - patterns = action[1] - pf = ZipContentFilter(args=self._args) - pf.compile(patterns) - elif cmd == 'clear:filter': - pf = None + zs.write_dirs(source_path, prefix=prefix, timestamp=ts) + else: + zs.write_file(source_path, prefix=prefix, timestamp=ts) + elif cmd == "pip": + runtime, pip_requirements, prefix, tmp_dir = action[1:] + with install_pip_requirements( + query, pip_requirements, tmp_dir + ) as rd: + if rd: + if pf: + self._zip_write_with_filter( + zs, pf, rd, prefix, timestamp=0 + ) + else: + # XXX: timestamp=0 - what actually do with it? + zs.write_dirs(rd, prefix=prefix, timestamp=0) + elif cmd == "poetry": + (runtime, path, poetry_export_extra_args, prefix, tmp_dir) = action[ + 1: + ] + log.info("poetry_export_extra_args: %s", poetry_export_extra_args) + with install_poetry_dependencies( + query, path, poetry_export_extra_args, tmp_dir + ) as rd: + if rd: + if pf: + self._zip_write_with_filter( + zs, pf, rd, prefix, timestamp=0 + ) + else: + # XXX: timestamp=0 - what actually do with it? + zs.write_dirs(rd, prefix=prefix, timestamp=0) + elif cmd == "npm": + runtime, npm_requirements, prefix, tmp_dir = action[1:] + with install_npm_requirements( + query, npm_requirements, tmp_dir + ) as rd: + if rd: + if pf: + self._zip_write_with_filter( + zs, pf, rd, prefix, timestamp=0 + ) + else: + # XXX: timestamp=0 - what actually do with it? + zs.write_dirs(rd, prefix=prefix, timestamp=0) + elif cmd == "sh": + with tempfile.NamedTemporaryFile( + mode="w+t", delete=True + ) as temp_file: + script = action[1] + + if log.isEnabledFor(DEBUG2): + log.debug("exec shell script ...") + for line in script.splitlines(): + sh_log.debug(line) + + script = "\n".join( + ( + script, + # NOTE: Execute `pwd` to determine the subprocess shell's + # working directory after having executed all other commands. + "retcode=$?", + f"pwd >{temp_file.name}", + "exit $retcode", + ) + ) + + p = subprocess.Popen( + script, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=sh_work_dir, + ) + + call_stdout, call_stderr = p.communicate() + exit_code = p.returncode + log.debug("exit_code: %s", exit_code) + if exit_code != 0: + raise RuntimeError( + "Script did not run successfully, exit code {}: {} - {}".format( + exit_code, + call_stdout.decode("utf-8").strip(), + call_stderr.decode("utf-8").strip(), + ) + ) + + temp_file.seek(0) + # NOTE: This var `sh_work_dir` is consumed in cmd == "zip" loop + sh_work_dir = temp_file.read().strip() + log.debug("WORKDIR: %s", sh_work_dir) + + elif cmd == "set:workdir": + path = action[1] + sh_work_dir = os.path.normpath(os.path.join(tf_work_dir, path)) + log.debug("WORKDIR: %s", sh_work_dir) + + elif cmd == "set:filter": + patterns = action[1] + pf = ZipContentFilter(args=self._args) + pf.compile(patterns) @staticmethod - def _zip_write_with_filter(zip_stream, path_filter, source_path, prefix, - timestamp=None): + def _zip_write_with_filter( + zip_stream, path_filter, source_path, prefix, timestamp=None + ): for path in path_filter.filter(source_path, prefix): if os.path.isdir(source_path): arcname = os.path.relpath(path, source_path) @@ -939,8 +1086,9 @@ def install_pip_requirements(query, requirements_file, tmp_dir): output = check_output(docker_image_id_command(docker_image)) if output: docker_image_tag_id = output.decode().strip() - log.debug("DOCKER TAG ID: %s -> %s", - docker_image, docker_image_tag_id) + log.debug( + "DOCKER TAG ID: %s -> %s", docker_image, docker_image_tag_id + ) ok = True if ok: break @@ -952,12 +1100,13 @@ def install_pip_requirements(query, requirements_file, tmp_dir): check_call(docker_cmd) ok = True elif docker_file or docker_build_root: - raise ValueError('docker_image must be specified ' - 'for a custom image future references') + raise ValueError( + "docker_image must be specified for a custom image future references" + ) working_dir = os.getcwd() - log.info('Installing python requirements: %s', requirements_file) + log.info("Installing python requirements: %s", requirements_file) with tempdir(tmp_dir) as temp_dir: requirements_filename = os.path.basename(requirements_file) target_file = os.path.join(temp_dir, requirements_filename) @@ -968,22 +1117,27 @@ def install_pip_requirements(query, requirements_file, tmp_dir): if not docker: if WINDOWS: - python_exec = 'python.exe' + python_exec = "python.exe" elif OSX: # Workaround for OSX when XCode command line tools' # python becomes the main system python interpreter - os_path = '{}:/Library/Developer/CommandLineTools' \ - '/usr/bin'.format(os.environ['PATH']) + os_path = "{}:/Library/Developer/CommandLineTools/usr/bin".format( + os.environ["PATH"] + ) subproc_env = os.environ.copy() - subproc_env['PATH'] = os_path + subproc_env["PATH"] = os_path # Install dependencies into the temporary directory. with cd(temp_dir): pip_command = [ - python_exec, '-m', 'pip', - 'install', '--no-compile', - '--prefix=', '--target=.', - '--requirement={}'.format(requirements_filename), + python_exec, + "-m", + "pip", + "install", + "--no-compile", + "--prefix=", + "--target=.", + "--requirement={}".format(requirements_filename), ] if docker: with_ssh_agent = docker.with_ssh_agent @@ -991,27 +1145,45 @@ def install_pip_requirements(query, requirements_file, tmp_dir): if pip_cache_dir: if isinstance(pip_cache_dir, str): pip_cache_dir = os.path.abspath( - os.path.join(working_dir, pip_cache_dir)) + os.path.join(working_dir, pip_cache_dir) + ) else: - pip_cache_dir = os.path.abspath(os.path.join( - working_dir, artifacts_dir, 'cache/pip')) - - chown_mask = '{}:{}'.format(os.getuid(), os.getgid()) - shell_command = [shlex_join(pip_command), '&&', - shlex_join(['chown', '-R', - chown_mask, '.'])] - shell_command = [' '.join(shell_command)] - check_call(docker_run_command( - '.', shell_command, runtime, - image=docker_image_tag_id, - shell=True, ssh_agent=with_ssh_agent, - pip_cache_dir=pip_cache_dir, docker=docker, - )) + pip_cache_dir = os.path.abspath( + os.path.join(working_dir, artifacts_dir, "cache/pip") + ) + + chown_mask = "{}:{}".format(os.getuid(), os.getgid()) + shell_command = [ + shlex_join(pip_command), + "&&", + shlex_join(["chown", "-R", chown_mask, "."]), + ] + shell_command = [" ".join(shell_command)] + check_call( + docker_run_command( + ".", + shell_command, + runtime, + image=docker_image_tag_id, + shell=True, + ssh_agent=with_ssh_agent, + pip_cache_dir=pip_cache_dir, + docker=docker, + ) + ) else: cmd_log.info(shlex_join(pip_command)) log_handler and log_handler.flush() try: - check_call(pip_command, env=subproc_env) + if query.quiet: + check_call( + pip_command, + env=subproc_env, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + else: + check_call(pip_command, env=subproc_env) except FileNotFoundError as e: raise RuntimeError( "Python interpreter version equal " @@ -1024,19 +1196,22 @@ def install_pip_requirements(query, requirements_file, tmp_dir): @contextmanager -def install_poetry_dependencies(query, path): +def install_poetry_dependencies(query, path, poetry_export_extra_args, tmp_dir): # TODO: # 1. Emit files instead of temp_dir # pyproject.toml is always required by poetry - pyproject_file = os.path.join(path, "pyproject.toml") + pyproject_file = path + if os.path.isdir(path): + pyproject_file = os.path.join(path, "pyproject.toml") if not os.path.exists(pyproject_file): yield return # poetry.lock & poetry.toml are optional - poetry_lock_file = os.path.join(path, "poetry.lock") - poetry_toml_file = os.path.join(path, "poetry.toml") + pyproject_path = os.path.dirname(pyproject_file) + poetry_lock_file = os.path.join(pyproject_path, "poetry.lock") + poetry_toml_file = os.path.join(pyproject_path, "poetry.toml") runtime = query.runtime artifacts_dir = query.artifacts_dir @@ -1075,7 +1250,8 @@ def install_poetry_dependencies(query, path): working_dir = os.getcwd() log.info("Installing python dependencies with poetry & pip: %s", poetry_lock_file) - with tempdir() as temp_dir: + with tempdir(tmp_dir) as temp_dir: + def copy_file_to_target(file, temp_dir): filename = os.path.basename(file) target_file = os.path.join(temp_dir, filename) @@ -1085,13 +1261,13 @@ def copy_file_to_target(file, temp_dir): pyproject_target_file = copy_file_to_target(pyproject_file, temp_dir) if os.path.isfile(poetry_lock_file): - log.info("Using poetry lock file: %s", poetry_lock_file) + log.info("Using poetry.lock file: %s", poetry_lock_file) poetry_lock_target_file = copy_file_to_target(poetry_lock_file, temp_dir) else: poetry_lock_target_file = None if os.path.isfile(poetry_toml_file): - log.info("Using poetry configuration file: %s", poetry_lock_file) + log.info("Using poetry.toml configuration file: %s", poetry_toml_file) poetry_toml_target_file = copy_file_to_target(poetry_toml_file, temp_dir) else: poetry_toml_target_file = None @@ -1107,51 +1283,46 @@ def copy_file_to_target(file, temp_dir): # Install dependencies into the temporary directory. with cd(temp_dir): # NOTE: poetry must be available in the build environment, which is the case with lambci/lambda:build-python* docker images but not public.ecr.aws/sam/build-python* docker images - # FIXME: poetry install does not currently allow to specify the target directory so we export the + # FIXME: poetry install does not currently allow to specify the target directory so we export the # requirements then install them with "pip --no-deps" to avoid using pip dependency resolver + + poetry_export = [ + poetry_exec, + "export", + "--format", + "requirements.txt", + "--output", + "requirements.txt", + "--with-credentials", + ] + poetry_export_extra_args + poetry_commands = [ - shlex_join( - [ - poetry_exec, - "config", - "--no-interaction", - "virtualenvs.create", - "true", - ] - ), - shlex_join( - [ - poetry_exec, - "config", - "--no-interaction", - "virtualenvs.in-project", - "true", - ] - ), - shlex_join( - [ - poetry_exec, - "export", - "--format", - "requirements.txt", - "--output", - "requirements.txt", - "--with-credentials", - ] - ), - shlex_join( - [ - python_exec, - "-m", - "pip", - "install", - "--no-compile", - "--no-deps", - "--prefix=", - "--target=.", - "--requirement=requirements.txt", - ] - ), + [ + poetry_exec, + "config", + "--no-interaction", + "virtualenvs.create", + "true", + ], + [ + poetry_exec, + "config", + "--no-interaction", + "virtualenvs.in-project", + "true", + ], + poetry_export, + [ + python_exec, + "-m", + "pip", + "install", + "--no-compile", + "--no-deps", + "--prefix=", + "--target=.", + "--requirement=requirements.txt", + ], ] if docker: with_ssh_agent = docker.with_ssh_agent @@ -1167,7 +1338,10 @@ def copy_file_to_target(file, temp_dir): ) chown_mask = "{}:{}".format(os.getuid(), os.getgid()) - shell_commands = poetry_commands + [shlex_join(["chown", "-R", chown_mask, "."])] + poetry_commands += [["chown", "-R", chown_mask, "."]] + shell_commands = [ + shlex_join(poetry_command) for poetry_command in poetry_commands + ] shell_command = [" && ".join(shell_commands)] check_call( docker_run_command( @@ -1178,13 +1352,22 @@ def copy_file_to_target(file, temp_dir): shell=True, ssh_agent=with_ssh_agent, poetry_cache_dir=poetry_cache_dir, + docker=docker, ) ) else: cmd_log.info(poetry_commands) log_handler and log_handler.flush() for poetry_command in poetry_commands: - check_call(poetry_command, env=subproc_env) + if query.quiet: + check_call( + poetry_command, + env=subproc_env, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + else: + check_call(poetry_command, env=subproc_env) os.remove(pyproject_target_file) if poetry_lock_target_file: @@ -1221,8 +1404,9 @@ def install_npm_requirements(query, requirements_file, tmp_dir): output = check_output(docker_image_id_command(docker_image)) if output: docker_image_tag_id = output.decode().strip() - log.debug("DOCKER TAG ID: %s -> %s", - docker_image, docker_image_tag_id) + log.debug( + "DOCKER TAG ID: %s -> %s", docker_image, docker_image_tag_id + ) ok = True if ok: break @@ -1234,40 +1418,61 @@ def install_npm_requirements(query, requirements_file, tmp_dir): check_call(docker_cmd) ok = True elif docker_file or docker_build_root: - raise ValueError('docker_image must be specified ' - 'for a custom image future references') + raise ValueError( + "docker_image must be specified for a custom image future references" + ) - log.info('Installing npm requirements: %s', requirements_file) + log.info("Installing npm requirements: %s", requirements_file) with tempdir(tmp_dir) as temp_dir: - requirements_filename = os.path.basename(requirements_file) - target_file = os.path.join(temp_dir, requirements_filename) - shutil.copyfile(requirements_file, target_file) + temp_copy = TemporaryCopy(os.path.dirname(requirements_file), temp_dir, log) + temp_copy.add(os.path.basename(requirements_file)) + temp_copy.add("package-lock.json", required=False) + temp_copy.copy_to_target_dir() subproc_env = None - if not docker and OSX: - subproc_env = os.environ.copy() + npm_exec = "npm" + if not docker: + if WINDOWS: + npm_exec = "npm.cmd" + elif OSX: + subproc_env = os.environ.copy() # Install dependencies into the temporary directory. with cd(temp_dir): - npm_command = ['npm', 'install'] + npm_command = [npm_exec, "install"] if docker: with_ssh_agent = docker.with_ssh_agent - chown_mask = '{}:{}'.format(os.getuid(), os.getgid()) - shell_command = [shlex_join(npm_command), '&&', - shlex_join(['chown', '-R', - chown_mask, '.'])] - shell_command = [' '.join(shell_command)] - check_call(docker_run_command( - '.', shell_command, runtime, - image=docker_image_tag_id, - shell=True, ssh_agent=with_ssh_agent, - docker=docker, - )) + chown_mask = "{}:{}".format(os.getuid(), os.getgid()) + shell_command = [ + shlex_join(npm_command), + "&&", + shlex_join(["chown", "-R", chown_mask, "."]), + ] + shell_command = [" ".join(shell_command)] + check_call( + docker_run_command( + ".", + shell_command, + runtime, + image=docker_image_tag_id, + shell=True, + ssh_agent=with_ssh_agent, + docker=docker, + ) + ) else: cmd_log.info(shlex_join(npm_command)) log_handler and log_handler.flush() try: - check_call(npm_command, env=subproc_env) + if query.quiet: + check_call( + npm_command, + env=subproc_env, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + else: + check_call(npm_command, env=subproc_env) except FileNotFoundError as e: raise RuntimeError( "Nodejs interpreter version equal " @@ -1275,13 +1480,66 @@ def install_npm_requirements(query, requirements_file, tmp_dir): "available in system PATH".format(runtime) ) from e - os.remove(target_file) + temp_copy.remove_from_target_dir() yield temp_dir +class TemporaryCopy: + """Temporarily copy files to a specified location and remove them when + not needed. + """ + + def __init__(self, source_dir_path, target_dir_path, logger=None): + """Initialise with a target and a source directories.""" + self.source_dir_path = source_dir_path + self.target_dir_path = target_dir_path + self._filenames = [] + self._logger = logger + + def _make_source_path(self, filename): + return os.path.join(self.source_dir_path, filename) + + def _make_target_path(self, filename): + return os.path.join(self.target_dir_path, filename) + + def add(self, filename, *, required=True): + """Add a file to be copied from from source to target directory + when `TemporaryCopy.copy_to_target_dir()` is called. + + By default, the file must exist in the source directory. Set `required` + to `False` if the file is optional. + """ + if os.path.exists(self._make_source_path(filename)): + self._filenames.append(filename) + elif required: + raise RuntimeError("File not found: {}".format(filename)) + + def copy_to_target_dir(self): + """Copy files (added so far) to the target directory.""" + for filename in self._filenames: + if self._logger: + self._logger.info("Copying temporarily '%s'", filename) + + shutil.copyfile( + self._make_source_path(filename), + self._make_target_path(filename), + ) + + def remove_from_target_dir(self): + """Remove files (added so far) from the target directory.""" + for filename in self._filenames: + if self._logger: + self._logger.info("Removing temporarily copied '%s'", filename) + + try: + os.remove(self._make_target_path(filename)) + except FileNotFoundError: + pass + + def docker_image_id_command(tag): """""" - docker_cmd = ['docker', 'images', '--format={{.ID}}', tag] + docker_cmd = ["docker", "images", "--format={{.ID}}", tag] cmd_log.info(shlex_join(docker_cmd)) log_handler and log_handler.flush() return docker_cmd @@ -1290,18 +1548,18 @@ def docker_image_id_command(tag): def docker_build_command(tag=None, docker_file=None, build_root=False): """""" if not (build_root or docker_file): - raise ValueError('docker_build_root or docker_file must be provided') + raise ValueError("docker_build_root or docker_file must be provided") - docker_cmd = ['docker', 'build'] + docker_cmd = ["docker", "build"] if tag: - docker_cmd.extend(['--tag', tag]) + docker_cmd.extend(["--tag", tag]) else: - raise ValueError('docker_image must be specified') + raise ValueError("docker_image must be specified") if not build_root: build_root = os.path.dirname(docker_file) if docker_file: - docker_cmd.extend(['--file', docker_file]) + docker_cmd.extend(["--file", docker_file]) docker_cmd.append(build_root) cmd_log.info(shlex_join(docker_cmd)) @@ -1309,76 +1567,101 @@ def docker_build_command(tag=None, docker_file=None, build_root=False): return docker_cmd -def docker_run_command(build_root, command, runtime, - image=None, shell=None, ssh_agent=False, - interactive=False, pip_cache_dir=None, poetry_cache_dir=None, - docker=None): +def docker_run_command( + build_root, + command, + runtime, + image=None, + shell=None, + ssh_agent=False, + interactive=False, + pip_cache_dir=None, + poetry_cache_dir=None, + docker=None, +): """""" - if platform.system() not in ('Linux', 'Darwin'): + if platform.system() not in ("Linux", "Darwin"): raise RuntimeError("Unsupported platform for docker building") - workdir = '/var/task' + workdir = "/var/task" - docker_cmd = ['docker', 'run', '--rm', '-w', workdir] + docker_cmd = ["docker", "run", "--rm", "-w", workdir] if interactive: - docker_cmd.append('-it') + docker_cmd.append("-it") bind_path = os.path.abspath(build_root) - docker_cmd.extend(['-v', "{}:{}:z".format(bind_path, workdir)]) - - home = os.environ['HOME'] - docker_cmd.extend([ - # '-v', '{}/.ssh/id_rsa:/root/.ssh/id_rsa:z'.format(home), - '-v', '{}/.ssh/known_hosts:/root/.ssh/known_hosts:z'.format(home), - ]) + docker_cmd.extend(["-v", "{}:{}:z".format(bind_path, workdir)]) + + home = os.environ["HOME"] + docker_cmd.extend( + [ + # '-v', '{}/.ssh/id_rsa:/root/.ssh/id_rsa:z'.format(home), + "-v", + "{}/.ssh/known_hosts:/root/.ssh/known_hosts:z".format(home), + ] + ) if docker and docker.docker_additional_options: docker_cmd.extend(docker.docker_additional_options) if ssh_agent: - if platform.system() == 'Darwin': + if platform.system() == "Darwin": # https://docs.docker.com/docker-for-mac/osxfs/#ssh-agent-forwarding - docker_cmd.extend([ - '--mount', 'type=bind,' - 'src=/run/host-services/ssh-auth.sock,' - 'target=/run/host-services/ssh-auth.sock', - '-e', 'SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock', - ]) - elif platform.system() == 'Linux': - sock = os.environ['SSH_AUTH_SOCK'] # TODO: Handle missing env var - docker_cmd.extend([ - '-v', '{}:/tmp/ssh_sock:z'.format(sock), - '-e', 'SSH_AUTH_SOCK=/tmp/ssh_sock', - ]) - - if platform.system() in ('Linux', 'Darwin'): + docker_cmd.extend( + [ + "--mount", + "type=bind," + "src=/run/host-services/ssh-auth.sock," + "target=/run/host-services/ssh-auth.sock", + "-e", + "SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock", + ] + ) + elif platform.system() == "Linux": + sock = os.environ["SSH_AUTH_SOCK"] # TODO: Handle missing env var + docker_cmd.extend( + [ + "-v", + "{}:/tmp/ssh_sock:z".format(sock), + "-e", + "SSH_AUTH_SOCK=/tmp/ssh_sock", + ] + ) + + if platform.system() in ("Linux", "Darwin"): if pip_cache_dir: pip_cache_dir = os.path.abspath(pip_cache_dir) - docker_cmd.extend([ - '-v', '{}:/root/.cache/pip:z'.format(pip_cache_dir), - ]) + docker_cmd.extend( + [ + "-v", + "{}:/root/.cache/pip:z".format(pip_cache_dir), + ] + ) if poetry_cache_dir: poetry_cache_dir = os.path.abspath(poetry_cache_dir) - docker_cmd.extend([ - '-v', '{}:/root/.cache/pypoetry:z'.format(poetry_cache_dir), - ]) + docker_cmd.extend( + [ + "-v", + "{}:/root/.cache/pypoetry:z".format(poetry_cache_dir), + ] + ) if not image: - image = 'public.ecr.aws/sam/build-{}'.format(runtime) + image = "public.ecr.aws/sam/build-{}".format(runtime) if docker and docker.docker_entrypoint: - docker_cmd.extend(['--entrypoint', docker.docker_entrypoint]) + docker_cmd.extend(["--entrypoint", docker.docker_entrypoint]) else: - docker_cmd.extend(['--entrypoint', '']) + docker_cmd.extend(["--entrypoint", ""]) docker_cmd.append(image) assert isinstance(command, list) if shell: if not isinstance(shell, str): - shell = '/bin/sh' - docker_cmd.extend([shell, '-c']) + shell = "/bin/sh" + docker_cmd.extend([shell, "-c"]) docker_cmd.extend(command) cmd_log.info(shlex_join(docker_cmd)) @@ -1389,6 +1672,7 @@ def docker_run_command(build_root, command, runtime, ################################################################################ # Commands + def prepare_command(args): """ Generates a content hash of the source_path, which is used to determine if @@ -1397,7 +1681,7 @@ def prepare_command(args): Outputs a filename and a command to run if the archive needs to be built. """ - log = logging.getLogger('prepare') + log = logging.getLogger("prepare") # Load the query. query_data = json.load(sys.stdin) @@ -1405,13 +1689,13 @@ def prepare_command(args): dump_env() if log.isEnabledFor(DEBUG2): if log.isEnabledFor(DEBUG3): - log.debug('QUERY: %s', json.dumps(query_data, indent=2)) + log.debug("QUERY: %s", json.dumps(query_data, indent=2)) else: - log_excludes = ('source_path', 'hash_extra_paths', 'paths') + log_excludes = ("source_path", "hash_extra_paths", "paths") qd = {k: v for k, v in query_data.items() if k not in log_excludes} - log.debug('QUERY (excerpt): %s', json.dumps(qd, indent=2)) + log.debug("QUERY (excerpt): %s", json.dumps(qd, indent=2)) - query = datatree('prepare_query', **query_data) + query = datatree("prepare_query", **query_data) tf_paths = query.paths runtime = query.runtime @@ -1420,14 +1704,18 @@ def prepare_command(args): hash_extra_paths = query.hash_extra_paths source_path = query.source_path hash_extra = query.hash_extra - recreate_missing_package = yesno_bool(args.recreate_missing_package if args.recreate_missing_package is not None else query.recreate_missing_package) + recreate_missing_package = yesno_bool( + args.recreate_missing_package + if args.recreate_missing_package is not None + else query.recreate_missing_package + ) docker = query.docker bpm = BuildPlanManager(args, log=log) build_plan = bpm.plan(source_path, query) if log.isEnabledFor(DEBUG2): - log.debug('BUILD_PLAN: %s', json.dumps(build_plan, indent=2)) + log.debug("BUILD_PLAN: %s", json.dumps(build_plan, indent=2)) # Expand a Terraform path. references hash_extra_paths = [p.format(path=tf_paths) for p in hash_extra_paths] @@ -1439,11 +1727,11 @@ def prepare_command(args): content_hash = content_hash.hexdigest() # Generate a unique filename based on the hash. - filename = os.path.join(artifacts_dir, '{}.zip'.format(content_hash)) + zip_filename = os.path.join(artifacts_dir, "{}.zip".format(content_hash)) # Compute timestamp trigger was_missing = False - filename_path = os.path.join(os.getcwd(), filename) + filename_path = os.path.join(os.getcwd(), zip_filename) if recreate_missing_package: if os.path.exists(filename_path): st = os.stat(filename_path) @@ -1452,36 +1740,41 @@ def prepare_command(args): timestamp = timestamp_now_ns() was_missing = True else: - timestamp = "" + timestamp = "" # Replace variables in the build command with calculated values. build_data = { - 'filename': filename, - 'runtime': runtime, - 'artifacts_dir': artifacts_dir, - 'build_plan': build_plan, + "filename": zip_filename, + "runtime": runtime, + "artifacts_dir": artifacts_dir, + "build_plan": build_plan, + "quiet": query.quiet, } if docker: - build_data['docker'] = docker + build_data["docker"] = docker build_plan = json.dumps(build_data) - build_plan_filename = os.path.join(artifacts_dir, - '{}.plan.json'.format(content_hash)) + build_plan_filename = os.path.join( + artifacts_dir, "{}.plan.json".format(content_hash) + ) if not os.path.exists(artifacts_dir): os.makedirs(artifacts_dir, exist_ok=True) - with open(build_plan_filename, 'w') as f: + with open(build_plan_filename, "w") as f: f.write(build_plan) # Output the result to Terraform. - json.dump({ - 'filename': filename, - 'build_plan': build_plan, - 'build_plan_filename': build_plan_filename, - 'timestamp': str(timestamp), - 'was_missing': 'true' if was_missing else 'false', - }, sys.stdout, indent=2) - sys.stdout.write('\n') + json.dump( + { + "filename": zip_filename, + "build_plan": build_plan, + "build_plan_filename": build_plan_filename, + "timestamp": str(timestamp), + "was_missing": "true" if was_missing else "false", + }, + sys.stdout, + indent=2, + ) + sys.stdout.write("\n") def build_command(args): @@ -1490,15 +1783,15 @@ def build_command(args): Installs dependencies with pip or npm automatically. """ - log = logging.getLogger('build') + log = logging.getLogger("build") dump_env() if log.isEnabledFor(DEBUG2): - log.debug('CMD: python3 %s', shlex_join(sys.argv)) + log.debug("CMD: python3 %s", shlex_join(sys.argv)) with open(args.build_plan_file) as f: query_data = json.load(f) - query = datatree('build_query', **query_data) + query = datatree("build_query", **query_data) runtime = query.runtime filename = query.filename @@ -1510,20 +1803,21 @@ def build_command(args): timestamp = int(_timestamp) if os.path.exists(filename) and not args.force: - log.info('Reused: %s', shlex.quote(filename)) + log.info("Reused: %s", shlex.quote(filename)) return # Zip up the build plan and write it to the target filename. # This will be used by the Lambda function as the source code package. - with ZipWriteStream(filename) as zs: + with ZipWriteStream(filename, quiet=getattr(query, "quiet", False)) as zs: bpm = BuildPlanManager(args, log=log) bpm.execute(build_plan, zs, query) os.utime(filename, ns=(timestamp, timestamp)) - log.info('Created: %s', shlex.quote(filename)) + if not getattr(query, "quiet", False): + log.info("Created: %s", shlex.quote(filename)) if log.isEnabledFor(logging.DEBUG): - with open(filename, 'rb') as f: - log.info('Base64sha256: %s', source_code_hash(f.read())) + with open(filename, "rb") as f: + log.info("Base64sha256: %s", source_code_hash(f.read())) def add_hidden_commands(sub_parsers): @@ -1534,22 +1828,34 @@ def hidden_parser(name, **kwargs): sp._choices_actions.pop() # XXX: help=argparse.SUPPRESS - doesn't work return p - p = hidden_parser('docker', help='Run docker build') - p.set_defaults(command=lambda args: subprocess.call(docker_run_command( - args.build_root, args.docker_command, args.runtime, interactive=True))) - p.add_argument('build_root', help='A docker build root folder') - p.add_argument('docker_command', help='A docker container command', - metavar='command', nargs=argparse.REMAINDER) - p.add_argument('-r', '--runtime', help='A docker image runtime', - default='python3.8') - - p = hidden_parser('docker-image', help='Run docker build') - p.set_defaults(command=lambda args: subprocess.call(docker_build_command( - args.build_root, args.docker_file, args.tag))) - p.add_argument('-t', '--tag', help='A docker image tag') - p.add_argument('build_root', help='A docker build root folder') - p.add_argument('docker_file', help='A docker file path', - nargs=argparse.OPTIONAL) + p = hidden_parser("docker", help="Run docker build") + p.set_defaults( + command=lambda args: subprocess.call( + docker_run_command( + args.build_root, args.docker_command, args.runtime, interactive=True + ) + ) + ) + p.add_argument("build_root", help="A docker build root folder") + p.add_argument( + "docker_command", + help="A docker container command", + metavar="command", + nargs=argparse.REMAINDER, + ) + p.add_argument( + "-r", "--runtime", help="A docker image runtime", default="python3.12" + ) + + p = hidden_parser("docker-image", help="Run docker build") + p.set_defaults( + command=lambda args: subprocess.call( + docker_build_command(args.build_root, args.docker_file, args.tag) + ) + ) + p.add_argument("-t", "--tag", help="A docker image tag") + p.add_argument("build_root", help="A docker build root folder") + p.add_argument("docker_file", help="A docker file path", nargs=argparse.OPTIONAL) def zip_cmd(args): if args.verbose: @@ -1557,27 +1863,33 @@ def zip_cmd(args): with ZipWriteStream(args.zipfile) as zs: zs.write_dirs(*args.dir, timestamp=args.timestamp) if log.isEnabledFor(logging.DEBUG): - zipinfo = shutil.which('zipinfo') + zipinfo = shutil.which("zipinfo") if zipinfo: - log.debug('-' * 80) + log.debug("-" * 80) subprocess.call([zipinfo, args.zipfile]) - log.debug('-' * 80) - log.debug('Source code hash: %s', - source_code_hash(open(args.zipfile, 'rb').read())) + log.debug("-" * 80) + log.debug( + "Source code hash: %s", + source_code_hash(open(args.zipfile, "rb").read()), + ) - p = hidden_parser('zip', help='Zip folder with provided files timestamp') + p = hidden_parser("zip", help="Zip folder with provided files timestamp") p.set_defaults(command=zip_cmd) - p.add_argument('zipfile', help='Path to a zip file') - p.add_argument('dir', nargs=argparse.ONE_OR_MORE, - help='Path to a directory for packaging') - p.add_argument('-t', '--timestamp', type=int, - help='A timestamp to override for all zip members') - p.add_argument('-v', '--verbose', action='store_true') - - p = hidden_parser('hash', help='Generate content hash for a file') - p.set_defaults( - command=lambda args: print(source_code_hash(args.file.read()))) - p.add_argument('file', help='Path to a file', type=argparse.FileType('rb')) + p.add_argument("zipfile", help="Path to a zip file") + p.add_argument( + "dir", nargs=argparse.ONE_OR_MORE, help="Path to a directory for packaging" + ) + p.add_argument( + "-t", + "--timestamp", + type=int, + help="A timestamp to override for all zip members", + ) + p.add_argument("-v", "--verbose", action="store_true") + + p = hidden_parser("hash", help="Generate content hash for a file") + p.set_defaults(command=lambda args: print(source_code_hash(args.file.read()))) + p.add_argument("file", help="Path to a file", type=argparse.FileType("rb")) def args_parser(): @@ -1585,31 +1897,41 @@ def args_parser(): ap.set_defaults(command=lambda _: ap.print_usage()) sp = ap.add_subparsers(metavar="COMMAND") - p = sp.add_parser('prepare', - help='compute a filename hash for a zip archive') + p = sp.add_parser("prepare", help="compute a filename hash for a zip archive") p.set_defaults(command=prepare_command) - p = sp.add_parser('build', - help='build and pack to a zip archive') + p = sp.add_parser("build", help="build and pack to a zip archive") p.set_defaults(command=build_command) - p.add_argument('--force', action='store_true', - help='Force rebuilding even if a zip artifact exists') - p.add_argument('-t', '--timestamp', - dest='zip_file_timestamp', required=True, - help='A zip file timestamp generated by the prepare command') - p.add_argument('build_plan_file', metavar='PLAN_FILE', - help='A build plan file provided by the prepare command') + p.add_argument( + "--force", + action="store_true", + help="Force rebuilding even if a zip artifact exists", + ) + p.add_argument( + "-t", + "--timestamp", + dest="zip_file_timestamp", + required=True, + help="A zip file timestamp generated by the prepare command", + ) + p.add_argument( + "build_plan_file", + metavar="PLAN_FILE", + help="A build plan file provided by the prepare command", + ) add_hidden_commands(sp) return ap def main(): ns = argparse.Namespace( - pattern_comments=yesno_bool(os.environ.get( - 'TF_LAMBDA_PACKAGE_PATTERN_COMMENTS', False)), + pattern_comments=yesno_bool( + os.environ.get("TF_LAMBDA_PACKAGE_PATTERN_COMMENTS", False) + ), recreate_missing_package=os.environ.get( - 'TF_RECREATE_MISSING_LAMBDA_PACKAGE', None), - log_level=os.environ.get('TF_LAMBDA_PACKAGE_LOG_LEVEL', 'INFO'), + "TF_RECREATE_MISSING_LAMBDA_PACKAGE", None + ), + log_level=os.environ.get("TF_LAMBDA_PACKAGE_LOG_LEVEL", "INFO"), ) p = args_parser() @@ -1628,5 +1950,5 @@ def main(): exit(args.command(args)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/package.tf b/package.tf index b68dc89f..99078600 100644 --- a/package.tf +++ b/package.tf @@ -40,6 +40,7 @@ data "external" "archive_prepare" { ) recreate_missing_package = var.recreate_missing_package + quiet = var.quiet_archive_local_exec } } @@ -49,7 +50,7 @@ data "external" "archive_prepare" { resource "local_file" "archive_plan" { count = var.create && var.create_package ? 1 : 0 - content = data.external.archive_prepare[0].result.build_plan + content = var.build_in_docker ? sensitive(data.external.archive_prepare[0].result.build_plan) : data.external.archive_prepare[0].result.build_plan filename = data.external.archive_prepare[0].result.build_plan_filename directory_permission = "0755" file_permission = "0644" @@ -61,7 +62,7 @@ resource "null_resource" "archive" { triggers = { filename = data.external.archive_prepare[0].result.filename - timestamp = data.external.archive_prepare[0].result.timestamp + timestamp = var.trigger_on_package_timestamp ? data.external.archive_prepare[0].result.timestamp : null } provisioner "local-exec" { @@ -70,6 +71,7 @@ resource "null_resource" "archive" { "--timestamp", data.external.archive_prepare[0].result.timestamp ] command = data.external.archive_prepare[0].result.build_plan_filename + quiet = var.quiet_archive_local_exec } depends_on = [local_file.archive_plan] diff --git a/tests/fixtures/node-app/index.js b/tests/fixtures/node-app/index.js new file mode 100644 index 00000000..09d4352e --- /dev/null +++ b/tests/fixtures/node-app/index.js @@ -0,0 +1 @@ +// test diff --git a/tests/fixtures/node-app/package.json b/tests/fixtures/node-app/package.json new file mode 100644 index 00000000..1bd4d69d --- /dev/null +++ b/tests/fixtures/node-app/package.json @@ -0,0 +1,16 @@ +{ + "name": "app", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + }, + "devDependencies": { + "axios": "^1.7.3" + } +} diff --git a/tests/test_package_toml.py b/tests/test_package_toml.py index 129ac588..9eba3f4a 100644 --- a/tests/test_package_toml.py +++ b/tests/test_package_toml.py @@ -1,4 +1,6 @@ -from package import get_build_system_from_pyproject_toml +from package import get_build_system_from_pyproject_toml, BuildPlanManager +from pytest import raises +from unittest.mock import Mock def test_get_build_system_from_pyproject_toml_inexistent(): @@ -14,10 +16,26 @@ def test_get_build_system_from_pyproject_toml_unknown(): ) +def test_build_manager_sucess_command(): + bpm = BuildPlanManager(args=Mock()) + # Should not have exception raised + bpm.execute(build_plan=[["sh", "/tmp", "pwd"]], zip_stream=None, query=None) + + +def test_build_manager_failing_command(): + bpm = BuildPlanManager(args=Mock()) + with raises(Exception): + bpm.execute( + build_plan=[[["sh", "/tmp", "NOTACOMMAND"]]], + zip_stream=None, + query=None, + ) + + def test_get_build_system_from_pyproject_toml_poetry(): assert ( get_build_system_from_pyproject_toml( - "examples/fixtures/python3.9-app-poetry/pyproject.toml" + "examples/fixtures/python-app-poetry/pyproject.toml" ) == "poetry" ) diff --git a/tests/test_zip_source.py b/tests/test_zip_source.py new file mode 100644 index 00000000..dd6750ca --- /dev/null +++ b/tests/test_zip_source.py @@ -0,0 +1,50 @@ +import os +from unittest.mock import MagicMock, Mock + +from package import BuildPlanManager + + +def test_zip_source_path_sh_work_dir(): + zs = Mock() + zs.write_dirs = MagicMock() + + bpm = BuildPlanManager(args=Mock()) + + bpm.execute( + build_plan=[ + [ + ["sh", "cd $(mktemp -d)\n echo pip install"], + ["zip:embedded", ".", "./python"], + ] + ], + zip_stream=zs, + query=None, + ) + + zs.write_dirs.assert_called_once() + + zip_source_path = zs.write_dirs.call_args_list[0][0][0] + assert zip_source_path != f"{os.getcwd()}" + + +def test_zip_source_path(): + zs = Mock() + zs.write_dirs = MagicMock() + + bpm = BuildPlanManager(args=Mock()) + + bpm.execute( + build_plan=[ + [ + ["sh", "echo pip install"], + ["zip:embedded", ".", "./python"], + ] + ], + zip_stream=zs, + query=None, + ) + + zs.write_dirs.assert_called_once() + + zip_source_path = zs.write_dirs.call_args_list[0][0][0] + assert zip_source_path == f"{os.getcwd()}" diff --git a/variables.tf b/variables.tf index c245db7a..6ea454f6 100644 --- a/variables.tf +++ b/variables.tf @@ -34,12 +34,24 @@ variable "create_lambda_function_url" { default = false } +variable "create_sam_metadata" { + description = "Controls whether the SAM metadata null resource should be created" + type = bool + default = false +} + variable "putin_khuylo" { description = "Do you agree that Putin doesn't respect Ukrainian sovereignty and territorial integrity? More info: https://en.wikipedia.org/wiki/Putin_khuylo!" type = bool default = true } +variable "region" { + description = "Region where the resource(s) will be managed. Defaults to the region set in the provider configuration" + type = string + default = null +} + ########### # Function ########### @@ -50,6 +62,12 @@ variable "lambda_at_edge" { default = false } +variable "lambda_at_edge_logs_all_regions" { + description = "Whether to specify a wildcard in IAM policy used by Lambda@Edge to allow logging in all regions" + type = bool + default = true +} + variable "function_name" { description = "A unique name for your Lambda Function" type = string @@ -66,11 +84,6 @@ variable "runtime" { description = "Lambda Function runtime" type = string default = "" - - # validation { - # condition = can(var.create && contains(["nodejs10.x", "nodejs12.x", "java8", "java11", "python2.7", " python3.6", "python3.7", "python3.8", "dotnetcore2.1", "dotnetcore3.1", "go1.x", "ruby2.5", "ruby2.7", "provided"], var.runtime)) - # error_message = "The runtime value must be one of supported by AWS Lambda." - # } } variable "lambda_role" { @@ -169,12 +182,30 @@ variable "vpc_security_group_ids" { default = null } +variable "ipv6_allowed_for_dual_stack" { + description = "Allows outbound IPv6 traffic on VPC functions that are connected to dual-stack subnets" + type = bool + default = null +} + variable "tags" { description = "A map of tags to assign to resources." type = map(string) default = {} } +variable "include_default_tag" { + description = "Set to false to not include the default tag in the tags map." + type = bool + default = true +} + +variable "function_tags" { + description = "A map of tags to assign only to the lambda function" + type = map(string) + default = {} +} + variable "s3_object_tags" { description = "A map of tags to assign to S3 bucket object." type = map(string) @@ -217,6 +248,36 @@ variable "image_config_working_directory" { default = null } +variable "snap_start" { + description = "(Optional) Snap start settings for low-latency startups" + type = bool + default = false +} + +variable "replace_security_groups_on_destroy" { + description = "(Optional) When true, all security groups defined in vpc_security_group_ids will be replaced with the default security group after the function is destroyed. Set the replacement_security_group_ids variable to use a custom list of security groups for replacement instead." + type = bool + default = null +} + +variable "replacement_security_group_ids" { + description = "(Optional) List of security group IDs to assign to orphaned Lambda function network interfaces upon destruction. replace_security_groups_on_destroy must be set to true to use this attribute." + type = list(string) + default = null +} + +variable "timeouts" { + description = "Define maximum timeout for creating, updating, and deleting Lambda Function resources" + type = map(string) + default = {} +} + +variable "skip_destroy" { + description = "Set to true if you do not wish the function to be deleted at destroy time, and instead just remove the function from the Terraform state. Useful for Lambda@Edge functions attached to CloudFront distributions." + type = bool + default = null +} + ############### # Function URL ############### @@ -239,6 +300,18 @@ variable "cors" { default = {} } +variable "invoke_mode" { + description = "Invoke mode of the Lambda Function URL. Valid values are BUFFERED (default) and RESPONSE_STREAM." + type = string + default = null +} + +variable "s3_object_override_default_tags" { + description = "Whether to override the default_tags from provider? NB: S3 objects support a maximum of 10 tags." + type = bool + default = false +} + ######## # Layer ######## @@ -383,6 +456,18 @@ variable "cloudwatch_logs_kms_key_id" { default = null } +variable "cloudwatch_logs_skip_destroy" { + description = "Whether to keep the log group (and any logs it may contain) at destroy time." + type = bool + default = false +} + +variable "cloudwatch_logs_log_group_class" { + description = "Specified the log class of the log group. Possible values are: `STANDARD` or `INFREQUENT_ACCESS`" + type = string + default = null +} + variable "cloudwatch_logs_tags" { description = "A map of tags to assign to the resource." type = map(string) @@ -429,6 +514,12 @@ variable "role_tags" { default = {} } +variable "role_maximum_session_duration" { + description = "Maximum session duration, in seconds, for the IAM role" + type = number + default = 3600 +} + ########### # Policies ########### @@ -445,6 +536,12 @@ variable "attach_cloudwatch_logs_policy" { default = true } +variable "attach_create_log_group_permission" { + description = "Controls whether to add the create log group permission to the CloudWatch logs policy" + type = bool + default = true +} + variable "attach_dead_letter_policy" { description = "Controls whether SNS/SQS dead letter notification policy should be added to IAM role for Lambda Function" type = bool @@ -493,12 +590,6 @@ variable "attach_policies" { default = false } -variable "policy_path" { - description = "Path of policies to that should be added to IAM role for Lambda Function" - type = string - default = null -} - variable "number_of_policy_jsons" { description = "Number of policies JSON to attach to IAM role for Lambda Function" type = number @@ -635,6 +726,12 @@ variable "s3_server_side_encryption" { default = null } +variable "s3_kms_key_id" { + description = "Specifies a custom KMS key to use for S3 object encryption." + type = string + default = null +} + variable "source_path" { description = "The absolute path to a local file or directory containing your Lambda source code" type = any # string | list(string | map(any)) @@ -700,3 +797,53 @@ variable "recreate_missing_package" { type = bool default = true } + +variable "trigger_on_package_timestamp" { + description = "Whether to recreate the Lambda package if the timestamp changes" + type = bool + default = true +} + +variable "quiet_archive_local_exec" { + description = "Whether to disable archive local execution output" + type = bool + default = true +} + +############################################ +# Lambda Advanced Logging Settings +############################################ + +variable "logging_log_format" { + description = "The log format of the Lambda Function. Valid values are \"JSON\" or \"Text\"." + type = string + default = "Text" +} + +variable "logging_application_log_level" { + description = "The application log level of the Lambda Function. Valid values are \"TRACE\", \"DEBUG\", \"INFO\", \"WARN\", \"ERROR\", or \"FATAL\"." + type = string + default = "INFO" +} + +variable "logging_system_log_level" { + description = "The system log level of the Lambda Function. Valid values are \"DEBUG\", \"INFO\", or \"WARN\"." + type = string + default = "INFO" +} + +variable "logging_log_group" { + description = "The CloudWatch log group to send logs to." + type = string + default = null +} + +############################################ +# Lambda Recursive Loop Settings +############################################ + +variable "recursive_loop" { + description = "Lambda function recursion configuration. Valid values are Allow or Terminate." + type = string + default = null +} diff --git a/versions.tf b/versions.tf index 094732c8..8dea461c 100644 --- a/versions.tf +++ b/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.9" + version = ">= 6.0" } external = { source = "hashicorp/external" diff --git a/wrappers/README.md b/wrappers/README.md new file mode 100644 index 00000000..954ea7d1 --- /dev/null +++ b/wrappers/README.md @@ -0,0 +1,100 @@ +# Wrapper for the root module + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/lambda/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-lambda.git//wrappers?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/lambda/aws//wrappers" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/wrappers/alias/README.md b/wrappers/alias/README.md new file mode 100644 index 00000000..a296ced7 --- /dev/null +++ b/wrappers/alias/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/alias` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/lambda/aws//wrappers/alias" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-lambda.git//wrappers/alias?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/lambda/aws//wrappers/alias" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/wrappers/alias/main.tf b/wrappers/alias/main.tf new file mode 100644 index 00000000..7729dd06 --- /dev/null +++ b/wrappers/alias/main.tf @@ -0,0 +1,25 @@ +module "wrapper" { + source = "../../modules/alias" + + for_each = var.items + + allowed_triggers = try(each.value.allowed_triggers, var.defaults.allowed_triggers, {}) + create = try(each.value.create, var.defaults.create, true) + create_async_event_config = try(each.value.create_async_event_config, var.defaults.create_async_event_config, false) + create_qualified_alias_allowed_triggers = try(each.value.create_qualified_alias_allowed_triggers, var.defaults.create_qualified_alias_allowed_triggers, true) + create_qualified_alias_async_event_config = try(each.value.create_qualified_alias_async_event_config, var.defaults.create_qualified_alias_async_event_config, true) + create_version_allowed_triggers = try(each.value.create_version_allowed_triggers, var.defaults.create_version_allowed_triggers, true) + create_version_async_event_config = try(each.value.create_version_async_event_config, var.defaults.create_version_async_event_config, true) + description = try(each.value.description, var.defaults.description, "") + destination_on_failure = try(each.value.destination_on_failure, var.defaults.destination_on_failure, null) + destination_on_success = try(each.value.destination_on_success, var.defaults.destination_on_success, null) + event_source_mapping = try(each.value.event_source_mapping, var.defaults.event_source_mapping, {}) + function_name = try(each.value.function_name, var.defaults.function_name, "") + function_version = try(each.value.function_version, var.defaults.function_version, "") + maximum_event_age_in_seconds = try(each.value.maximum_event_age_in_seconds, var.defaults.maximum_event_age_in_seconds, null) + maximum_retry_attempts = try(each.value.maximum_retry_attempts, var.defaults.maximum_retry_attempts, null) + name = try(each.value.name, var.defaults.name, "") + refresh_alias = try(each.value.refresh_alias, var.defaults.refresh_alias, true) + routing_additional_version_weights = try(each.value.routing_additional_version_weights, var.defaults.routing_additional_version_weights, {}) + use_existing_alias = try(each.value.use_existing_alias, var.defaults.use_existing_alias, false) +} diff --git a/wrappers/alias/outputs.tf b/wrappers/alias/outputs.tf new file mode 100644 index 00000000..ec6da5f4 --- /dev/null +++ b/wrappers/alias/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/wrappers/alias/variables.tf b/wrappers/alias/variables.tf new file mode 100644 index 00000000..a6ea0962 --- /dev/null +++ b/wrappers/alias/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/wrappers/alias/versions.tf b/wrappers/alias/versions.tf new file mode 100644 index 00000000..db13b0a8 --- /dev/null +++ b/wrappers/alias/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.0" + } + } +} diff --git a/wrappers/deploy/README.md b/wrappers/deploy/README.md new file mode 100644 index 00000000..5d24d8b2 --- /dev/null +++ b/wrappers/deploy/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/deploy` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/lambda/aws//wrappers/deploy" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-lambda.git//wrappers/deploy?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/lambda/aws//wrappers/deploy" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/wrappers/deploy/main.tf b/wrappers/deploy/main.tf new file mode 100644 index 00000000..47cc3d8b --- /dev/null +++ b/wrappers/deploy/main.tf @@ -0,0 +1,41 @@ +module "wrapper" { + source = "../../modules/deploy" + + for_each = var.items + + after_allow_traffic_hook_arn = try(each.value.after_allow_traffic_hook_arn, var.defaults.after_allow_traffic_hook_arn, "") + alarm_enabled = try(each.value.alarm_enabled, var.defaults.alarm_enabled, false) + alarm_ignore_poll_alarm_failure = try(each.value.alarm_ignore_poll_alarm_failure, var.defaults.alarm_ignore_poll_alarm_failure, false) + alarms = try(each.value.alarms, var.defaults.alarms, []) + alias_name = try(each.value.alias_name, var.defaults.alias_name, "") + app_name = try(each.value.app_name, var.defaults.app_name, "") + attach_hooks_policy = try(each.value.attach_hooks_policy, var.defaults.attach_hooks_policy, true) + attach_triggers_policy = try(each.value.attach_triggers_policy, var.defaults.attach_triggers_policy, false) + auto_rollback_enabled = try(each.value.auto_rollback_enabled, var.defaults.auto_rollback_enabled, true) + auto_rollback_events = try(each.value.auto_rollback_events, var.defaults.auto_rollback_events, ["DEPLOYMENT_STOP_ON_ALARM"]) + aws_cli_command = try(each.value.aws_cli_command, var.defaults.aws_cli_command, "aws") + before_allow_traffic_hook_arn = try(each.value.before_allow_traffic_hook_arn, var.defaults.before_allow_traffic_hook_arn, "") + codedeploy_principals = try(each.value.codedeploy_principals, var.defaults.codedeploy_principals, ["codedeploy.amazonaws.com"]) + codedeploy_role_name = try(each.value.codedeploy_role_name, var.defaults.codedeploy_role_name, "") + create = try(each.value.create, var.defaults.create, true) + create_app = try(each.value.create_app, var.defaults.create_app, false) + create_codedeploy_role = try(each.value.create_codedeploy_role, var.defaults.create_codedeploy_role, true) + create_deployment = try(each.value.create_deployment, var.defaults.create_deployment, false) + create_deployment_group = try(each.value.create_deployment_group, var.defaults.create_deployment_group, false) + current_version = try(each.value.current_version, var.defaults.current_version, "") + deployment_config_name = try(each.value.deployment_config_name, var.defaults.deployment_config_name, "CodeDeployDefault.LambdaAllAtOnce") + deployment_group_name = try(each.value.deployment_group_name, var.defaults.deployment_group_name, "") + description = try(each.value.description, var.defaults.description, "") + force_deploy = try(each.value.force_deploy, var.defaults.force_deploy, false) + function_name = try(each.value.function_name, var.defaults.function_name, "") + get_deployment_sleep_timer = try(each.value.get_deployment_sleep_timer, var.defaults.get_deployment_sleep_timer, 5) + interpreter = try(each.value.interpreter, var.defaults.interpreter, ["/bin/bash", "-c"]) + run_deployment = try(each.value.run_deployment, var.defaults.run_deployment, false) + save_deploy_script = try(each.value.save_deploy_script, var.defaults.save_deploy_script, false) + tags = try(each.value.tags, var.defaults.tags, {}) + target_version = try(each.value.target_version, var.defaults.target_version, "") + triggers = try(each.value.triggers, var.defaults.triggers, {}) + use_existing_app = try(each.value.use_existing_app, var.defaults.use_existing_app, false) + use_existing_deployment_group = try(each.value.use_existing_deployment_group, var.defaults.use_existing_deployment_group, false) + wait_deployment_completion = try(each.value.wait_deployment_completion, var.defaults.wait_deployment_completion, false) +} diff --git a/wrappers/deploy/outputs.tf b/wrappers/deploy/outputs.tf new file mode 100644 index 00000000..ec6da5f4 --- /dev/null +++ b/wrappers/deploy/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/wrappers/deploy/variables.tf b/wrappers/deploy/variables.tf new file mode 100644 index 00000000..a6ea0962 --- /dev/null +++ b/wrappers/deploy/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/wrappers/deploy/versions.tf b/wrappers/deploy/versions.tf new file mode 100644 index 00000000..ddb64c76 --- /dev/null +++ b/wrappers/deploy/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.0" + } + local = { + source = "hashicorp/local" + version = ">= 1.0" + } + null = { + source = "hashicorp/null" + version = ">= 2.0" + } + } +} diff --git a/wrappers/docker-build/README.md b/wrappers/docker-build/README.md new file mode 100644 index 00000000..093f989b --- /dev/null +++ b/wrappers/docker-build/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/docker-build` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/lambda/aws//wrappers/docker-build" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-lambda.git//wrappers/docker-build?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/lambda/aws//wrappers/docker-build" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/wrappers/docker-build/main.tf b/wrappers/docker-build/main.tf new file mode 100644 index 00000000..61a99a93 --- /dev/null +++ b/wrappers/docker-build/main.tf @@ -0,0 +1,28 @@ +module "wrapper" { + source = "../../modules/docker-build" + + for_each = var.items + + build_args = try(each.value.build_args, var.defaults.build_args, {}) + build_target = try(each.value.build_target, var.defaults.build_target, null) + builder = try(each.value.builder, var.defaults.builder, null) + cache_from = try(each.value.cache_from, var.defaults.cache_from, []) + create_ecr_repo = try(each.value.create_ecr_repo, var.defaults.create_ecr_repo, false) + create_sam_metadata = try(each.value.create_sam_metadata, var.defaults.create_sam_metadata, false) + docker_file_path = try(each.value.docker_file_path, var.defaults.docker_file_path, "Dockerfile") + ecr_address = try(each.value.ecr_address, var.defaults.ecr_address, null) + ecr_force_delete = try(each.value.ecr_force_delete, var.defaults.ecr_force_delete, true) + ecr_repo = try(each.value.ecr_repo, var.defaults.ecr_repo, null) + ecr_repo_lifecycle_policy = try(each.value.ecr_repo_lifecycle_policy, var.defaults.ecr_repo_lifecycle_policy, null) + ecr_repo_tags = try(each.value.ecr_repo_tags, var.defaults.ecr_repo_tags, {}) + force_remove = try(each.value.force_remove, var.defaults.force_remove, false) + image_tag = try(each.value.image_tag, var.defaults.image_tag, null) + image_tag_mutability = try(each.value.image_tag_mutability, var.defaults.image_tag_mutability, "MUTABLE") + keep_locally = try(each.value.keep_locally, var.defaults.keep_locally, false) + keep_remotely = try(each.value.keep_remotely, var.defaults.keep_remotely, false) + platform = try(each.value.platform, var.defaults.platform, null) + scan_on_push = try(each.value.scan_on_push, var.defaults.scan_on_push, false) + source_path = try(each.value.source_path, var.defaults.source_path, null) + triggers = try(each.value.triggers, var.defaults.triggers, {}) + use_image_tag = try(each.value.use_image_tag, var.defaults.use_image_tag, true) +} diff --git a/wrappers/docker-build/outputs.tf b/wrappers/docker-build/outputs.tf new file mode 100644 index 00000000..ec6da5f4 --- /dev/null +++ b/wrappers/docker-build/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/wrappers/docker-build/variables.tf b/wrappers/docker-build/variables.tf new file mode 100644 index 00000000..a6ea0962 --- /dev/null +++ b/wrappers/docker-build/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/wrappers/docker-build/versions.tf b/wrappers/docker-build/versions.tf new file mode 100644 index 00000000..b203b635 --- /dev/null +++ b/wrappers/docker-build/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.0" + } + docker = { + source = "kreuzwerker/docker" + version = ">= 3.5.0" + } + null = { + source = "hashicorp/null" + version = ">= 2.0" + } + } +} diff --git a/wrappers/main.tf b/wrappers/main.tf new file mode 100644 index 00000000..eb78bb41 --- /dev/null +++ b/wrappers/main.tf @@ -0,0 +1,139 @@ +module "wrapper" { + source = "../" + + for_each = var.items + + allowed_triggers = try(each.value.allowed_triggers, var.defaults.allowed_triggers, {}) + architectures = try(each.value.architectures, var.defaults.architectures, null) + artifacts_dir = try(each.value.artifacts_dir, var.defaults.artifacts_dir, "builds") + assume_role_policy_statements = try(each.value.assume_role_policy_statements, var.defaults.assume_role_policy_statements, {}) + attach_async_event_policy = try(each.value.attach_async_event_policy, var.defaults.attach_async_event_policy, false) + attach_cloudwatch_logs_policy = try(each.value.attach_cloudwatch_logs_policy, var.defaults.attach_cloudwatch_logs_policy, true) + attach_create_log_group_permission = try(each.value.attach_create_log_group_permission, var.defaults.attach_create_log_group_permission, true) + attach_dead_letter_policy = try(each.value.attach_dead_letter_policy, var.defaults.attach_dead_letter_policy, false) + attach_network_policy = try(each.value.attach_network_policy, var.defaults.attach_network_policy, false) + attach_policies = try(each.value.attach_policies, var.defaults.attach_policies, false) + attach_policy = try(each.value.attach_policy, var.defaults.attach_policy, false) + attach_policy_json = try(each.value.attach_policy_json, var.defaults.attach_policy_json, false) + attach_policy_jsons = try(each.value.attach_policy_jsons, var.defaults.attach_policy_jsons, false) + attach_policy_statements = try(each.value.attach_policy_statements, var.defaults.attach_policy_statements, false) + attach_tracing_policy = try(each.value.attach_tracing_policy, var.defaults.attach_tracing_policy, false) + authorization_type = try(each.value.authorization_type, var.defaults.authorization_type, "NONE") + build_in_docker = try(each.value.build_in_docker, var.defaults.build_in_docker, false) + cloudwatch_logs_kms_key_id = try(each.value.cloudwatch_logs_kms_key_id, var.defaults.cloudwatch_logs_kms_key_id, null) + cloudwatch_logs_log_group_class = try(each.value.cloudwatch_logs_log_group_class, var.defaults.cloudwatch_logs_log_group_class, null) + cloudwatch_logs_retention_in_days = try(each.value.cloudwatch_logs_retention_in_days, var.defaults.cloudwatch_logs_retention_in_days, null) + cloudwatch_logs_skip_destroy = try(each.value.cloudwatch_logs_skip_destroy, var.defaults.cloudwatch_logs_skip_destroy, false) + cloudwatch_logs_tags = try(each.value.cloudwatch_logs_tags, var.defaults.cloudwatch_logs_tags, {}) + code_signing_config_arn = try(each.value.code_signing_config_arn, var.defaults.code_signing_config_arn, null) + compatible_architectures = try(each.value.compatible_architectures, var.defaults.compatible_architectures, null) + compatible_runtimes = try(each.value.compatible_runtimes, var.defaults.compatible_runtimes, []) + cors = try(each.value.cors, var.defaults.cors, {}) + create = try(each.value.create, var.defaults.create, true) + create_async_event_config = try(each.value.create_async_event_config, var.defaults.create_async_event_config, false) + create_current_version_allowed_triggers = try(each.value.create_current_version_allowed_triggers, var.defaults.create_current_version_allowed_triggers, true) + create_current_version_async_event_config = try(each.value.create_current_version_async_event_config, var.defaults.create_current_version_async_event_config, true) + create_function = try(each.value.create_function, var.defaults.create_function, true) + create_lambda_function_url = try(each.value.create_lambda_function_url, var.defaults.create_lambda_function_url, false) + create_layer = try(each.value.create_layer, var.defaults.create_layer, false) + create_package = try(each.value.create_package, var.defaults.create_package, true) + create_role = try(each.value.create_role, var.defaults.create_role, true) + create_sam_metadata = try(each.value.create_sam_metadata, var.defaults.create_sam_metadata, false) + create_unqualified_alias_allowed_triggers = try(each.value.create_unqualified_alias_allowed_triggers, var.defaults.create_unqualified_alias_allowed_triggers, true) + create_unqualified_alias_async_event_config = try(each.value.create_unqualified_alias_async_event_config, var.defaults.create_unqualified_alias_async_event_config, true) + create_unqualified_alias_lambda_function_url = try(each.value.create_unqualified_alias_lambda_function_url, var.defaults.create_unqualified_alias_lambda_function_url, true) + dead_letter_target_arn = try(each.value.dead_letter_target_arn, var.defaults.dead_letter_target_arn, null) + description = try(each.value.description, var.defaults.description, "") + destination_on_failure = try(each.value.destination_on_failure, var.defaults.destination_on_failure, null) + destination_on_success = try(each.value.destination_on_success, var.defaults.destination_on_success, null) + docker_additional_options = try(each.value.docker_additional_options, var.defaults.docker_additional_options, []) + docker_build_root = try(each.value.docker_build_root, var.defaults.docker_build_root, "") + docker_entrypoint = try(each.value.docker_entrypoint, var.defaults.docker_entrypoint, null) + docker_file = try(each.value.docker_file, var.defaults.docker_file, "") + docker_image = try(each.value.docker_image, var.defaults.docker_image, "") + docker_pip_cache = try(each.value.docker_pip_cache, var.defaults.docker_pip_cache, null) + docker_with_ssh_agent = try(each.value.docker_with_ssh_agent, var.defaults.docker_with_ssh_agent, false) + environment_variables = try(each.value.environment_variables, var.defaults.environment_variables, {}) + ephemeral_storage_size = try(each.value.ephemeral_storage_size, var.defaults.ephemeral_storage_size, 512) + event_source_mapping = try(each.value.event_source_mapping, var.defaults.event_source_mapping, {}) + file_system_arn = try(each.value.file_system_arn, var.defaults.file_system_arn, null) + file_system_local_mount_path = try(each.value.file_system_local_mount_path, var.defaults.file_system_local_mount_path, null) + function_name = try(each.value.function_name, var.defaults.function_name, "") + function_tags = try(each.value.function_tags, var.defaults.function_tags, {}) + handler = try(each.value.handler, var.defaults.handler, "") + hash_extra = try(each.value.hash_extra, var.defaults.hash_extra, "") + ignore_source_code_hash = try(each.value.ignore_source_code_hash, var.defaults.ignore_source_code_hash, false) + image_config_command = try(each.value.image_config_command, var.defaults.image_config_command, []) + image_config_entry_point = try(each.value.image_config_entry_point, var.defaults.image_config_entry_point, []) + image_config_working_directory = try(each.value.image_config_working_directory, var.defaults.image_config_working_directory, null) + image_uri = try(each.value.image_uri, var.defaults.image_uri, null) + include_default_tag = try(each.value.include_default_tag, var.defaults.include_default_tag, true) + invoke_mode = try(each.value.invoke_mode, var.defaults.invoke_mode, null) + ipv6_allowed_for_dual_stack = try(each.value.ipv6_allowed_for_dual_stack, var.defaults.ipv6_allowed_for_dual_stack, null) + kms_key_arn = try(each.value.kms_key_arn, var.defaults.kms_key_arn, null) + lambda_at_edge = try(each.value.lambda_at_edge, var.defaults.lambda_at_edge, false) + lambda_at_edge_logs_all_regions = try(each.value.lambda_at_edge_logs_all_regions, var.defaults.lambda_at_edge_logs_all_regions, true) + lambda_role = try(each.value.lambda_role, var.defaults.lambda_role, "") + layer_name = try(each.value.layer_name, var.defaults.layer_name, "") + layer_skip_destroy = try(each.value.layer_skip_destroy, var.defaults.layer_skip_destroy, false) + layers = try(each.value.layers, var.defaults.layers, null) + license_info = try(each.value.license_info, var.defaults.license_info, "") + local_existing_package = try(each.value.local_existing_package, var.defaults.local_existing_package, null) + logging_application_log_level = try(each.value.logging_application_log_level, var.defaults.logging_application_log_level, "INFO") + logging_log_format = try(each.value.logging_log_format, var.defaults.logging_log_format, "Text") + logging_log_group = try(each.value.logging_log_group, var.defaults.logging_log_group, null) + logging_system_log_level = try(each.value.logging_system_log_level, var.defaults.logging_system_log_level, "INFO") + maximum_event_age_in_seconds = try(each.value.maximum_event_age_in_seconds, var.defaults.maximum_event_age_in_seconds, null) + maximum_retry_attempts = try(each.value.maximum_retry_attempts, var.defaults.maximum_retry_attempts, null) + memory_size = try(each.value.memory_size, var.defaults.memory_size, 128) + number_of_policies = try(each.value.number_of_policies, var.defaults.number_of_policies, 0) + number_of_policy_jsons = try(each.value.number_of_policy_jsons, var.defaults.number_of_policy_jsons, 0) + package_type = try(each.value.package_type, var.defaults.package_type, "Zip") + policies = try(each.value.policies, var.defaults.policies, []) + policy = try(each.value.policy, var.defaults.policy, null) + policy_json = try(each.value.policy_json, var.defaults.policy_json, null) + policy_jsons = try(each.value.policy_jsons, var.defaults.policy_jsons, []) + policy_name = try(each.value.policy_name, var.defaults.policy_name, null) + policy_statements = try(each.value.policy_statements, var.defaults.policy_statements, {}) + provisioned_concurrent_executions = try(each.value.provisioned_concurrent_executions, var.defaults.provisioned_concurrent_executions, -1) + publish = try(each.value.publish, var.defaults.publish, false) + putin_khuylo = try(each.value.putin_khuylo, var.defaults.putin_khuylo, true) + quiet_archive_local_exec = try(each.value.quiet_archive_local_exec, var.defaults.quiet_archive_local_exec, true) + recreate_missing_package = try(each.value.recreate_missing_package, var.defaults.recreate_missing_package, true) + recursive_loop = try(each.value.recursive_loop, var.defaults.recursive_loop, null) + region = try(each.value.region, var.defaults.region, null) + replace_security_groups_on_destroy = try(each.value.replace_security_groups_on_destroy, var.defaults.replace_security_groups_on_destroy, null) + replacement_security_group_ids = try(each.value.replacement_security_group_ids, var.defaults.replacement_security_group_ids, null) + reserved_concurrent_executions = try(each.value.reserved_concurrent_executions, var.defaults.reserved_concurrent_executions, -1) + role_description = try(each.value.role_description, var.defaults.role_description, null) + role_force_detach_policies = try(each.value.role_force_detach_policies, var.defaults.role_force_detach_policies, true) + role_maximum_session_duration = try(each.value.role_maximum_session_duration, var.defaults.role_maximum_session_duration, 3600) + role_name = try(each.value.role_name, var.defaults.role_name, null) + role_path = try(each.value.role_path, var.defaults.role_path, null) + role_permissions_boundary = try(each.value.role_permissions_boundary, var.defaults.role_permissions_boundary, null) + role_tags = try(each.value.role_tags, var.defaults.role_tags, {}) + runtime = try(each.value.runtime, var.defaults.runtime, "") + s3_acl = try(each.value.s3_acl, var.defaults.s3_acl, "private") + s3_bucket = try(each.value.s3_bucket, var.defaults.s3_bucket, null) + s3_existing_package = try(each.value.s3_existing_package, var.defaults.s3_existing_package, null) + s3_kms_key_id = try(each.value.s3_kms_key_id, var.defaults.s3_kms_key_id, null) + s3_object_override_default_tags = try(each.value.s3_object_override_default_tags, var.defaults.s3_object_override_default_tags, false) + s3_object_storage_class = try(each.value.s3_object_storage_class, var.defaults.s3_object_storage_class, "ONEZONE_IA") + s3_object_tags = try(each.value.s3_object_tags, var.defaults.s3_object_tags, {}) + s3_object_tags_only = try(each.value.s3_object_tags_only, var.defaults.s3_object_tags_only, false) + s3_prefix = try(each.value.s3_prefix, var.defaults.s3_prefix, null) + s3_server_side_encryption = try(each.value.s3_server_side_encryption, var.defaults.s3_server_side_encryption, null) + skip_destroy = try(each.value.skip_destroy, var.defaults.skip_destroy, null) + snap_start = try(each.value.snap_start, var.defaults.snap_start, false) + source_path = try(each.value.source_path, var.defaults.source_path, null) + store_on_s3 = try(each.value.store_on_s3, var.defaults.store_on_s3, false) + tags = try(each.value.tags, var.defaults.tags, {}) + timeout = try(each.value.timeout, var.defaults.timeout, 3) + timeouts = try(each.value.timeouts, var.defaults.timeouts, {}) + tracing_mode = try(each.value.tracing_mode, var.defaults.tracing_mode, null) + trigger_on_package_timestamp = try(each.value.trigger_on_package_timestamp, var.defaults.trigger_on_package_timestamp, true) + trusted_entities = try(each.value.trusted_entities, var.defaults.trusted_entities, []) + use_existing_cloudwatch_log_group = try(each.value.use_existing_cloudwatch_log_group, var.defaults.use_existing_cloudwatch_log_group, false) + vpc_security_group_ids = try(each.value.vpc_security_group_ids, var.defaults.vpc_security_group_ids, null) + vpc_subnet_ids = try(each.value.vpc_subnet_ids, var.defaults.vpc_subnet_ids, null) +} diff --git a/wrappers/outputs.tf b/wrappers/outputs.tf new file mode 100644 index 00000000..ec6da5f4 --- /dev/null +++ b/wrappers/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/wrappers/variables.tf b/wrappers/variables.tf new file mode 100644 index 00000000..a6ea0962 --- /dev/null +++ b/wrappers/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/wrappers/versions.tf b/wrappers/versions.tf new file mode 100644 index 00000000..8dea461c --- /dev/null +++ b/wrappers/versions.tf @@ -0,0 +1,22 @@ +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.0" + } + external = { + source = "hashicorp/external" + version = ">= 1.0" + } + local = { + source = "hashicorp/local" + version = ">= 1.0" + } + null = { + source = "hashicorp/null" + version = ">= 2.0" + } + } +}