diff --git a/.chglog/CHANGELOG.tpl.md b/.chglog/CHANGELOG.tpl.md deleted file mode 100644 index 687d7023..00000000 --- a/.chglog/CHANGELOG.tpl.md +++ /dev/null @@ -1,111 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. - -{{ if .Versions -}} - -## [Unreleased] -{{ if .Unreleased.CommitGroups -}} -{{ range .Unreleased.CommitGroups -}} -### {{ .Title }} -{{ range .Commits -}} -{{/* SKIPPING RULES - START */ -}} -{{- if not (hasPrefix .Subject "Updated CHANGELOG") -}} -{{- if not (contains .Subject "[ci skip]") -}} -{{- if not (contains .Subject "[skip ci]") -}} -{{- if not (hasPrefix .Subject "Merge pull request ") -}} -{{- if not (hasPrefix .Subject "Added CHANGELOG") -}} -{{- /* SKIPPING RULES - END */ -}} -- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} -{{/* SKIPPING RULES - START */ -}} -{{ end -}} -{{ end -}} -{{ end -}} -{{ end -}} -{{ end -}} -{{/* SKIPPING RULES - END */ -}} -{{ end }} -{{ end -}} -{{ else }} -{{ range .Unreleased.Commits -}} -{{/* SKIPPING RULES - START */ -}} -{{- if not (hasPrefix .Subject "Updated CHANGELOG") -}} -{{- if not (contains .Subject "[ci skip]") -}} -{{- if not (contains .Subject "[skip ci]") -}} -{{- if not (hasPrefix .Subject "Merge pull request ") -}} -{{- if not (hasPrefix .Subject "Added CHANGELOG") -}} -{{- /* SKIPPING RULES - END */ -}} -- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} -{{/* SKIPPING RULES - START */ -}} -{{ end -}} -{{ end -}} -{{ end -}} -{{ end -}} -{{ end -}} -{{/* SKIPPING RULES - END */ -}} -{{ end }} -{{ end -}} -{{ end -}} - -{{ range .Versions }} - -## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }} -{{ if .CommitGroups -}} -{{ range .CommitGroups -}} -### {{ .Title }} -{{ range .Commits -}} -{{/* SKIPPING RULES - START */ -}} -{{- if not (hasPrefix .Subject "Updated CHANGELOG") -}} -{{- if not (contains .Subject "[ci skip]") -}} -{{- if not (contains .Subject "[skip ci]") -}} -{{- if not (hasPrefix .Subject "Merge pull request ") -}} -{{- if not (hasPrefix .Subject "Added CHANGELOG") -}} -{{- /* SKIPPING RULES - END */ -}} -- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} -{{/* SKIPPING RULES - START */ -}} -{{ end -}} -{{ end -}} -{{ end -}} -{{ end -}} -{{ end -}} -{{/* SKIPPING RULES - END */ -}} -{{ end }} -{{ end -}} -{{ else }} -{{ range .Commits -}} -{{/* SKIPPING RULES - START */ -}} -{{- if not (hasPrefix .Subject "Updated CHANGELOG") -}} -{{- if not (contains .Subject "[ci skip]") -}} -{{- if not (contains .Subject "[skip ci]") -}} -{{- if not (hasPrefix .Subject "Merge pull request ") -}} -{{- if not (hasPrefix .Subject "Added CHANGELOG") -}} -{{- /* SKIPPING RULES - END */ -}} -- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} -{{/* SKIPPING RULES - START */ -}} -{{ end -}} -{{ end -}} -{{ end -}} -{{ end -}} -{{ end -}} -{{/* SKIPPING RULES - END */ -}} -{{ end }} -{{ end -}} - -{{- if .NoteGroups -}} -{{ range .NoteGroups -}} -### {{ .Title }} -{{ range .Notes }} -{{ .Body }} -{{ end }} -{{ end -}} -{{ end -}} -{{ end -}} - -{{- if .Versions }} -[Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD -{{ range .Versions -}} -{{ if .Tag.Previous -}} -[{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }} -{{ end -}} -{{ end -}} -{{ end -}} diff --git a/.chglog/config.yml b/.chglog/config.yml deleted file mode 100644 index 2b837bd5..00000000 --- a/.chglog/config.yml +++ /dev/null @@ -1,10 +0,0 @@ -style: github -template: CHANGELOG.tpl.md -info: - title: CHANGELOG - repository_url: https://github.com/terraform-aws-modules/terraform-aws-lambda -options: - header: - pattern: "^(.*)$" - pattern_maps: - - Subject diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml new file mode 100644 index 00000000..bd5f2df7 --- /dev/null +++ b/.github/workflows/lock.yml @@ -0,0 +1,21 @@ +name: 'Lock Threads' + +on: + schedule: + - cron: '50 1 * * *' + +jobs: + lock: + runs-on: ubuntu-latest + steps: + - uses: dessant/lock-threads@v5 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + issue-comment: > + I'm going to lock this issue because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active issues. + If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further. + issue-inactive-days: '30' + pr-comment: > + I'm going to lock this pull request because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active issues. + If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further. + pr-inactive-days: '30' diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml new file mode 100644 index 00000000..6419f3aa --- /dev/null +++ b/.github/workflows/pr-title.yml @@ -0,0 +1,52 @@ +name: 'Validate PR title' + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +jobs: + main: + name: Validate PR title + runs-on: ubuntu-latest + steps: + # Please look up the latest version from + # https://github.com/amannn/action-semantic-pull-request/releases + - uses: amannn/action-semantic-pull-request@v6.1.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # Configure which types are allowed. + # Default: https://github.com/commitizen/conventional-commit-types + types: | + fix + feat + docs + ci + chore + # Configure that a scope must always be provided. + requireScope: false + # Configure additional validation for the subject based on a regex. + # This example ensures the subject starts with an uppercase character. + subjectPattern: ^[A-Z].+$ + # If `subjectPattern` is configured, you can use this property to override + # the default error message that is shown when the pattern doesn't match. + # The variables `subject` and `title` can be used within the message. + subjectPatternError: | + The subject "{subject}" found in the pull request title "{title}" + didn't match the configured pattern. Please ensure that the subject + starts with an uppercase character. + # For work-in-progress PRs you can typically use draft pull requests + # from Github. However, private repositories on the free plan don't have + # this option and therefore this action allows you to opt-in to using the + # special "[WIP]" prefix to indicate this state. This will avoid the + # validation of the PR title and the pull request checks remain pending. + # Note that a second check will be reported if this is enabled. + wip: true + # When using "Squash and merge" on a PR with only one commit, GitHub + # will suggest using that commit message instead of the PR title for the + # merge commit, and it's easy to commit this by mistake. Enable this option + # to also validate the commit message for one commit PRs. + validateSingleCommit: false diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 00000000..057b9c42 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,168 @@ +name: Pre-Commit + +on: + pull_request: + branches: + - main + - master + +env: + TERRAFORM_DOCS_VERSION: v0.20.0 + TFLINT_VERSION: v0.59.1 + +jobs: + collectInputs: + name: Collect workflow inputs + runs-on: ubuntu-latest + outputs: + directories: ${{ steps.dirs.outputs.directories }} + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Get root directories + id: dirs + uses: clowdhaus/terraform-composite-actions/directories@v1.14.0 + + preCommitMinVersions: + name: Min TF pre-commit + needs: collectInputs + runs-on: ubuntu-latest + strategy: + 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@v5 + + - name: Terraform min/max versions + id: minMax + 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.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.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: + name: Max TF pre-commit + 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@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@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.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 new file mode 100644 index 00000000..e739b790 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,45 @@ +name: Release + +on: + workflow_dispatch: + push: + branches: + - main + - master + paths: + - '**/*.tpl' + - '**/*.py' + - '**/*.tf' + - '.github/workflows/release.yml' + +jobs: + release: + name: Release + runs-on: ubuntu-latest + # Skip running release workflow on forks + if: github.repository_owner == 'terraform-aws-modules' + steps: + - name: Checkout + 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@v5 + with: + 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 new file mode 100644 index 00000000..3e826dcf --- /dev/null +++ b/.github/workflows/stale-actions.yaml @@ -0,0 +1,32 @@ +name: 'Mark or close stale issues and PRs' +on: + schedule: + - cron: '0 0 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v10 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + # Staling issues and PR's + days-before-stale: 30 + stale-issue-label: stale + stale-pr-label: stale + stale-issue-message: | + This issue has been automatically marked as stale because it has been open 30 days + with no activity. Remove stale label or comment or this issue will be closed in 10 days + stale-pr-message: | + This PR has been automatically marked as stale because it has been open 30 days + with no activity. Remove stale label or comment or this PR will be closed in 10 days + # Not stale if have this labels or part of milestone + exempt-issue-labels: bug,wip,on-hold + exempt-pr-labels: bug,wip,on-hold + exempt-all-milestones: true + # Close issue operations + # Label will be automatically removed if the issues are no longer closed nor locked. + days-before-close: 10 + delete-branch: true + close-issue-message: This issue was automatically closed because of stale in 10 days + close-pr-message: This PR was automatically closed because of stale in 10 days diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..ab145451 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,60 @@ +name: Tests + +env: + PYTEST_VERSION: 7.4.4 + RUFF_VERSION: 0.1.13 + RUFF_PY_VERSION: 3.12 + +on: + push: + branches: [master] + tags: ["*"] + pull_request: + branches: [master] + +jobs: + tests: + name: Test with Python ${{ matrix.python_version }} + runs-on: ubuntu-latest + strategy: + matrix: + python_version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + fail-fast: false + steps: + - uses: actions/checkout@v4.1.1 + + - name: Set up Python ${{ matrix.python_version }} + uses: actions/setup-python@v5.0.0 + with: + python-version: ${{ matrix.python_version }} + + - name: Install poetry + shell: bash + run: | + pip install pytest==${{ env.PYTEST_VERSION }} + + - 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 0308fbf4..fd39819e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,39 @@ -.terraform -*.tfstate.backup +# Local .terraform directories +**/.terraform/* + +# Terraform lockfile +.terraform.lock.hcl + +# .tfstate files *.tfstate +*.tfstate.* + +# Crash log files +crash.log + +# Exclude all .tfvars files, which are likely to contain sentitive data, such as +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject +# to change depending on the environment. *.tfvars -*.tfplan -builds/ +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Ignore CLI configuration files +.terraformrc +terraform.rc +# Lambda build artifacts +builds/ __pycache__/ +*.zip +.tox + +# Local editors/macos files +.DS_Store +.idea diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2308d6fe..991a8bbf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,33 @@ repos: - - repo: git://github.com/antonbabenko/pre-commit-terraform - rev: v1.31.0 + - repo: https://github.com/antonbabenko/pre-commit-terraform + rev: v1.103.0 hooks: - id: terraform_fmt + - id: terraform_wrapper_module_for_each - id: terraform_docs - - repo: git://github.com/pre-commit/pre-commit-hooks - rev: v3.1.0 + args: + - "--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" + - id: terraform_validate + - repo: https://github.com/pre-commit/pre-commit-hooks + 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/.releaserc.json b/.releaserc.json new file mode 100644 index 00000000..66b3eefd --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,45 @@ +{ + "branches": [ + "main", + "master" + ], + "ci": false, + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits" + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits" + } + ], + [ + "@semantic-release/github", + { + "successComment": "This ${issue.pull_request ? 'PR is included' : 'issue has been resolved'} in version ${nextRelease.version} :tada:", + "labels": false, + "releasedLabels": false + } + ], + [ + "@semantic-release/changelog", + { + "changelogFile": "CHANGELOG.md", + "changelogTitle": "# Changelog\n\nAll notable changes to this project will be documented in this file." + } + ], + [ + "@semantic-release/git", + { + "assets": [ + "CHANGELOG.md" + ], + "message": "chore(release): version ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" + } + ] + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index a70bccbd..7b60d567 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,1234 @@ -# Change Log +# Changelog All notable changes to this project will be documented in this file. - -## [Unreleased] +## [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) + + +### Bug Fixes + +* Fixed opposite refresh_alias behavior in modules/alias ([#372](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/372)) ([f7b2a3a](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/f7b2a3a5e4f9764dac26034b5909e755e1c05880)) + +## [4.7.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.6.1...v4.7.0) (2022-11-11) + + +### Features + +* Added static/defined/computed ARN for the Lambda Function outputs ([#376](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/376)) ([eed4f42](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/eed4f42cb53ec0186fcf26016e29442f635a5159)) + +### [4.6.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.6.0...v4.6.1) (2022-11-07) + + +### Bug Fixes + +* Update CI configuration files to use latest version ([#374](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/374)) ([4a75d95](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/4a75d95bc92e21227e901192143b29c11695124e)) + +## [4.6.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.5.0...v4.6.0) (2022-11-03) + + +### Features + +* Add SAM Metadata resources to enable the integration with SAM CLI tool ([#325](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/325)) ([bfcd34c](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/bfcd34cfb21e4975990c807b85747f52b8601567)) + +## [4.5.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.4.1...v4.5.0) (2022-10-31) + + +### Features + +* Support additional arguments for docker and entrypoint override ([#366](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/366)) ([dc4d000](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/dc4d00068dc1bb1cbcac8943541b6406abcecbf2)) + +### [4.4.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.4.0...v4.4.1) (2022-10-31) + + +### Bug Fixes + +* Fixed policy name when create_role is false ([#371](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/371)) ([da56fc5](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/da56fc56b9b98535f24db013a1d6e34c3fa3a066)) + +## [4.4.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.3.0...v4.4.0) (2022-10-31) + + +### Features + +* Add a way to define IAM policy name prefix ([#354](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/354)) ([7df6bbf](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/7df6bbffa3d7d87570d6858db770bf8059f20591)) + +## [4.3.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.2.2...v4.3.0) (2022-10-31) + + +### Features + +* Support installing poetry dependencies with pip ([#311](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/311)) ([398ae5a](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/398ae5a9ace660bb3e7021824c0bffe1ee19f44c)) + +### [4.2.2](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.2.1...v4.2.2) (2022-10-31) + + +### Bug Fixes + +* Checks for `npm` instead of `runtime` when building nodejs packages ([#364](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/364)) ([682052c](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/682052c516b70425cd89ebd4086f2ffcf5c96bae)) + +### [4.2.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.2.0...v4.2.1) (2022-10-27) + + +### Bug Fixes + +* Qualifiers in event invoke config ([#368](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/368)) ([93e1dc3](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/93e1dc3207105bd0620d3c3a952a0cce4d247972)) + +## [4.2.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.1.4...v4.2.0) (2022-10-22) + + +### Features + +* Added support for Code Signing Configuration ([#351](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/351)) ([dd40178](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/dd40178f7534fa4fd341a8e9dbf645bbe4c279d0)) + +### [4.1.4](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.1.3...v4.1.4) (2022-10-21) + + +### Bug Fixes + +* Skips the runtime test when building in docker ([#362](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/362)) ([2055256](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/20552562aa80843fe5cb5e569b5e58daaf569741)) + +### [4.1.3](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.1.2...v4.1.3) (2022-10-20) + + +### Bug Fixes + +* Performs plan-phase runtime check only if building package ([#359](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/359)) ([dfc8934](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/dfc8934e907e5eb7f1820b838ec6e98f4011128a)) + +### [4.1.2](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.1.1...v4.1.2) (2022-10-20) + + +### Bug Fixes + +* Generates error in plan phase if runtime is not available ([#358](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/358)) ([f9bf21d](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/f9bf21df9bef0730ed3efc174fc12a79e3a5268c)) + +### [4.1.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.1.0...v4.1.1) (2022-10-14) + + +### Bug Fixes + +* Forces the local_filename output to wait for the package to be built ([#356](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/356)) ([745dc53](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/745dc5359f45d15fe4201114c0f0ec0069c99fa1)) + +## [4.1.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.0.2...v4.1.0) (2022-10-14) + + +### Features + +* Add example for S3 bucket access through VPC Endpoint ([#349](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/349)) ([2ceb32f](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/2ceb32fdbef85758305a59b2320bdd40e246290f)) + +### [4.0.2](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.0.1...v4.0.2) (2022-09-17) + + +### Bug Fixes + +* Override docker entrypoint when it exists ([#316](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/316)) ([3bb7623](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/3bb7623e74f7cc6f45519cf162ea252b7d69c7bc)) + +### [4.0.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v4.0.0...v4.0.1) (2022-09-01) + + +### Bug Fixes + +* Lambda should depend on policy attachments ([#327](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/327)) ([b4eef74](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/b4eef74b79e73928a11be36e4400cac8b5ad7227)) + +## [4.0.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v3.3.1...v4.0.0) (2022-08-18) + + +### ⚠ BREAKING CHANGES + +* Updated AWS provider to v4, added ECR repo force_delete argument in docker-build module (#337) + +### Features + +* Updated AWS provider to v4, added ECR repo force_delete argument in docker-build module ([#337](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/337)) ([953ccee](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/953ccee287135da9850818b2d7411bdb72f23ae5)) + +### [3.3.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v3.3.0...v3.3.1) (2022-06-17) + + +### Bug Fixes + +* Fixed enabled attribute in Lambda Event Source Mapping by default ([#321](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/321)) ([779b368](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/779b368781f0bf14964c2f6e306c1c9ef4690bbb)) + +## [3.3.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v3.2.1...v3.3.0) (2022-06-16) + + +### Features + +* Added support for event source mapping in alias submodule ([#320](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/320)) ([af22d00](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/af22d006c0b771809a0bf7a7a2bda49dafabb6a5)) + +### [3.2.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v3.2.0...v3.2.1) (2022-05-23) + + +### Bug Fixes + +* Removed docker provider block from docker-build submodule ([#314](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/314)) ([151a09a](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/151a09a9b64a10cc8898becef245b7cdf96ee943)) + +## [3.2.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v3.1.1...v3.2.0) (2022-04-27) + + +### Features + +* Add support for Lambda Function URL resource ([#308](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/308)) ([c239f9d](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/c239f9d722c8c68cb5d43f96f108540c1b99f95b)) + +### [3.1.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v3.1.0...v3.1.1) (2022-04-13) + + +### Bug Fixes + +* Fixed ephemeral_storage in AWS govcloud region ([#305](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/305)) ([13c4449](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/13c444905e18fa9eceffd07ee884251eb28a8fd5)) + +## [3.1.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v3.0.1...v3.1.0) (2022-03-28) + + +### Features + +* Added support for self managed kafka in event source mapping ([#278](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/278)) ([ee41186](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/ee41186b6e8bd04edfb1805b49820a7237f941a8)) + +### [3.0.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v3.0.0...v3.0.1) (2022-03-28) + + +### Bug Fixes + +* Removed hard-coded AWS account id in examples ([#275](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/275)) ([5ab1383](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/5ab1383042c1e73ea1a1f709c9a279815ae0cf1a)) + +## [3.0.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.36.0...v3.0.0) (2022-03-28) + + +### ⚠ BREAKING CHANGES + +* Updated AWS provider to version 4.8 (#296) + +### Features + +* Added support for ephemeral storage (requires AWS provider version 4.8.0) ([#291](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/291)) ([f191bae](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/f191baea053e126fc6b83a2ea4d6988c4f47ebde)) +* Updated AWS provider to version 4.8 ([#296](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/296)) ([d4b55a8](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/d4b55a8bb142a7124f4cd910d68a631d9658260e)), closes [#291](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/291) + +## [2.36.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.35.1...v2.36.0) (2022-03-26) + + +### Features + +* Add support to build automatically npm dependencies ([#293](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/293)) ([ecb3807](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/ecb38076b0408982183ebb8070aff7c7e01c4b82)) + +### [2.35.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.35.0...v2.35.1) (2022-03-18) + + +### Bug Fixes + +* Added support for keep_remotely in docker-build submodule ([#284](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/284)) ([db34260](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/db34260a1685333fa1f491b77f4564033c29729b)) + +## [2.35.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.34.1...v2.35.0) (2022-03-12) + + +### Features + +* Made it clear that we stand with Ukraine ([2d32d84](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/2d32d84a3483bb2eb66f37b33cab13fba0d96adc)) + +### [2.34.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.34.0...v2.34.1) (2022-02-23) + + +### Bug Fixes + +* Fixed event source mapping filter criteria ([#272](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/272)) ([a5c03fe](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/a5c03fef2c5c332dc31b84030cbb63302ef8a23d)) + +## [2.34.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.33.2...v2.34.0) (2022-01-31) + + +### Features + +* Add event filter criteria capabilities ([#242](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/242)) ([159f57a](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/159f57aede1173a41ab9ef362909f8fb3e67d8d4)) + +### [2.33.2](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.33.1...v2.33.2) (2022-01-21) + + +### Bug Fixes + +* Fixed incorrect tomap() ([#257](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/257)) ([2478baa](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/2478baa167816af2dee477d7e88703efff8b713b)) + +### [2.33.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.33.0...v2.33.1) (2022-01-21) + + +### Bug Fixes + +* Updated code style to use `try()` ([#256](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/256)) ([e9aed29](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/e9aed29a45762ea2bc1675fa9e1ed7458703f86b)) + +## [2.33.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.32.0...v2.33.0) (2022-01-21) + + +### Features + +* Accept new arguments `function_response_types` in `aws_lambda_event_source_mapping` ([#255](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/255)) ([1fda108](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/1fda108d41a8b167007ecc43b78654a4a2fa9aa5)) + +## [2.32.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.31.0...v2.32.0) (2022-01-17) + + +### Features + +* Added flag to exclude general tags from S3 Object tagging ([#250](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/250)) ([a8a185c](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/a8a185cb85b794cae8c169522c12039077507f52)) + +## [2.31.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.30.0...v2.31.0) (2022-01-10) + + +### Features + +* Allow the use of third party images to build dependencies ([#245](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/245)) ([0a9793e](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/0a9793ec9f04d96a0ffa6abb3d920659fae654b1)) + +# [2.30.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.29.0...v2.30.0) (2022-01-06) + + +### Features + +* Added support for skip_destroy in Lambda Layer Version ([#244](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/244)) ([b9671e1](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/b9671e13d57823319e5b25900457dafcc81a4dbe)) + +# [2.29.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.28.0...v2.29.0) (2022-01-05) + + +### Features + +* Add ECR Lifecycle Policy Option to docker-build module ([#243](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/243)) ([577b077](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/577b07768be37c0c24ea16294e2a9760833762bf)) + +# [2.28.0](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.27.1...v2.28.0) (2021-12-10) + + +### Features + +* Add `pip_tmp_dir` to allow setting the location of the pip temporary directory ([#230](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/230)) ([f5f86b5](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/f5f86b593f6d72408464ae5124e34dc01f73387c)) + +## [2.27.1](https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.27.0...v2.27.1) (2021-11-27) + + +### Bug Fixes + +* update CI/CD process to enable auto-release workflow ([#234](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/234)) ([e882a07](https://github.com/terraform-aws-modules/terraform-aws-lambda/commit/e882a072ff587d7271e0fdd647f180f9b61ceefc)) + + +## [v2.27.0] - 2021-11-22 + +- feat: Added support for random sleep delay in deploy submodule ([#233](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/233)) + + + +## [v2.26.0] - 2021-11-12 + +- fix: Fixed max timeout for Lambda[@Edge](https://github.com/Edge) ([#232](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/232)) + + + +## [v2.25.0] - 2021-11-09 + +- feat: Added required IAM permissions for CodeDeploy hooks ([#228](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/228)) + + + +## [v2.24.0] - 2021-11-05 + +- feat: Added support for Cross-Account ECR for docker-build module ([#227](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/227)) +- fix: Raise failure when CodeDeploy deployment fails ([#225](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/225)) + + + +## [v2.23.0] - 2021-10-22 + +- feat: Allow passing build_args for building with docker-build module ([#217](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/217)) + + + +## [v2.22.0] - 2021-10-12 + +- feat: Add policy_path variable for IAM policies ([#202](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/202)) +- chore: Added example for pip install in layers ([#214](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/214)) + + + +## [v2.21.0] - 2021-10-07 + +- fix: Use `AWSXRayDaemonWriteAccess` instead of deprecated `AWSXrayWriteOnlyAccess` ([#211](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/211)) + + + +## [v2.20.0] - 2021-10-02 + +- feat: Add support for AWS Graviton2 powered functions ([#206](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/206)) + + + +## [v2.19.0] - 2021-10-01 + +- feat: add support for additional assume_role_policy statements ([#203](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/203)) + + + +## [v2.18.0] - 2021-09-25 + +- feat: Added support for partition in IAM policies to work in GovCloud ([#201](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/201)) +- docs: Added a mention of good examples - 1Mill/serverless-tf-examples ([#197](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/197)) + + + +## [v2.17.0] - 2021-09-11 + +- fix: Replace aws_iam_policy_attachment to aws_iam_role_policy_attachment ([#195](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/195)) + + + +## [v2.16.0] - 2021-08-30 + +- feat: Add `recreate_missing_package` parameter ([#181](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/181)) + + + +## [v2.15.0] - 2021-08-30 + +- fix: Strip leading `./` in S3 key ([#191](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/191)) +- docs: Added a note for TFC/TFE customers ([#193](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/193)) + + + +## [v2.14.0] - 2021-08-30 + +- fix: Take patterns into account when computing hash ([#169](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/169)) +- feat: Add unique_id output of the lambda role ([#173](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/173)) + + + +## [v2.13.0] - 2021-08-30 + +- fix: Sort directories and files to ensure they are always processed in the same order ([#177](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/177)) +- feat: Added docker pip cache support for macOS ([#192](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/192)) + + + +## [v2.12.0] - 2021-08-30 + +- feat: Add Amazon MQ event source type support ([#190](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/190)) + + + +## [v2.11.0] - 2021-08-20 + +- fix: No need to set `aws_s3_bucket_object` `etag` as filename is already a hash of the content ([#180](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/180)) + + + +## [v2.10.0] - 2021-08-20 + +- feat: Add support for separate deployments of infra and code ([#175](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/175)) + + + +## [v2.9.0] - 2021-08-20 + +- feat: Add topics parameter support for lambda event source ([#166](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/166)) + + + +## [v2.8.0] - 2021-08-14 + +- feat: Expose ecr tag & scan variables in docker-build module ([#189](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/189)) + + + +## [v2.7.0] - 2021-07-08 + +- fix: Remove `random` provider because it is not used ([#172](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/172)) + + + +## [v2.6.0] - 2021-07-07 + +- fix: Fixed deprecated call to map() in deploy submodule ([#171](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/171)) + + + +## [v2.5.0] - 2021-06-28 + +- feat: Add submodule to handle creation of docker images ([#162](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/162)) + + + +## [v2.4.0] - 2021-06-07 + +- docs: Updated README with S3 bucket id handling ([#157](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/157)) + + + +## [v2.3.0] - 2021-05-27 + +- feat: add tags to `aws_iam_policy` resources ([#153](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/153)) + + + +## [v2.2.0] - 2021-05-25 + +- chore: Remove checked checkboxes to make module docs render properly ([#156](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/156)) + + + +## [v2.1.0] - 2021-05-20 + +- feat: Added destination_config in aws_lambda_event_source_mapping ([#152](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/152)) +- chore: update CI/CD to use stable `terraform-docs` release artifact and discoverable Apache2.0 license ([#149](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/149)) +- chore: Updated versions&comments in examples +- chore: Updated versions in README + + + +## [v2.0.0] - 2021-04-26 + +- feat: Shorten outputs (removing this_) ([#148](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/148)) + + + +## [v1.48.0] - 2021-04-26 + +- fix: make lambda function depend on the Cloudwatch log group ([#133](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/133)) +- fix: add documentation for the :zip command ([#115](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/115)) +- feat: Added example to show creation of Lambdas with for_each ([#146](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/146)) + + + +## [v1.47.0] - 2021-04-19 + +- feat: Extended `trusted_entities` variable to support multiple types ([#143](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/143)) + + + +## [v1.46.0] - 2021-04-13 + +- fix: package.py not found with -chdir option ([#136](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/136)) + + + +## [v1.45.0] - 2021-04-06 + +- fix: permission for lambda-to-lambda async calls ([#141](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/141)) +- chore: update documentation and pin `terraform_docs` version to avoid future changes ([#134](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/134)) + + + +## [v1.44.0] - 2021-03-09 + +- chore: Added examples to show CloudWatch Event Rule as triggers ([#126](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/126)) + + + +## [v1.43.0] - 2021-03-03 + +- fix: Defaults the role_name coalesce to * to workaround import error ([#121](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/121)) + + + +## [v1.42.0] - 2021-03-02 + +- feat: Add s3_acl and s3_server_site_encryption variables ([#120](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/120)) + + + +## [v1.41.0] - 2021-03-01 + +- feat: Added interpreter variable to control script runtime in deploy module ([#92](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/92)) + + + +## [v1.40.0] - 2021-02-28 + +- fix: revert module Terraform 0.13.x version upgrade ([#117](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/117)) +- chore: fix documentation due to terraform docs 0.11.2 update ([#116](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/116)) + + + +## [v1.39.0] - 2021-02-22 + +- chore: only run validate check on min terraform version ([#114](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/114)) +- chore: add ci-cd workflow for pre-commit checks ([#112](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/112)) +- docs: Fixed terraform-docs automatically + + + +## [v1.38.0] - 2021-02-18 + +- feat: Add output for lambda CloudWatch log group name ([#111](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/111)) +- chore: update documentation based on latest `terraform-docs` which includes module and resource sections ([#108](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/108)) + + + +## [v1.37.0] - 2021-02-14 + +- feat: Added Lambda event source mapping ([#103](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/103)) + + + +## [v1.36.0] - 2021-02-03 + +- feat: add eventbridge async permissions ([#101](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/101)) + + + +## [v1.35.0] - 2021-01-26 + +- fix: add permission to create log group ([#100](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/100)) +- docs: Fix memory size limit ([#99](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/99)) + + + +## [v1.34.0] - 2021-01-14 + +- fix: skip creating deployments if current and target versions match ([#85](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/85)) + + + +## [v1.33.0] - 2021-01-14 + +- docs: update description of hook vars, note naming expectations of default policy ([#95](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/95)) + + + +## [v1.32.0] - 2021-01-14 + +- fix: Fixed apigateway trigger to use source_arn ([#94](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/94)) +- docs: Improved package.py error message for missing source_paths ([#88](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/88)) +- docs: Explicitly state the IAM role property used for lambda_role ([#90](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/90)) + + + +## [v1.31.0] - 2020-12-07 + +- feat: Add support for creating lambdas that use Container Images ([#80](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/80)) + + + +## [v1.30.0] - 2020-11-23 + +- fix: Fixed CodeDeploy hooks ([#76](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/76)) + + + +## [v1.29.0] - 2020-11-19 + +- feat: Customizable prefixes for IAM policies (as for IAM role) ([#74](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/74)) + + + +## [v1.28.0] - 2020-11-17 + +- feat: Updated range of supported versions of Terraform and providers ([#71](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/71)) + + + +## [v1.27.0] - 2020-11-02 + +- ci: Updated pre-commit hooks, added terraform_validate ([#68](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/68)) + + + +## [v1.26.0] - 2020-10-27 + +- fix: Removed hash_extra_paths to have the same hash for multiple executors ([#66](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/66)) + + + +## [v1.25.0] - 2020-10-26 + +- fix: Fixed concurrent builds ([#65](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/65)) +- chore: Upgraded pre-commit-terraform to fix terraform-docs + + + +## [v1.24.0] - 2020-09-23 + +- feat: Added tflint as pre-commit hook ([#60](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/60)) + + + +## [v1.23.0] - 2020-09-14 + +- feat: Added support for policy_jsons (list of strings) ([#58](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/58)) + + + +## [v1.22.0] - 2020-08-26 + +- feat: Updated submodules to support Terraform 0.13 + + + +## [v1.21.0] - 2020-08-25 + +- fix: os xcode python interpreter ([#50](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/50)) +- docs: Updated description for provisioned_concurrent_executions (closes [#38](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/38)) +- chore: Set number_of_policies in example + + + +## [v1.20.0] - 2020-08-19 + +- fix: Fix policy attachments for managed policies ([#45](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/45)) +- feat: Add support for EFS File System Config ([#46](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/46)) +- feat: Bump version of AWS provider to support v3 +- feat: Upgraded Terraform version supported +- docs: Updated FAQ with info about "We currently do not support adding policies for " +- fix: Adds region wildcard to log group arn when lambda[@edge](https://github.com/edge) ([#35](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/35)) +- fix: Fixed issue with zip renaming on Windows platform ([#32](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/32)) +- feat: docker image building for installing pip requirements independently from OS ([#31](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/31)) +- fix: Fixed patterns applying ([#30](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/30)) +- fix: Fixed DUMP_ENV logging level ([#28](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/28)) +- fix: Fixed IAM policy attachment with multiple functions ([#26](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/26)) +- feat: Added support for variety of options for source_path, closes [#12](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/12) ([#25](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/25)) +- Updated examples and readme +- Added more samples to examples/simple/main.tf +- package.py - Log directories with ending / +- package.py - Log skipped items + made uniform some messages +- package.py - Added support for comments in patterns +- package.py - Renamed: logger -> log +- feat: Added ZipContentFilter class to apply patterns filtering +- package.py - Fixed and improved logging +- package.py - Added BuildPlanManager initial implementation +- package.py - Fixed building in docker +- package.py - Implemented ZipFileStream.write_file +- feat: In-place zip archiving +- package.py - Removed dir changing on zip archive generation +- package.py - Simplified emit_dir_files func +- package.py - Fixed timestamp appling +- package.py - Added hidden hash command to calculate Lambda's content hash +- package.py - Finished ZipFileStream.write_dirs implementation +- package.py - Moved borrowed ZipInfo.from_file to a ZipWriteStream class +- package.py - Added initial ZipFileStream skel +- package.py - Move out inner functions from *_command functions +- feat: Added pid to the prepare stage log records +- feat: Added AWS CodeDeploy group name to outputs +- fix: Create AWS CodeDeploy resources conditionally +- fix: Do not create AWS Cloudwatch log group for Lambda Layers +- feat: Add Cloudwatch Logs resources (or use existing) ([#24](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/24)) + + + +## [v1.6.1] - 2020-08-14 + +- fix: Added support for AWS provider v3 used by notify-slack module with Terraform 0.12 + + + +## [v1.19.0] - 2020-08-14 + +- feat: Add support for EFS File System Config ([#46](https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/46)) + + + +## [v1.18.0] - 2020-08-13 + +- feat: Bump version of AWS provider to support v3 + + + +## [v1.17.0] - 2020-07-20 + +- feat: Upgraded Terraform version supported @@ -148,7 +1372,68 @@ All notable changes to this project will be documented in this file. - first commit -[Unreleased]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.16.0...HEAD +[Unreleased]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.27.0...HEAD +[v2.27.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.26.0...v2.27.0 +[v2.26.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.25.0...v2.26.0 +[v2.25.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.24.0...v2.25.0 +[v2.24.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.23.0...v2.24.0 +[v2.23.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.22.0...v2.23.0 +[v2.22.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.21.0...v2.22.0 +[v2.21.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.20.0...v2.21.0 +[v2.20.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.19.0...v2.20.0 +[v2.19.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.18.0...v2.19.0 +[v2.18.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.17.0...v2.18.0 +[v2.17.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.16.0...v2.17.0 +[v2.16.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.15.0...v2.16.0 +[v2.15.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.14.0...v2.15.0 +[v2.14.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.13.0...v2.14.0 +[v2.13.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.12.0...v2.13.0 +[v2.12.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.11.0...v2.12.0 +[v2.11.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.10.0...v2.11.0 +[v2.10.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.9.0...v2.10.0 +[v2.9.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.8.0...v2.9.0 +[v2.8.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.7.0...v2.8.0 +[v2.7.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.6.0...v2.7.0 +[v2.6.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.5.0...v2.6.0 +[v2.5.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.4.0...v2.5.0 +[v2.4.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.3.0...v2.4.0 +[v2.3.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.2.0...v2.3.0 +[v2.2.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.1.0...v2.2.0 +[v2.1.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v2.0.0...v2.1.0 +[v2.0.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.48.0...v2.0.0 +[v1.48.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.47.0...v1.48.0 +[v1.47.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.46.0...v1.47.0 +[v1.46.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.45.0...v1.46.0 +[v1.45.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.44.0...v1.45.0 +[v1.44.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.43.0...v1.44.0 +[v1.43.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.42.0...v1.43.0 +[v1.42.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.41.0...v1.42.0 +[v1.41.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.40.0...v1.41.0 +[v1.40.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.39.0...v1.40.0 +[v1.39.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.38.0...v1.39.0 +[v1.38.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.37.0...v1.38.0 +[v1.37.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.36.0...v1.37.0 +[v1.36.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.35.0...v1.36.0 +[v1.35.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.34.0...v1.35.0 +[v1.34.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.33.0...v1.34.0 +[v1.33.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.32.0...v1.33.0 +[v1.32.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.31.0...v1.32.0 +[v1.31.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.30.0...v1.31.0 +[v1.30.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.29.0...v1.30.0 +[v1.29.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.28.0...v1.29.0 +[v1.28.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.27.0...v1.28.0 +[v1.27.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.26.0...v1.27.0 +[v1.26.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.25.0...v1.26.0 +[v1.25.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.24.0...v1.25.0 +[v1.24.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.23.0...v1.24.0 +[v1.23.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.22.0...v1.23.0 +[v1.22.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.21.0...v1.22.0 +[v1.21.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.20.0...v1.21.0 +[v1.20.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.6.1...v1.20.0 +[v1.6.1]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.19.0...v1.6.1 +[v1.19.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.18.0...v1.19.0 +[v1.18.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.17.0...v1.18.0 +[v1.17.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.16.0...v1.17.0 [v1.16.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.15.0...v1.16.0 [v1.15.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.14.0...v1.15.0 [v1.14.0]: https://github.com/terraform-aws-modules/terraform-aws-lambda/compare/v1.13.0...v1.14.0 diff --git a/LICENSE b/LICENSE index 29010ded..d9a10c0d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,13 +1,176 @@ -Copyright 2020 Anton Babenko (Betajob AS) + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - http://www.apache.org/licenses/LICENSE-2.0 + 1. Definitions. -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/Makefile b/Makefile deleted file mode 100644 index 558dac5a..00000000 --- a/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -.PHONY: changelog release - -changelog: - git-chglog -o CHANGELOG.md --next-tag `semtag final -s minor -o` - -release: - semtag final -s minor diff --git a/README.md b/README.md index 60432fce..3d045ce0 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,7 @@ Terraform module, which creates almost all supported AWS Lambda resources as well as taking care of building and packaging of required Lambda dependencies for functions and layers. -These types of resources supported: - -* [Lambda Function](https://www.terraform.io/docs/providers/aws/r/lambda_function.html) -* [Lambda Layer](https://www.terraform.io/docs/providers/aws/r/lambda_layer_version.html) -* [Lambda Alias](https://www.terraform.io/docs/providers/aws/r/lambda_alias.html) - using [alias module](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/modules/alias) -* [Lambda Provisioned Concurrency](https://www.terraform.io/docs/providers/aws/r/lambda_provisioned_concurrency_config.html) -* [Lambda Async Event Configuration](https://www.terraform.io/docs/providers/aws/r/lambda_function_event_invoke_config.html) -* [Lambda Permission](https://www.terraform.io/docs/providers/aws/r/lambda_permission.html) - -Not supported, yet: -* [Lambda Event Source Mapping](https://www.terraform.io/docs/providers/aws/r/lambda_event_source_mapping.html) - +[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) 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: @@ -22,21 +11,20 @@ This Terraform module is the part of [serverless.tf framework](https://github.co 3. Create, update, and publish AWS Lambda Function and Lambda Layer - [see usage](#usage). 4. Create static and dynamic aliases for AWS Lambda Function - [see usage](#usage), see [modules/alias](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/modules/alias). 5. Do complex deployments (eg, rolling, canary, rollbacks, triggers) - [read more](#deployment), see [modules/deploy](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/modules/deploy). - +6. Use AWS SAM CLI to test Lambda Function - [read more](#sam_cli_integration). ## Features -- [x] Build dependencies for your Lambda Function and Layer. -- [x] Support builds locally and in Docker (with or without SSH agent support for private builds). -- [x] Create deployment package or deploy existing (previously built package) from local, from S3, from URL. -- [x] Store deployment packages locally or in the S3 bucket. -- [x] Support almost all features of Lambda resources (function, layer, alias, etc.) -- [x] Lambda@Edge -- [x] Conditional creation for many types of resources. -- [x] Control execution of nearly any step in the process - build, package, store package, deploy, update. -- [x] Control nearly all aspects of Lambda resources (provisioned concurrency, VPC, dead-letter notification, tracing, async events, IAM role, IAM policies, and more). -- [x] Support integration with other `serverless.tf` modules like [HTTP API Gateway](https://github.com/terraform-aws-modules/terraform-aws-apigateway-v2) (see [examples there](https://github.com/terraform-aws-modules/terraform-aws-apigateway-v2/tree/master/examples/complete-http)). - +- Build dependencies for your Lambda Function and Layer. +- 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.) +- Lambda@Edge +- Conditional creation for many types of resources. +- Control execution of nearly any step in the process - build, package, store package, deploy, update. +- Control nearly all aspects of Lambda resources (provisioned concurrency, VPC, EFS, dead-letter notification, tracing, async events, event source mapping, IAM role, IAM policies, and more). +- Support integration with other `serverless.tf` modules like [HTTP API Gateway](https://github.com/terraform-aws-modules/terraform-aws-apigateway-v2) (see [examples there](https://github.com/terraform-aws-modules/terraform-aws-apigateway-v2/tree/master/examples/complete-http)). ## Usage @@ -49,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" @@ -68,16 +56,16 @@ 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" store_on_s3 = true - s3_bucket = "my-bucket-with-lambda-builds" + s3_bucket = "my-bucket-id-with-lambda-builds" layers = [ - module.lambda_layer_s3.this_lambda_layer_arn, + module.lambda_layer_s3.lambda_layer_arn, ] environment_variables = { @@ -96,14 +84,13 @@ 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" store_on_s3 = true - s3_bucket = "my-bucket-with-lambda-builds" + s3_bucket = "my-bucket-id-with-lambda-builds" } - ``` ### Lambda Functions with existing package (prebuilt) stored locally @@ -115,13 +102,39 @@ 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" } ``` +### Lambda Function or Lambda Layer with the deployable artifact maintained separately from the infrastructure + +If you want to manage function code and infrastructure resources (such as IAM permissions, policies, events, etc) in separate flows (e.g., different repositories, teams, CI/CD pipelines). + +Disable source code tracking to turn off deployments (and rollbacks) using the module by setting `ignore_source_code_hash = true` and deploy a _dummy function_. + +When the infrastructure and the dummy function is deployed, you can use external tool to update the source code of the function (eg, using [AWS CLI](https://docs.aws.amazon.com/cli/latest/reference/lambda/update-function-code.html)) and keep using this module via Terraform to manage the infrastructure. + +Be aware that changes in `local_existing_package` value may trigger deployment via Terraform. + +```hcl +module "lambda_function_externally_managed_package" { + source = "terraform-aws-modules/lambda/aws" + + function_name = "my-lambda-externally-managed-package" + description = "My lambda function code is deployed separately" + handler = "index.lambda_handler" + runtime = "python3.12" + + create_package = false + local_existing_package = "./lambda_functions/code.zip" + + ignore_source_code_hash = true +} +``` + ### Lambda Function with existing package (prebuilt) stored in S3 bucket Note that this module does not copy prebuilt packages into S3 bucket. This module can only store packages it builds locally and in S3 bucket. @@ -131,8 +144,13 @@ locals { my_function_source = "../path/to/package.zip" } -resource "aws_s3_bucket_object" "my_function" { - bucket = "my-bucket-with-lambda-builds" +resource "aws_s3_bucket" "builds" { + bucket = "my-builds" + acl = "private" +} + +resource "aws_s3_object" "my_function" { + bucket = aws_s3_bucket.builds.id key = "${filemd5(local.my_function_source)}.zip" source = local.my_function_source } @@ -143,16 +161,32 @@ 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 = { - bucket = "my-bucket-with-lambda-builds" - key = aws_s3_bucket_object.my_function.id + bucket = aws_s3_bucket.builds.id + key = aws_s3_object.my_function.id } } ``` +### Lambda Functions from Container Image stored on AWS ECR + +```hcl +module "lambda_function_container_image" { + source = "terraform-aws-modules/lambda/aws" + + function_name = "my-lambda-existing-package-local" + description = "My awesome lambda function" + + create_package = false + + image_uri = "132367819851.dkr.ecr.eu-west-1.amazonaws.com/complete-cow:1.0" + package_type = "Image" +} +``` + ### Lambda Layers (store packages locally and on S3) ```hcl @@ -163,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" { @@ -175,12 +209,12 @@ 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-with-lambda-builds" + s3_bucket = "my-bucket-id-with-lambda-builds" } ``` @@ -197,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" @@ -216,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] @@ -237,31 +271,35 @@ module "vpc" { } ``` - ## Additional IAM policies for Lambda Functions -There are 4 supported ways to attach IAM policies to IAM role used by Lambda Function: +There are 6 supported ways to attach IAM policies to IAM role used by Lambda Function: -1. `policy_json` - set as JSON string or heredoc, when `attach_policy_json = true`. +1. `policy_json` - JSON string or heredoc, when `attach_policy_json = true`. +1. `policy_jsons` - List of JSON strings or heredoc, when `attach_policy_jsons = true` and `number_of_policy_jsons > 0`. 1. `policy` - ARN of existing IAM policy, when `attach_policy = true`. -1. `policies` - List of ARNs of existing IAM policies, when `attach_policies = true`. -1. `policy_statements` - Map of maps to define IAM statements which will be generated as IAM policy. Requires `attach_policy_statements = true`. See examples/complete for more information. - +1. `policies` - List of ARNs of existing IAM policies, when `attach_policies = true` and `number_of_policies > 0`. +1. `policy_statements` - Map of maps to define IAM statements which will be generated as IAM policy. Requires `attach_policy_statements = true`. See `examples/complete` for more information. +1. `assume_role_policy_statements` - Map of maps to define IAM statements which will be generated as IAM policy for assuming Lambda Function role (trust relationship). See `examples/complete` for more information. ## Lambda Permissions for allowed triggers Lambda Permissions should be specified to allow certain resources to invoke Lambda Function. - + ```hcl module "lambda_function" { source = "terraform-aws-modules/lambda/aws" - + # ...omitted for brevity allowed_triggers = { + Config = { + principal = "config.amazonaws.com" + principal_org_id = "o-abcdefghij" + } APIGatewayAny = { - service = "apigateway" - arn = "arn:aws:execute-api:eu-west-1:135367859851:aqnku8akd0" + service = "apigateway" + source_arn = "arn:aws:execute-api:eu-west-1:135367859851:aqnku8akd0/*/*/*" }, APIGatewayDevPost = { service = "apigateway" @@ -275,8 +313,6 @@ module "lambda_function" { } ``` -Note: `service = "apigateway" with arn` is a short form to allow invocations of a Lambda Function from any stage, any method, any resource of an API Gateway. - ## Conditional creation Sometimes you need to have a way to create resources conditionally but Terraform does not allow usage of `count` inside `module` block, so the solution is to specify `create` arguments. @@ -312,7 +348,6 @@ Hash of zip-archive created with the same content of the files is always identic When calling this module multiple times in one execution to create packages with the same `source_path`, zip-archives will be corrupted due to concurrent writes into the same file. There are two solutions - set different values for `hash_extra` to create different archives, or create package once outside (using this module) and then pass `local_existing_package` argument to create other Lambda resources. - ## Debug Building and packaging has been historically hard to debug (especially with Terraform), so we made an effort to make it easier for user to see debug info. There are 3 different debug levels: `DEBUG` - to see only what is happening during planning phase and how a zip file content filtering in case of applied patterns, `DEBUG2` - to see more logging output, `DEBUG3` - to see all logging values, `DUMP_ENV` - to see all logging values and env variables (be careful sharing your env variables as they may contain secrets!). @@ -321,20 +356,19 @@ User can specify debug level like this: ``` export TF_LAMBDA_PACKAGE_LOG_LEVEL=DEBUG2 -terraform apply +terraform apply ``` User can enable comments in heredoc strings in `patterns` which can be helpful in some situations. To do this set this environment variable: ``` export TF_LAMBDA_PACKAGE_PATTERN_COMMENTS=true -terraform apply +terraform apply ``` - ## Build Dependencies -You can specify `source_path` in a variety of ways to achieve desired flexibility when building deployment packages locally or in Docker. You can use absolute or relative paths. +You can specify `source_path` in a variety of ways to achieve desired flexibility when building deployment packages locally or in Docker. You can use absolute or relative paths. If you have placed terraform files in subdirectories, note that relative paths are specified from the directory where `terraform plan` is run and not the location of your terraform file. Note that, when building locally, files are not copying anywhere from the source directories when making packages, we use fast Python regular expressions to find matching files and directories, which makes packaging very fast and easy to understand. @@ -348,10 +382,9 @@ When `source_path` is set to a string, the content of that path will be used to When `source_path` is set to a list of directories the content of each will be taken and one archive will be created. - ### 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 = [ @@ -363,11 +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", @@ -375,14 +409,22 @@ source_path = [ "!vendor/colorful/__pycache__/?.*", ] }, { - path = "src/python3.8-app3", - commands = ["npm install"], + path = "src/nodejs14.x-app1", + npm_requirements = true, + npm_tmp_dir = "/tmp/dir/location" + prefix_in_zip = "foo/bar1", + }, { + path = "src/python-app3", + commands = [ + "npm install", + ":zip" + ], patterns = [ "!.*/.*\\.txt", # Skip all txt files recursively "node_modules/.+", # Include all node_modules ], }, { - path = "src/python3.8-app3", + path = "src/python-app3", commands = ["go build"], patterns = < Deployment package - Create or use existing @@ -446,14 +519,13 @@ When creating archive locally outside of this module you need to set `create_pac } ``` - ### Using deployment package from remote URL This can be implemented in two steps: download file locally using CURL, and pass path to deployment package as `local_existing_package` argument. ```hcl locals { - package_url = "https://raw.githubusercontent.com/terraform-aws-modules/terraform-aws-lambda/master/examples/fixtures/python3.8-zip/existing_package.zip" + package_url = "https://raw.githubusercontent.com/terraform-aws-modules/terraform-aws-lambda/master/examples/fixtures/python-zip/existing_package.zip" downloaded = "downloaded_package_${md5(local.package_url)}.zip" } @@ -480,13 +552,40 @@ module "lambda_function_existing_package_from_remote_url" { 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 = data.null_data_source.downloaded_package.outputs["filename"] } ``` +## 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. 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. + +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] +``` +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, +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 +``` ## How to deploy and manage Lambda Functions? @@ -496,7 +595,6 @@ Typically, Lambda Function resource updates when source code changes. If `publis Published Lambda Function can be invoked using either by version number or using `$LATEST`. This is the simplest way of deployment which does not required any additional tool or service. - ### Controlled deployments (rolling, canary, rollbacks) In order to do controlled deployments (rolling, canary, rollbacks) of Lambda Functions we need to use [Lambda Function aliases](https://docs.aws.amazon.com/lambda/latest/dg/configuration-aliases.html). @@ -509,6 +607,9 @@ There is [alias module](https://github.com/terraform-aws-modules/terraform-aws-l There is [deploy module](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/modules/deploy), which creates required resources to do deployments using AWS CodeDeploy. It also creates the deployment, and wait for completion. See [examples/deploy](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/deploy) for complete end-to-end build/update/deploy process. +## Terraform CI/CD + +Terraform Cloud, Terraform Enterprise, and many other SaaS for running Terraform do not have Python pre-installed on the workers. You will need to provide an [alternative Docker image](https://www.terraform.io/docs/enterprise/install/installer.html#alternative-terraform-worker-image) with Python installed to be able to use this module there. ## FAQ @@ -522,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), you can set environment variable `TF_RECREATE_MISSING_LAMBDA_PACKAGE=false` 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."` ? @@ -532,147 +633,300 @@ Q4: What does this error mean - `"We currently do not support adding policies fo > > The solution is to either disable the creation of Lambda permissions for the current version by setting `create_current_version_allowed_triggers = false`, or to enable publish of Lambda function (`publish = true`). - ## Notes 1. Creation of Lambda Functions and Lambda Layers is very similar and both support the same features (building from source path, using existing package, storing package locally or on S3) 2. Check out this [Awesome list of AWS Lambda Layers](https://github.com/mthenw/awesome-layers) - ## Examples -* [Complete](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/complete) - Create Lambda resources in various combinations with all supported features. -* [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. -* [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 and SNS integration). -* [With VPC](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/with-vpc) - Create Lambda Function with VPC. +- [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). +- [With VPC](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/with-vpc) - Create Lambda Function with VPC. +- [With VPC and VPC Endpoint for S3](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/with-vpc-s3-endpoint) - Create Lambda Function with VPC and VPC Endpoint for S3. +- [With EFS](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/with-efs) - Create Lambda Function with Elastic File System attached (Terraform 0.13+ is recommended). +- [Multiple regions](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/multiple-regions) - Create the same Lambda Function in multiple regions with non-conflicting IAM roles and policies. +- [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 | ~> 0.12.6 | -| aws | ~> 2.46 | +| [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 | ## Providers | Name | Version | |------|---------| -| aws | ~> 2.46 | -| external | n/a | -| local | n/a | -| null | n/a | +| [aws](#provider\_aws) | >= 6.0 | +| [external](#provider\_external) | >= 1.0 | +| [local](#provider\_local) | >= 1.0 | +| [null](#provider\_null) | >= 2.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_group.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_iam_role.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | 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_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 | +| [aws_lambda_permission.unqualified_alias_triggers](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource | +| [aws_lambda_provisioned_concurrency_config.current_version](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_provisioned_concurrency_config) | resource | +| [aws_s3_object.lambda_package](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_object) | resource | +| [local_file.archive_plan](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource | +| [null_resource.archive](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | +| [null_resource.sam_metadata_aws_lambda_function](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | +| [null_resource.sam_metadata_aws_lambda_layer_version](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | +| [aws_arn.log_group_arn](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/arn) | data source | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_cloudwatch_log_group.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/cloudwatch_log_group) | data source | +| [aws_iam_policy.tracing](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy) | data source | +| [aws_iam_policy.vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy) | data source | +| [aws_iam_policy_document.additional_inline](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.async](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.dead_letter](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | +| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | +| [external_external.archive_prepare](https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/external) | data source | ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| allowed\_triggers | Map of allowed triggers to create Lambda permissions | `map(any)` | `{}` | no | -| artifacts\_dir | Directory name where artifacts should be stored | `string` | `"builds"` | no | -| 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 | Controls whether CloudWatch Logs policy should be added to IAM role for Lambda Function | `bool` | `true` | no | -| 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 | Controls whether VPC/network policy should be added to IAM role for Lambda Function | `bool` | `false` | no | -| attach\_policies | Controls whether list of policies should be added to IAM role for Lambda Function | `bool` | `false` | no | -| attach\_policy | Controls whether policy should be added to IAM role for Lambda Function | `bool` | `false` | no | -| attach\_policy\_json | Controls whether policy\_json should be added to IAM role for Lambda Function | `bool` | `false` | no | -| attach\_policy\_statements | Controls whether policy\_statements should be added to IAM role for Lambda Function | `bool` | `false` | no | -| attach\_tracing\_policy | Controls whether X-Ray tracing policy should be added to IAM role for Lambda Function | `bool` | `false` | no | -| build\_in\_docker | Whether to build dependencies in Docker | `bool` | `false` | no | -| cloudwatch\_logs\_kms\_key\_id | The ARN of the KMS Key to use when encrypting log data. | `string` | `null` | no | -| 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\_tags | A map of tags to assign to the resource. | `map(string)` | `{}` | no | -| compatible\_runtimes | A list of Runtimes this layer is compatible with. Up to 5 runtimes can be specified. | `list(string)` | `[]` | no | -| create | Controls whether resources should be created | `bool` | `true` | no | -| create\_async\_event\_config | Controls whether async event configuration for Lambda Function/Alias should be created | `bool` | `false` | no | -| create\_current\_version\_allowed\_triggers | Whether to allow triggers on current version of Lambda Function (this will revoke permissions from previous version because Terraform manages only current resources) | `bool` | `true` | no | -| create\_current\_version\_async\_event\_config | Whether to allow async event configuration on current version of Lambda Function (this will revoke permissions from previous version because Terraform manages only current resources) | `bool` | `true` | no | -| create\_function | Controls whether Lambda Function resource should be created | `bool` | `true` | no | -| create\_layer | Controls whether Lambda Layer resource should be created | `bool` | `false` | no | -| create\_package | Controls whether Lambda package should be created | `bool` | `true` | no | -| create\_role | Controls whether IAM role for Lambda Function should be created | `bool` | `true` | no | -| 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 | Whether to allow async event configuration on unqualified alias pointing to $LATEST version | `bool` | `true` | no | -| dead\_letter\_target\_arn | The ARN of an SNS topic or SQS queue to notify when an invocation fails. | `string` | `null` | no | -| description | Description of your Lambda Function (or Layer) | `string` | `""` | no | -| destination\_on\_failure | Amazon Resource Name (ARN) of the destination resource for failed asynchronous invocations | `string` | `null` | no | -| destination\_on\_success | Amazon Resource Name (ARN) of the destination resource for successful asynchronous invocations | `string` | `null` | no | -| docker\_build\_root | Root dir where to build in Docker | `string` | `""` | no | -| docker\_file | Path to a Dockerfile when building in Docker | `string` | `""` | no | -| docker\_image | Docker image to use for the build | `string` | `""` | no | -| docker\_pip\_cache | Whether to mount a shared pip cache folder into docker environment or not | `any` | `null` | no | -| docker\_with\_ssh\_agent | Whether to pass SSH\_AUTH\_SOCK into docker environment or not | `bool` | `false` | no | -| environment\_variables | A map that defines environment variables for the Lambda Function. | `map(string)` | `{}` | no | -| function\_name | A unique name for your Lambda Function | `string` | `""` | no | -| handler | Lambda Function entrypoint in your code | `string` | `""` | no | -| hash\_extra | The string to add into hashing function. Useful when building same source path for different functions. | `string` | `""` | no | -| kms\_key\_arn | The ARN of KMS key to use by your Lambda Function | `string` | `null` | no | -| 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\_role | IAM role 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 | Name of Lambda Layer to create | `string` | `""` | no | -| layers | List of Lambda Layer Version ARNs (maximum of 5) to attach to your Lambda Function. | `list(string)` | `null` | no | -| license\_info | License info for your Lambda Layer. Eg, MIT or full url of a license. | `string` | `""` | no | -| local\_existing\_package | The absolute path to an existing zip-file to use | `string` | `null` | no | -| 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 | 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 | Amount of memory in MB your Lambda Function can use at runtime. Valid value between 128 MB to 3008 MB, in 64 MB increments. | `number` | `128` | no | -| number\_of\_policies | Number of policies to attach to IAM role for Lambda Function | `number` | `0` | no | -| policies | List of policy statements ARN to attach to Lambda Function role | `list(string)` | `[]` | no | -| policy | An additional policy document ARN to attach to the Lambda Function role | `string` | `null` | no | -| policy\_json | An additional policy document as JSON to attach to the Lambda Function role | `string` | `null` | no | -| policy\_statements | Map of dynamic policy statements to attach to Lambda Function role | `any` | `{}` | no | -| provisioned\_concurrent\_executions | Amount of capacity to allocate. Must be greater than or equal to 1. | `number` | `-1` | no | -| publish | Whether to publish creation/change as new Lambda Function Version. | `bool` | `false` | no | -| 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 | Description of IAM role to use for Lambda Function | `string` | `null` | no | -| role\_force\_detach\_policies | Specifies to force detaching any policies the IAM role has before destroying it. | `bool` | `true` | no | -| role\_name | Name of IAM role to use for Lambda Function | `string` | `null` | no | -| role\_path | Path of IAM role to use for Lambda Function | `string` | `null` | no | -| 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 | -| role\_tags | A map of tags to assign to IAM role | `map(string)` | `{}` | no | -| runtime | Lambda Function runtime | `string` | `""` | no | -| s3\_bucket | S3 bucket to store artifacts | `string` | `null` | no | -| 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\_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 | A map of tags to assign to S3 bucket object. | `map(string)` | `{}` | no | -| source\_path | The absolute path to a local file or directory containing your Lambda source code | `any` | `null` | no | -| store\_on\_s3 | Whether to store produced artifacts on S3 or locally. | `bool` | `false` | no | -| tags | A map of tags to assign to resources. | `map(string)` | `{}` | no | -| timeout | The amount of time your Lambda Function has to run in seconds. | `number` | `3` | no | -| tracing\_mode | Tracing mode of the Lambda Function. Valid value can be either PassThrough or Active. | `string` | `null` | no | -| trusted\_entities | Lambda Function additional trusted entities for assuming roles (trust relationship) | `list(string)` | `[]` | no | -| use\_existing\_cloudwatch\_log\_group | Whether to use an existing CloudWatch log group or create new | `bool` | `false` | no | -| vpc\_security\_group\_ids | List of security group ids when Lambda Function should run in the VPC. | `list(string)` | `null` | no | -| vpc\_subnet\_ids | List of subnet ids when Lambda Function should run in the VPC. Usually private or intra subnets. | `list(string)` | `null` | no | +| [allowed\_triggers](#input\_allowed\_triggers) | Map of allowed triggers to create Lambda permissions | `map(any)` | `{}` | no | +| [architectures](#input\_architectures) | Instruction set architecture for your Lambda function. Valid values are ["x86\_64"] and ["arm64"]. | `list(string)` | `null` | no | +| [artifacts\_dir](#input\_artifacts\_dir) | Directory name where artifacts should be stored | `string` | `"builds"` | no | +| [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 | +| [attach\_policy](#input\_attach\_policy) | Controls whether policy should be added to IAM role for Lambda Function | `bool` | `false` | no | +| [attach\_policy\_json](#input\_attach\_policy\_json) | Controls whether policy\_json should be added to IAM role for Lambda Function | `bool` | `false` | no | +| [attach\_policy\_jsons](#input\_attach\_policy\_jsons) | Controls whether policy\_jsons should be added to IAM role for Lambda Function | `bool` | `false` | no | +| [attach\_policy\_statements](#input\_attach\_policy\_statements) | Controls whether policy\_statements should be added to IAM role for Lambda Function | `bool` | `false` | no | +| [attach\_tracing\_policy](#input\_attach\_tracing\_policy) | Controls whether X-Ray tracing policy should be added to IAM role for Lambda Function | `bool` | `false` | no | +| [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 | +| [compatible\_runtimes](#input\_compatible\_runtimes) | A list of Runtimes this layer is compatible with. Up to 5 runtimes can be specified. | `list(string)` | `[]` | no | +| [cors](#input\_cors) | CORS settings to be used by the Lambda Function URL | `any` | `{}` | no | +| [create](#input\_create) | Controls whether resources should be created | `bool` | `true` | no | +| [create\_async\_event\_config](#input\_create\_async\_event\_config) | Controls whether async event configuration for Lambda Function/Alias should be created | `bool` | `false` | no | +| [create\_current\_version\_allowed\_triggers](#input\_create\_current\_version\_allowed\_triggers) | Whether to allow triggers on current version of Lambda Function (this will revoke permissions from previous version because Terraform manages only current resources) | `bool` | `true` | no | +| [create\_current\_version\_async\_event\_config](#input\_create\_current\_version\_async\_event\_config) | Whether to allow async event configuration on current version of Lambda Function (this will revoke permissions from previous version because Terraform manages only current resources) | `bool` | `true` | no | +| [create\_function](#input\_create\_function) | Controls whether Lambda Function resource should be created | `bool` | `true` | no | +| [create\_lambda\_function\_url](#input\_create\_lambda\_function\_url) | Controls whether the Lambda Function URL resource should be created | `bool` | `false` | no | +| [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 | +| [dead\_letter\_target\_arn](#input\_dead\_letter\_target\_arn) | The ARN of an SNS topic or SQS queue to notify when an invocation fails. | `string` | `null` | no | +| [description](#input\_description) | Description of your Lambda Function (or Layer) | `string` | `""` | no | +| [destination\_on\_failure](#input\_destination\_on\_failure) | Amazon Resource Name (ARN) of the destination resource for failed asynchronous invocations | `string` | `null` | no | +| [destination\_on\_success](#input\_destination\_on\_success) | Amazon Resource Name (ARN) of the destination resource for successful asynchronous invocations | `string` | `null` | no | +| [docker\_additional\_options](#input\_docker\_additional\_options) | Additional options to pass to the docker run command (e.g. to set environment variables, volumes, etc.) | `list(string)` | `[]` | no | +| [docker\_build\_root](#input\_docker\_build\_root) | Root dir where to build in Docker | `string` | `""` | no | +| [docker\_entrypoint](#input\_docker\_entrypoint) | Path to the Docker entrypoint to use | `string` | `null` | no | +| [docker\_file](#input\_docker\_file) | Path to a Dockerfile when building in Docker | `string` | `""` | no | +| [docker\_image](#input\_docker\_image) | Docker image to use for the build | `string` | `""` | no | +| [docker\_pip\_cache](#input\_docker\_pip\_cache) | Whether to mount a shared pip cache folder into docker environment or not | `any` | `null` | no | +| [docker\_with\_ssh\_agent](#input\_docker\_with\_ssh\_agent) | Whether to pass SSH\_AUTH\_SOCK into docker environment or not | `bool` | `false` | no | +| [environment\_variables](#input\_environment\_variables) | A map that defines environment variables for the Lambda Function. | `map(string)` | `{}` | no | +| [ephemeral\_storage\_size](#input\_ephemeral\_storage\_size) | Amount of ephemeral storage (/tmp) in MB your Lambda Function can use at runtime. Valid value between 512 MB to 10,240 MB (10 GB). | `number` | `512` | no | +| [event\_source\_mapping](#input\_event\_source\_mapping) | Map of event source mapping | `any` | `{}` | no | +| [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 | +| [image\_config\_command](#input\_image\_config\_command) | The CMD for the docker image | `list(string)` | `[]` | no | +| [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 | +| [number\_of\_policies](#input\_number\_of\_policies) | Number of policies to attach to IAM role for Lambda Function | `number` | `0` | no | +| [number\_of\_policy\_jsons](#input\_number\_of\_policy\_jsons) | Number of policies JSON to attach to IAM role for Lambda Function | `number` | `0` | no | +| [package\_type](#input\_package\_type) | The Lambda deployment package type. Valid options: Zip or Image | `string` | `"Zip"` | no | +| [policies](#input\_policies) | List of policy statements ARN to attach to Lambda Function role | `list(string)` | `[]` | no | +| [policy](#input\_policy) | An additional policy document ARN to attach to the Lambda Function role | `string` | `null` | no | +| [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\_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 | +| [role\_tags](#input\_role\_tags) | A map of tags to assign to IAM role | `map(string)` | `{}` | no | +| [runtime](#input\_runtime) | Lambda Function runtime | `string` | `""` | no | +| [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 | +| [vpc\_subnet\_ids](#input\_vpc\_subnet\_ids) | List of subnet ids when Lambda Function should run in the VPC. Usually private or intra subnets. | `list(string)` | `null` | no | ## Outputs | Name | Description | |------|-------------| -| lambda\_cloudwatch\_log\_group\_arn | The ARN of the Cloudwatch Log Group | -| lambda\_role\_arn | The ARN of the IAM role created for the Lambda Function | -| lambda\_role\_name | The name of the IAM role created for the Lambda Function | -| local\_filename | The filename of zip archive deployed (if deployment was from local) | -| s3\_object | The map with S3 object data of zip archive deployed (if deployment was from S3) | -| this\_lambda\_function\_arn | The ARN of the Lambda Function | -| this\_lambda\_function\_invoke\_arn | The Invoke ARN of the Lambda Function | -| this\_lambda\_function\_kms\_key\_arn | The ARN for the KMS encryption key of Lambda Function | -| this\_lambda\_function\_last\_modified | The date Lambda Function resource was last modified | -| this\_lambda\_function\_name | The name of the Lambda Function | -| this\_lambda\_function\_qualified\_arn | The ARN identifying your Lambda Function Version | -| this\_lambda\_function\_source\_code\_hash | Base64-encoded representation of raw SHA-256 sum of the zip file | -| this\_lambda\_function\_source\_code\_size | The size in bytes of the function .zip file | -| this\_lambda\_function\_version | Latest published version of Lambda Function | -| this\_lambda\_layer\_arn | The ARN of the Lambda Layer with version | -| this\_lambda\_layer\_created\_date | The date Lambda Layer resource was created | -| this\_lambda\_layer\_layer\_arn | The ARN of the Lambda Layer without version | -| this\_lambda\_layer\_source\_code\_size | The size in bytes of the Lambda Layer .zip file | -| this\_lambda\_layer\_version | The Lambda Layer version | - - +| [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 | +| [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\_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 | +| [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 | +| [lambda\_function\_url\_id](#output\_lambda\_function\_url\_id) | The Lambda Function URL generated id | +| [lambda\_function\_version](#output\_lambda\_function\_version) | Latest published version of Lambda Function | +| [lambda\_layer\_arn](#output\_lambda\_layer\_arn) | The ARN of the Lambda Layer with version | +| [lambda\_layer\_created\_date](#output\_lambda\_layer\_created\_date) | The date Lambda Layer resource was created | +| [lambda\_layer\_layer\_arn](#output\_lambda\_layer\_layer\_arn) | The ARN of the Lambda Layer without version | +| [lambda\_layer\_source\_code\_size](#output\_lambda\_layer\_source\_code\_size) | The size in bytes of the Lambda Layer .zip file | +| [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 | +| [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 + +### Python + +During development involving modifying python files, use tox to run unit tests: + +``` +tox +``` + +This will try to run unit tests which each supported python version, reporting errors for python versions which are not installed locally. + +If you only want to test against your main python version: + +``` +tox -e py +``` + +You can also pass additional positional arguments to pytest which is used to run test, e.g. to make it verbose: +``` +tox -e py -- -vvv +``` ## Authors @@ -680,7 +934,12 @@ Module managed by [Anton Babenko](https://github.com/antonbabenko). Check out [s Please reach out to [Betajob](https://www.betajob.com/) if you are looking for commercial support for your Terraform, AWS, or serverless project. - ## License -Apache 2 Licensed. See LICENSE for full details. +Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/LICENSE) for full details. + +## Additional information for users from Russia and Belarus + +* Russia has [illegally annexed Crimea in 2014](https://en.wikipedia.org/wiki/Annexation_of_Crimea_by_the_Russian_Federation) and [brought the war in Donbas](https://en.wikipedia.org/wiki/War_in_Donbas) followed by [full-scale invasion of Ukraine in 2022](https://en.wikipedia.org/wiki/2022_Russian_invasion_of_Ukraine). +* Russia has brought sorrow and devastations to millions of Ukrainians, killed hundreds of innocent people, damaged thousands of buildings, and forced several million people to flee. +* [Putin khuylo!](https://en.wikipedia.org/wiki/Putin_khuylo!) 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 f3677763..6bcb2530 100644 --- a/examples/alias/README.md +++ b/examples/alias/README.md @@ -14,47 +14,73 @@ $ terraform apply Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. - + ## Requirements -No requirements. +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | +| [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| random | n/a | +| [aws](#provider\_aws) | >= 6.0 | +| [random](#provider\_random) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [alias\_existing](#module\_alias\_existing) | ../../modules/alias | n/a | +| [alias\_no\_refresh](#module\_alias\_no\_refresh) | ../../modules/alias | n/a | +| [alias\_refresh](#module\_alias\_refresh) | ../../modules/alias | n/a | +| [lambda\_function](#module\_lambda\_function) | ../../ | n/a | +| [sqs\_events](#module\_sqs\_events) | terraform-aws-modules/sqs/aws | ~> 3.0 | + +## Resources + +| 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 -No input. +No inputs. ## Outputs | Name | Description | |------|-------------| -| lambda\_role\_arn | The ARN of the IAM role created for the Lambda Function | -| lambda\_role\_name | The name of the IAM role created for the Lambda Function | -| local\_filename | The filename of zip archive deployed (if deployment was from local) | -| s3\_object | The map with S3 object data of zip archive deployed (if deployment was from S3) | -| this\_lambda\_alias\_arn | The ARN of the Lambda Function Alias | -| this\_lambda\_alias\_description | Description of alias | -| this\_lambda\_alias\_function\_version | Lambda function version which the alias uses | -| this\_lambda\_alias\_invoke\_arn | The ARN to be used for invoking Lambda Function from API Gateway | -| this\_lambda\_alias\_name | The name of the Lambda Function Alias | -| this\_lambda\_function\_arn | The ARN of the Lambda Function | -| this\_lambda\_function\_invoke\_arn | The Invoke ARN of the Lambda Function | -| this\_lambda\_function\_kms\_key\_arn | The ARN for the KMS encryption key of Lambda Function | -| this\_lambda\_function\_last\_modified | The date Lambda Function resource was last modified | -| this\_lambda\_function\_name | The name of the Lambda Function | -| this\_lambda\_function\_qualified\_arn | The ARN identifying your Lambda Function Version | -| this\_lambda\_function\_source\_code\_hash | Base64-encoded representation of raw SHA-256 sum of the zip file | -| this\_lambda\_function\_source\_code\_size | The size in bytes of the function .zip file | -| this\_lambda\_function\_version | Latest published version of Lambda Function | -| this\_lambda\_layer\_arn | The ARN of the Lambda Layer with version | -| this\_lambda\_layer\_created\_date | The date Lambda Layer resource was created | -| this\_lambda\_layer\_layer\_arn | The ARN of the Lambda Layer without version | -| this\_lambda\_layer\_source\_code\_size | The size in bytes of the Lambda Layer .zip file | -| this\_lambda\_layer\_version | The Lambda Layer version | - - +| [lambda\_alias\_arn](#output\_lambda\_alias\_arn) | The ARN of the Lambda Function Alias | +| [lambda\_alias\_description](#output\_lambda\_alias\_description) | Description of alias | +| [lambda\_alias\_event\_source\_mapping\_function\_arn](#output\_lambda\_alias\_event\_source\_mapping\_function\_arn) | The the ARN of the Lambda function the event source mapping is sending events to | +| [lambda\_alias\_event\_source\_mapping\_state](#output\_lambda\_alias\_event\_source\_mapping\_state) | The state of the event source mapping | +| [lambda\_alias\_event\_source\_mapping\_state\_transition\_reason](#output\_lambda\_alias\_event\_source\_mapping\_state\_transition\_reason) | The reason the event source mapping is in its current state | +| [lambda\_alias\_event\_source\_mapping\_uuid](#output\_lambda\_alias\_event\_source\_mapping\_uuid) | The UUID of the created event source mapping | +| [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 | +| [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 | +| [lambda\_layer\_arn](#output\_lambda\_layer\_arn) | The ARN of the Lambda Layer with version | +| [lambda\_layer\_created\_date](#output\_lambda\_layer\_created\_date) | The date Lambda Layer resource was created | +| [lambda\_layer\_layer\_arn](#output\_lambda\_layer\_layer\_arn) | The ARN of the Lambda Layer without version | +| [lambda\_layer\_source\_code\_size](#output\_lambda\_layer\_source\_code\_size) | The size in bytes of the Lambda Layer .zip file | +| [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 | +| [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 9e6b2bc1..5fed7678 100644 --- a/examples/alias/main.tf +++ b/examples/alias/main.tf @@ -2,47 +2,58 @@ 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 } +module "sqs_events" { + source = "terraform-aws-modules/sqs/aws" + version = "~> 3.0" + + name = "${random_pet.this.id}-events" +} + module "lambda_function" { source = "../../" 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 maximum_event_age_in_seconds = 100 - provisioned_concurrent_executions = 1 + attach_policies = true + policies = [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole", + ] + number_of_policies = 1 allowed_triggers = { APIGatewayAny = { - service = "apigateway" - arn = "arn:aws:execute-api:eu-west-1:135367859851:aqnku8akd0" + service = "apigateway" + source_arn = "arn:aws:execute-api:eu-west-1:135367859851:aqnku8akd0/*/*/*" } } - // current version - // create_current_version_async_event_config = false - // create_current_version_triggers = false + # current version + # create_current_version_async_event_config = false + # create_current_version_triggers = false - // unqualified alias - // create_unqualified_alias_async_event_config = false - // create_unqualified_alias_triggers = false + # unqualified alias + # create_unqualified_alias_async_event_config = false + # create_unqualified_alias_triggers = false } module "alias_no_refresh" { @@ -53,17 +64,29 @@ module "alias_no_refresh" { name = "current-no-refresh" - function_name = module.lambda_function.this_lambda_function_name - function_version = module.lambda_function.this_lambda_function_version + function_name = module.lambda_function.lambda_function_name + function_version = module.lambda_function.lambda_function_version + + # create_version_async_event_config = false + # create_async_event_config = true + # maximum_event_age_in_seconds = 130 - // create_version_async_event_config = false - // create_async_event_config = true - // maximum_event_age_in_seconds = 130 + event_source_mapping = { + sqs = { + service = "sqs" + event_source_arn = module.sqs_events.sqs_queue_arn + maximum_concurrency = 10 + } + } allowed_triggers = { - AnotherAPIGatewayAny = { // keys should be unique - service = "apigateway" - arn = "arn:aws:execute-api:eu-west-1:135367859851:abcdedfgse" + 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/*/*/*" } } @@ -77,7 +100,7 @@ module "alias_refresh" { name = "current-with-refresh" - function_name = module.lambda_function.this_lambda_function_name + function_name = module.lambda_function.lambda_function_name } module "alias_existing" { @@ -86,16 +109,27 @@ module "alias_existing" { create = true use_existing_alias = true - name = module.alias_refresh.this_lambda_alias_name - function_name = module.lambda_function.this_lambda_function_name + name = module.alias_refresh.lambda_alias_name + function_name = module.lambda_function.lambda_function_name create_async_event_config = true maximum_event_age_in_seconds = 100 + event_source_mapping = { + sqs = { + service = "sqs" + event_source_arn = module.sqs_events.sqs_queue_arn + } + } + allowed_triggers = { + Config = { + principal = "config.amazonaws.com" + principal_org_id = data.aws_organizations_organization.this.id + } ThirdAPIGatewayAny = { - service = "apigateway" - arn = "arn:aws:execute-api:eu-west-1:135367859851:aqnku8akd0" + service = "apigateway" + source_arn = "arn:aws:execute-api:eu-west-1:135367859851:aqnku8akd0/*/*/*" } } diff --git a/examples/alias/outputs.tf b/examples/alias/outputs.tf index 03380965..8a3e2274 100644 --- a/examples/alias/outputs.tf +++ b/examples/alias/outputs.tf @@ -1,73 +1,78 @@ # Lambda Function -output "this_lambda_function_arn" { +output "lambda_function_arn" { description = "The ARN of the Lambda Function" - value = module.lambda_function.this_lambda_function_arn + value = module.lambda_function.lambda_function_arn } -output "this_lambda_function_invoke_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.lambda_function_arn_static +} + +output "lambda_function_invoke_arn" { description = "The Invoke ARN of the Lambda Function" - value = module.lambda_function.this_lambda_function_invoke_arn + value = module.lambda_function.lambda_function_invoke_arn } -output "this_lambda_function_name" { +output "lambda_function_name" { description = "The name of the Lambda Function" - value = module.lambda_function.this_lambda_function_name + value = module.lambda_function.lambda_function_name } -output "this_lambda_function_qualified_arn" { +output "lambda_function_qualified_arn" { description = "The ARN identifying your Lambda Function Version" - value = module.lambda_function.this_lambda_function_qualified_arn + value = module.lambda_function.lambda_function_qualified_arn } -output "this_lambda_function_version" { +output "lambda_function_version" { description = "Latest published version of Lambda Function" - value = module.lambda_function.this_lambda_function_version + value = module.lambda_function.lambda_function_version } -output "this_lambda_function_last_modified" { +output "lambda_function_last_modified" { description = "The date Lambda Function resource was last modified" - value = module.lambda_function.this_lambda_function_last_modified + value = module.lambda_function.lambda_function_last_modified } -output "this_lambda_function_kms_key_arn" { +output "lambda_function_kms_key_arn" { description = "The ARN for the KMS encryption key of Lambda Function" - value = module.lambda_function.this_lambda_function_kms_key_arn + value = module.lambda_function.lambda_function_kms_key_arn } -output "this_lambda_function_source_code_hash" { +output "lambda_function_source_code_hash" { description = "Base64-encoded representation of raw SHA-256 sum of the zip file" - value = module.lambda_function.this_lambda_function_source_code_hash + value = module.lambda_function.lambda_function_source_code_hash } -output "this_lambda_function_source_code_size" { +output "lambda_function_source_code_size" { description = "The size in bytes of the function .zip file" - value = module.lambda_function.this_lambda_function_source_code_size + value = module.lambda_function.lambda_function_source_code_size } # Lambda Layer -output "this_lambda_layer_arn" { +output "lambda_layer_arn" { description = "The ARN of the Lambda Layer with version" - value = module.lambda_function.this_lambda_layer_arn + value = module.lambda_function.lambda_layer_arn } -output "this_lambda_layer_layer_arn" { +output "lambda_layer_layer_arn" { description = "The ARN of the Lambda Layer without version" - value = module.lambda_function.this_lambda_layer_layer_arn + value = module.lambda_function.lambda_layer_layer_arn } -output "this_lambda_layer_created_date" { +output "lambda_layer_created_date" { description = "The date Lambda Layer resource was created" - value = module.lambda_function.this_lambda_layer_created_date + value = module.lambda_function.lambda_layer_created_date } -output "this_lambda_layer_source_code_size" { +output "lambda_layer_source_code_size" { description = "The size in bytes of the Lambda Layer .zip file" - value = module.lambda_function.this_lambda_layer_source_code_size + value = module.lambda_function.lambda_layer_source_code_size } -output "this_lambda_layer_version" { +output "lambda_layer_version" { description = "The Lambda Layer version" - value = module.lambda_function.this_lambda_layer_version + value = module.lambda_function.lambda_layer_version } # IAM Role @@ -95,27 +100,47 @@ output "s3_object" { ############### # Lambda Alias ############### -output "this_lambda_alias_name" { +output "lambda_alias_name" { description = "The name of the Lambda Function Alias" - value = module.alias_refresh.this_lambda_alias_name + value = module.alias_refresh.lambda_alias_name } -output "this_lambda_alias_arn" { +output "lambda_alias_arn" { description = "The ARN of the Lambda Function Alias" - value = module.alias_refresh.this_lambda_alias_arn + value = module.alias_refresh.lambda_alias_arn } -output "this_lambda_alias_invoke_arn" { +output "lambda_alias_invoke_arn" { description = "The ARN to be used for invoking Lambda Function from API Gateway" - value = module.alias_refresh.this_lambda_alias_invoke_arn + value = module.alias_refresh.lambda_alias_invoke_arn } -output "this_lambda_alias_description" { +output "lambda_alias_description" { description = "Description of alias" - value = module.alias_refresh.this_lambda_alias_description + value = module.alias_refresh.lambda_alias_description } -output "this_lambda_alias_function_version" { +output "lambda_alias_function_version" { description = "Lambda function version which the alias uses" - value = module.alias_refresh.this_lambda_alias_function_version + value = module.alias_refresh.lambda_alias_function_version +} + +output "lambda_alias_event_source_mapping_function_arn" { + description = "The the ARN of the Lambda function the event source mapping is sending events to" + value = module.alias_no_refresh.lambda_alias_event_source_mapping_function_arn +} + +output "lambda_alias_event_source_mapping_state" { + description = "The state of the event source mapping" + value = module.alias_no_refresh.lambda_alias_event_source_mapping_state +} + +output "lambda_alias_event_source_mapping_state_transition_reason" { + description = "The reason the event source mapping is in its current state" + value = module.alias_no_refresh.lambda_alias_event_source_mapping_state_transition_reason +} + +output "lambda_alias_event_source_mapping_uuid" { + description = "The UUID of the created event source mapping" + value = module.alias_no_refresh.lambda_alias_event_source_mapping_uuid } diff --git a/examples/alias/variables.tf b/examples/alias/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/alias/versions.tf b/examples/alias/versions.tf new file mode 100644 index 00000000..d2f4f3e8 --- /dev/null +++ b/examples/alias/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/async/README.md b/examples/async/README.md index 6e78114d..3bca0e68 100644 --- a/examples/async/README.md +++ b/examples/async/README.md @@ -14,44 +14,62 @@ $ terraform apply Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. - + ## Requirements -No requirements. +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | +| [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| aws | n/a | -| random | n/a | +| [aws](#provider\_aws) | >= 6.0 | +| [random](#provider\_random) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [lambda\_function](#module\_lambda\_function) | ../../ | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_sns_topic.async](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic) | resource | +| [aws_sqs_queue.async](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 | ## Inputs -No input. +No inputs. ## Outputs | Name | Description | |------|-------------| -| lambda\_cloudwatch\_log\_group\_arn | The ARN of the Cloudwatch Log Group | -| lambda\_role\_arn | The ARN of the IAM role created for the Lambda Function | -| lambda\_role\_name | The name of the IAM role created for the Lambda Function | -| local\_filename | The filename of zip archive deployed (if deployment was from local) | -| s3\_object | The map with S3 object data of zip archive deployed (if deployment was from S3) | -| this\_lambda\_function\_arn | The ARN of the Lambda Function | -| this\_lambda\_function\_invoke\_arn | The Invoke ARN of the Lambda Function | -| this\_lambda\_function\_kms\_key\_arn | The ARN for the KMS encryption key of Lambda Function | -| this\_lambda\_function\_last\_modified | The date Lambda Function resource was last modified | -| this\_lambda\_function\_name | The name of the Lambda Function | -| this\_lambda\_function\_qualified\_arn | The ARN identifying your Lambda Function Version | -| this\_lambda\_function\_source\_code\_hash | Base64-encoded representation of raw SHA-256 sum of the zip file | -| this\_lambda\_function\_source\_code\_size | The size in bytes of the function .zip file | -| this\_lambda\_function\_version | Latest published version of Lambda Function | -| this\_lambda\_layer\_arn | The ARN of the Lambda Layer with version | -| this\_lambda\_layer\_created\_date | The date Lambda Layer resource was created | -| this\_lambda\_layer\_layer\_arn | The ARN of the Lambda Layer without version | -| this\_lambda\_layer\_source\_code\_size | The size in bytes of the Lambda Layer .zip file | -| this\_lambda\_layer\_version | The Lambda Layer version | - - +| [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 | +| [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 | +| [lambda\_layer\_arn](#output\_lambda\_layer\_arn) | The ARN of the Lambda Layer with version | +| [lambda\_layer\_created\_date](#output\_lambda\_layer\_created\_date) | The date Lambda Layer resource was created | +| [lambda\_layer\_layer\_arn](#output\_lambda\_layer\_layer\_arn) | The ARN of the Lambda Layer without version | +| [lambda\_layer\_source\_code\_size](#output\_lambda\_layer\_source\_code\_size) | The size in bytes of the Lambda Layer .zip file | +| [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 | +| [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 b91e06b6..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,9 +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/outputs.tf b/examples/async/outputs.tf index c69f2572..00d490a9 100644 --- a/examples/async/outputs.tf +++ b/examples/async/outputs.tf @@ -1,73 +1,78 @@ # Lambda Function -output "this_lambda_function_arn" { +output "lambda_function_arn" { description = "The ARN of the Lambda Function" - value = module.lambda_function.this_lambda_function_arn + value = module.lambda_function.lambda_function_arn } -output "this_lambda_function_invoke_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.lambda_function_arn_static +} + +output "lambda_function_invoke_arn" { description = "The Invoke ARN of the Lambda Function" - value = module.lambda_function.this_lambda_function_invoke_arn + value = module.lambda_function.lambda_function_invoke_arn } -output "this_lambda_function_name" { +output "lambda_function_name" { description = "The name of the Lambda Function" - value = module.lambda_function.this_lambda_function_name + value = module.lambda_function.lambda_function_name } -output "this_lambda_function_qualified_arn" { +output "lambda_function_qualified_arn" { description = "The ARN identifying your Lambda Function Version" - value = module.lambda_function.this_lambda_function_qualified_arn + value = module.lambda_function.lambda_function_qualified_arn } -output "this_lambda_function_version" { +output "lambda_function_version" { description = "Latest published version of Lambda Function" - value = module.lambda_function.this_lambda_function_version + value = module.lambda_function.lambda_function_version } -output "this_lambda_function_last_modified" { +output "lambda_function_last_modified" { description = "The date Lambda Function resource was last modified" - value = module.lambda_function.this_lambda_function_last_modified + value = module.lambda_function.lambda_function_last_modified } -output "this_lambda_function_kms_key_arn" { +output "lambda_function_kms_key_arn" { description = "The ARN for the KMS encryption key of Lambda Function" - value = module.lambda_function.this_lambda_function_kms_key_arn + value = module.lambda_function.lambda_function_kms_key_arn } -output "this_lambda_function_source_code_hash" { +output "lambda_function_source_code_hash" { description = "Base64-encoded representation of raw SHA-256 sum of the zip file" - value = module.lambda_function.this_lambda_function_source_code_hash + value = module.lambda_function.lambda_function_source_code_hash } -output "this_lambda_function_source_code_size" { +output "lambda_function_source_code_size" { description = "The size in bytes of the function .zip file" - value = module.lambda_function.this_lambda_function_source_code_size + value = module.lambda_function.lambda_function_source_code_size } # Lambda Layer -output "this_lambda_layer_arn" { +output "lambda_layer_arn" { description = "The ARN of the Lambda Layer with version" - value = module.lambda_function.this_lambda_layer_arn + value = module.lambda_function.lambda_layer_arn } -output "this_lambda_layer_layer_arn" { +output "lambda_layer_layer_arn" { description = "The ARN of the Lambda Layer without version" - value = module.lambda_function.this_lambda_layer_layer_arn + value = module.lambda_function.lambda_layer_layer_arn } -output "this_lambda_layer_created_date" { +output "lambda_layer_created_date" { description = "The date Lambda Layer resource was created" - value = module.lambda_function.this_lambda_layer_created_date + value = module.lambda_function.lambda_layer_created_date } -output "this_lambda_layer_source_code_size" { +output "lambda_layer_source_code_size" { description = "The size in bytes of the Lambda Layer .zip file" - value = module.lambda_function.this_lambda_layer_source_code_size + value = module.lambda_function.lambda_layer_source_code_size } -output "this_lambda_layer_version" { +output "lambda_layer_version" { description = "The Lambda Layer version" - value = module.lambda_function.this_lambda_layer_version + value = module.lambda_function.lambda_layer_version } # IAM Role @@ -97,4 +102,3 @@ output "s3_object" { description = "The map with S3 object data of zip archive deployed (if deployment was from S3)" value = module.lambda_function.s3_object } - diff --git a/examples/async/variables.tf b/examples/async/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/async/versions.tf b/examples/async/versions.tf new file mode 100644 index 00000000..d2f4f3e8 --- /dev/null +++ b/examples/async/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/build-package/README.md b/examples/build-package/README.md index 8900bb8e..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,23 +23,62 @@ $ terraform apply Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. - + ## Requirements -No requirements. +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | +| [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| random | n/a | +| [random](#provider\_random) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [lambda\_function\_from\_package](#module\_lambda\_function\_from\_package) | ../../ | n/a | +| [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 | +| [package\_with\_pip\_requirements\_in\_docker\_overriding\_entrypoint](#module\_package\_with\_pip\_requirements\_in\_docker\_overriding\_entrypoint) | ../../ | n/a | + +## Resources + +| Name | Type | +|------|------| +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | ## Inputs -No input. +No inputs. ## Outputs -No output. - - +No outputs. + diff --git a/examples/build-package/main.tf b/examples/build-package/main.tf index 3abd2cda..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" { @@ -17,14 +15,126 @@ resource "random_pet" "this" { # Build packages ################# -# Create zip-archive of a single directory where "pip install" will also be executed (default for python runtime) +# Create zip-archive of a single directory where "pip install" will also be executed (default for python runtime with requirements.txt present) module "package_dir" { source = "../../" create_function = false - runtime = "python3.8" - source_path = "${path.module}/../fixtures/python3.8-app1" + build_in_docker = true + runtime = "python3.12" + source_path = "${path.module}/../fixtures/python-app1" + artifacts_dir = "${path.root}/builds/package_dir/" +} + +# Create zip-archive of a single directory where "pip install" will also be executed (default for python runtime with requirements.txt present) and set temporary directory for pip install +module "package_dir_pip_dir" { + source = "../../" + + create_function = false + + build_in_docker = true + runtime = "python3.12" + source_path = [{ + path = "${path.module}/../fixtures/python-app1" + pip_tmp_dir = "${path.cwd}/../fixtures" + 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 (using docker) +module "package_dir_poetry" { + source = "../../" + + create_function = false + + build_in_docker = true + runtime = "python3.12" + docker_image = "build-python-poetry" + docker_file = "${path.module}/../fixtures/python-app-poetry/docker/Dockerfile" + + source_path = [ + { + 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) @@ -33,10 +143,10 @@ module "package_dir_without_pip_install" { 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 } @@ -49,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: @@ -61,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" } ] @@ -81,16 +191,45 @@ module "package_with_pip_requirements_in_docker" { create_function = false - runtime = "python3.8" + runtime = "python3.12" + source_path = [ + "${path.module}/../fixtures/python-app1/index.py", + "${path.module}/../fixtures/python-app1/dir1/dir2", + { + pip_requirements = "${path.module}/../fixtures/python-app1/requirements.txt" + } + ] + + build_in_docker = true +} + +# Create zip-archive which contains: +# 1. A single file - index.py +# 2. Content of directory "dir2" +# 3. Install pip requirements +# "pip install" is running in a Docker container for the specified runtime +# The docker entrypoint is overridden, allowing you to run additional commands within the container +module "package_with_pip_requirements_in_docker_overriding_entrypoint" { + source = "../../" + + create_function = false + + 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" build_in_docker = true + docker_additional_options = [ + "-e", "MY_ENV_VAR='My environment variable value'", + "-v", "${abspath(path.module)}/../fixtures/python-app1/docker/entrypoint.sh:/entrypoint/entrypoint.sh:ro", + ] + docker_entrypoint = "/entrypoint/entrypoint.sh" } # Create zip-archive which contains content of directory with commands and patterns applied. @@ -104,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 = [ @@ -123,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" { @@ -130,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) | >= 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](#module\_lambda) | ../../ | n/a | +| [s3\_bucket](#module\_s3\_bucket) | terraform-aws-modules/s3-bucket/aws | ~> 5.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_lambda_code_signing_config.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_code_signing_config) | resource | +| [aws_s3_object.unsigned](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_object) | resource | +| [aws_signer_signing_job.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/signer_signing_job) | resource | +| [aws_signer_signing_profile.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/signer_signing_profile) | resource | +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [lambda\_function\_arn](#output\_lambda\_function\_arn) | The ARN of the Lambda Function | +| [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 new file mode 100644 index 00000000..8b8e9e3e --- /dev/null +++ b/examples/code-signing/main.tf @@ -0,0 +1,118 @@ +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 +} + +################################################################################ +# Lambda Function +################################################################################ + +module "lambda" { + source = "../../" + + function_name = random_pet.this.id + handler = "index.lambda_handler" + runtime = "python3.12" + code_signing_config_arn = aws_lambda_code_signing_config.this.arn + create_package = false + + s3_existing_package = { + bucket = aws_signer_signing_job.this.signed_object[0].s3[0].bucket + key = aws_signer_signing_job.this.signed_object[0].s3[0].key + } +} + +################################################################################ +# Lambda Code Signing +################################################################################ + +resource "aws_s3_object" "unsigned" { + bucket = module.s3_bucket.s3_bucket_id + key = "unsigned/existing_package.zip" + source = "${path.module}/../fixtures/python-zip/existing_package.zip" + + # Making sure that S3 versioning configuration is propagated properly + depends_on = [ + module.s3_bucket + ] +} + +resource "aws_signer_signing_profile" "this" { + platform_id = "AWSLambda-SHA384-ECDSA" + # invalid value for name (must be alphanumeric with max length of 64 characters) + name = replace(random_pet.this.id, "-", "") + + signature_validity_period { + value = 3 + type = "MONTHS" + } +} + +resource "aws_signer_signing_job" "this" { + profile_name = aws_signer_signing_profile.this.name + + source { + s3 { + bucket = module.s3_bucket.s3_bucket_id + key = aws_s3_object.unsigned.id + version = aws_s3_object.unsigned.version_id + } + } + + destination { + s3 { + bucket = module.s3_bucket.s3_bucket_id + prefix = "signed/" + } + } + + ignore_signing_job_failure = true +} + +resource "aws_lambda_code_signing_config" "this" { + allowed_publishers { + signing_profile_version_arns = [aws_signer_signing_profile.this.version_arn] + } + + policies { + untrusted_artifact_on_deployment = "Enforce" + } +} + +################################################################################ +# Supporting Resources +################################################################################ + +resource "random_pet" "this" { + length = 2 +} + +module "s3_bucket" { + source = "terraform-aws-modules/s3-bucket/aws" + version = "~> 5.0" + + bucket_prefix = "${random_pet.this.id}-" + force_destroy = true + + # S3 bucket-level Public Access Block configuration + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true + + versioning = { + enabled = true + } + + server_side_encryption_configuration = { + rule = { + apply_server_side_encryption_by_default = { + sse_algorithm = "AES256" + } + } + } +} diff --git a/examples/code-signing/outputs.tf b/examples/code-signing/outputs.tf new file mode 100644 index 00000000..de42ca30 --- /dev/null +++ b/examples/code-signing/outputs.tf @@ -0,0 +1,19 @@ +output "lambda_function_signing_job_arn" { + description = "ARN of the signing job" + value = module.lambda.lambda_function_signing_job_arn +} + +output "lambda_function_signing_profile_version_arn" { + description = "ARN of the signing profile version" + value = module.lambda.lambda_function_signing_profile_version_arn +} + +output "lambda_function_arn" { + description = "The ARN of the Lambda Function" + value = module.lambda.lambda_function_arn +} + +output "lambda_function_invoke_arn" { + description = "The Invoke ARN of the Lambda Function" + value = module.lambda.lambda_function_invoke_arn +} diff --git a/examples/code-signing/variables.tf b/examples/code-signing/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/code-signing/versions.tf b/examples/code-signing/versions.tf new file mode 100644 index 00000000..d2f4f3e8 --- /dev/null +++ b/examples/code-signing/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/complete/README.md b/examples/complete/README.md index 0eec3449..552ea09c 100644 --- a/examples/complete/README.md +++ b/examples/complete/README.md @@ -15,44 +15,81 @@ $ terraform apply Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. - + ## Requirements -No requirements. +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | +| [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| aws | n/a | -| random | n/a | +| [aws](#provider\_aws) | >= 6.0 | +| [random](#provider\_random) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [disabled\_lambda](#module\_disabled\_lambda) | ../../ | n/a | +| [lambda\_at\_edge](#module\_lambda\_at\_edge) | ../../ | n/a | +| [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 | ~> 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 -No input. +No inputs. ## Outputs | Name | Description | |------|-------------| -| lambda\_cloudwatch\_log\_group\_arn | The ARN of the Cloudwatch Log Group | -| lambda\_role\_arn | The ARN of the IAM role created for the Lambda Function | -| lambda\_role\_name | The name of the IAM role created for the Lambda Function | -| local\_filename | The filename of zip archive deployed (if deployment was from local) | -| s3\_object | The map with S3 object data of zip archive deployed (if deployment was from S3) | -| this\_lambda\_function\_arn | The ARN of the Lambda Function | -| this\_lambda\_function\_invoke\_arn | The Invoke ARN of the Lambda Function | -| this\_lambda\_function\_kms\_key\_arn | The ARN for the KMS encryption key of Lambda Function | -| this\_lambda\_function\_last\_modified | The date Lambda Function resource was last modified | -| this\_lambda\_function\_name | The name of the Lambda Function | -| this\_lambda\_function\_qualified\_arn | The ARN identifying your Lambda Function Version | -| this\_lambda\_function\_source\_code\_hash | Base64-encoded representation of raw SHA-256 sum of the zip file | -| this\_lambda\_function\_source\_code\_size | The size in bytes of the function .zip file | -| this\_lambda\_function\_version | Latest published version of Lambda Function | -| this\_lambda\_layer\_arn | The ARN of the Lambda Layer with version | -| this\_lambda\_layer\_created\_date | The date Lambda Layer resource was created | -| this\_lambda\_layer\_layer\_arn | The ARN of the Lambda Layer without version | -| this\_lambda\_layer\_source\_code\_size | The size in bytes of the Lambda Layer .zip file | -| this\_lambda\_layer\_version | The Lambda Layer version | - - +| [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 | +| [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\_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 | +| [lambda\_function\_url\_id](#output\_lambda\_function\_url\_id) | The Lambda Function URL generated id | +| [lambda\_function\_version](#output\_lambda\_function\_version) | Latest published version of Lambda Function | +| [lambda\_layer\_arn](#output\_lambda\_layer\_arn) | The ARN of the Lambda Layer with version | +| [lambda\_layer\_created\_date](#output\_lambda\_layer\_created\_date) | The date Lambda Layer resource was created | +| [lambda\_layer\_layer\_arn](#output\_lambda\_layer\_layer\_arn) | The ARN of the Lambda Layer without version | +| [lambda\_layer\_source\_code\_size](#output\_lambda\_layer\_source\_code\_size) | The size in bytes of the Lambda Layer .zip file | +| [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 | +| [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 580566ea..356d9f3e 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -2,13 +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) @@ -17,20 +19,32 @@ provider "aws" { module "lambda_function" { source = "../../" - function_name = "${random_pet.this.id}-lambda1" - description = "My awesome lambda function" - handler = "index.lambda_handler" - runtime = "python3.8" - publish = true + function_name = "${random_pet.this.id}-lambda1" + description = "My awesome lambda function" + handler = "index.lambda_handler" + 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.this_s3_bucket_id + 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 = [ - module.lambda_layer_local.this_lambda_layer_arn, - module.lambda_layer_s3.this_lambda_layer_arn, + module.lambda_layer_local.lambda_layer_arn, + module.lambda_layer_s3.lambda_layer_arn, ] environment_variables = { @@ -38,49 +52,112 @@ module "lambda_function" { Serverless = "Terraform" } + 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" - arn = "arn:aws:execute-api:eu-west-1:135367859851:aqnku8akd0" + service = "apigateway" + source_arn = "arn:aws:execute-api:eu-west-1:${data.aws_caller_identity.current.account_id}:aqnku8akd0/*/*/*" }, APIGatewayDevPost = { service = "apigateway" - source_arn = "arn:aws:execute-api:eu-west-1:135367859851:aqnku8akd0/dev/POST/*" + source_arn = "arn:aws:execute-api:eu-west-1:${data.aws_caller_identity.current.account_id}:aqnku8akd0/dev/POST/*" }, OneRule = { principal = "events.amazonaws.com" - source_arn = "arn:aws:events:eu-west-1:135367859851:rule/RunDaily" + source_arn = "arn:aws:events:eu-west-1:${data.aws_caller_identity.current.account_id}:rule/RunDaily" } } + ###################### + # Lambda Function URL + ###################### + create_lambda_function_url = true + authorization_type = "AWS_IAM" + cors = { + allow_credentials = true + allow_origins = ["*"] + allow_methods = ["*"] + allow_headers = ["date", "keep-alive"] + expose_headers = ["keep-alive", "date"] + max_age = 86400 + } + invoke_mode = "RESPONSE_STREAM" + ###################### # Additional policies ###################### - attach_policy_json = true - policy_json = < +## Requirements + +| Name | Version | +|------|---------| +| [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) | >= 6.0 | +| [random](#provider\_random) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [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 + +| Name | Type | +|------|------| +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | +| [aws_caller_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_ecr_authorization_token.token](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ecr_authorization_token) | data source | +| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | + +## Inputs + +No inputs. + +## Outputs + +| 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 | +| [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 | +| [lambda\_layer\_arn](#output\_lambda\_layer\_arn) | The ARN of the Lambda Layer with version | +| [lambda\_layer\_created\_date](#output\_lambda\_layer\_created\_date) | The date Lambda Layer resource was created | +| [lambda\_layer\_layer\_arn](#output\_lambda\_layer\_layer\_arn) | The ARN of the Lambda Layer without version | +| [lambda\_layer\_source\_code\_size](#output\_lambda\_layer\_source\_code\_size) | The size in bytes of the Lambda Layer .zip file | +| [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 new file mode 100644 index 00000000..64350ec5 --- /dev/null +++ b/examples/container-image/context/Dockerfile @@ -0,0 +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 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/context/empty b/examples/container-image/context/empty new file mode 100644 index 00000000..3f99b56c --- /dev/null +++ b/examples/container-image/context/empty @@ -0,0 +1 @@ +# empty file :) diff --git a/examples/container-image/main.tf b/examples/container-image/main.tf new file mode 100644 index 00000000..9e0ee33d --- /dev/null +++ b/examples/container-image/main.tf @@ -0,0 +1,176 @@ +data "aws_region" "current" {} + +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_metadata_api_check = true + skip_region_validation = true + skip_credentials_validation = true +} + +provider "docker" { + registry_auth { + 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_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-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 + ################## + package_type = "Image" + architectures = ["arm64"] # ["x86_64"] + + image_uri = module.docker_build_from_ecr.image_uri +} + +module "docker_build" { + source = "../../modules/docker-build" + + create_ecr_repo = true + ecr_repo = random_pet.this.id + ecr_repo_lifecycle_policy = jsonencode({ + "rules" : [ + { + "rulePriority" : 1, + "description" : "Keep only the last 2 images", + "selection" : { + "tagStatus" : "any", + "countType" : "imageCountMoreThan", + "countNumber" : 2 + }, + "action" : { + "type" : "expire" + } + } + ] + }) + + 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" { + length = 2 +} diff --git a/examples/container-image/outputs.tf b/examples/container-image/outputs.tf new file mode 100644 index 00000000..7e6f0d2c --- /dev/null +++ b/examples/container-image/outputs.tf @@ -0,0 +1,109 @@ +# Lambda Function +output "lambda_function_arn" { + description = "The ARN of the Lambda Function" + 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_with_docker_build.lambda_function_arn_static +} + +output "lambda_function_invoke_arn" { + description = "The Invoke ARN of the Lambda Function" + 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_with_docker_build.lambda_function_name +} + +output "lambda_function_qualified_arn" { + description = "The ARN identifying your Lambda Function Version" + 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_with_docker_build.lambda_function_version +} + +output "lambda_function_last_modified" { + description = "The date Lambda Function resource was 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_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_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_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_with_docker_build.lambda_layer_arn +} + +output "lambda_layer_layer_arn" { + description = "The ARN of the Lambda Layer without version" + 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_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_with_docker_build.lambda_layer_source_code_size +} + +output "lambda_layer_version" { + description = "The 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_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_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_with_docker_build.lambda_cloudwatch_log_group_arn +} + +# Docker Image by modules/docker-build +output "docker_image_uri" { + description = "The ECR Docker image URI used to deploy Lambda Function" + 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/variables.tf b/examples/container-image/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/container-image/versions.tf b/examples/container-image/versions.tf new file mode 100644 index 00000000..dbb8009c --- /dev/null +++ b/examples/container-image/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.0" + } + random = { + source = "hashicorp/random" + version = ">= 2.0" + } + } +} diff --git a/examples/deploy/README.md b/examples/deploy/README.md index 3e277ff8..829a0468 100644 --- a/examples/deploy/README.md +++ b/examples/deploy/README.md @@ -14,34 +14,53 @@ $ terraform apply Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. - + ## Requirements -No requirements. +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | +| [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| aws | n/a | -| random | n/a | +| [aws](#provider\_aws) | >= 6.0 | +| [random](#provider\_random) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [alias\_refresh](#module\_alias\_refresh) | ../../modules/alias | n/a | +| [deploy](#module\_deploy) | ../../modules/deploy | n/a | +| [lambda\_function](#module\_lambda\_function) | ../../ | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_sns_topic.sns1](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic) | resource | +| [aws_sns_topic.sns2](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic) | resource | +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | ## Inputs -No input. +No inputs. ## Outputs | Name | Description | |------|-------------| -| appspec | n/a | -| appspec\_content | n/a | -| appspec\_sha256 | n/a | -| codedeploy\_app\_name | Name of CodeDeploy application | -| codedeploy\_deployment\_group\_id | CodeDeploy deployment group id | -| codedeploy\_deployment\_group\_name | CodeDeploy deployment group name | -| codedeploy\_iam\_role\_name | Name of IAM role used by CodeDeploy | -| deploy\_script | n/a | -| script | n/a | - - +| [appspec](#output\_appspec) | Appspec data as HCL | +| [appspec\_content](#output\_appspec\_content) | Appspec data as valid JSON | +| [appspec\_sha256](#output\_appspec\_sha256) | SHA256 of Appspec JSON | +| [codedeploy\_app\_name](#output\_codedeploy\_app\_name) | Name of CodeDeploy application | +| [codedeploy\_deployment\_group\_id](#output\_codedeploy\_deployment\_group\_id) | CodeDeploy deployment group id | +| [codedeploy\_deployment\_group\_name](#output\_codedeploy\_deployment\_group\_name) | CodeDeploy deployment group name | +| [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 c236b272..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,18 +16,11 @@ 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" - - allowed_triggers = { - APIGatewayAny = { - service = "apigateway" - arn = "arn:aws:execute-api:eu-west-1:135367859851:aqnku8akd0" - } - } } module "alias_refresh" { @@ -39,20 +30,20 @@ module "alias_refresh" { name = "current-with-refresh" - function_name = module.lambda_function.this_lambda_function_name + function_name = module.lambda_function.lambda_function_name # Set function_version when creating alias to be able to deploy using it, # because AWS CodeDeploy doesn't understand $LATEST as CurrentVersion. - function_version = module.lambda_function.this_lambda_function_version + function_version = module.lambda_function.lambda_function_version } module "deploy" { source = "../../modules/deploy" - alias_name = module.alias_refresh.this_lambda_alias_name - function_name = module.lambda_function.this_lambda_function_name + alias_name = module.alias_refresh.lambda_alias_name + function_name = module.lambda_function.lambda_function_name - target_version = module.lambda_function.this_lambda_function_version + target_version = module.lambda_function.lambda_function_version description = "This is my awesome deploy!" create_app = true @@ -62,6 +53,7 @@ module "deploy" { deployment_group_name = "something" create_deployment = true + run_deployment = true save_deploy_script = true wait_deployment_completion = true force_deploy = true diff --git a/examples/deploy/outputs.tf b/examples/deploy/outputs.tf index 8201adad..4e329b82 100644 --- a/examples/deploy/outputs.tf +++ b/examples/deploy/outputs.tf @@ -19,21 +19,26 @@ output "codedeploy_iam_role_name" { } output "appspec" { - value = module.deploy.appspec + description = "Appspec data as HCL" + value = module.deploy.appspec } output "appspec_content" { - value = module.deploy.appspec_content + description = "Appspec data as valid JSON" + value = module.deploy.appspec_content } output "appspec_sha256" { - value = module.deploy.appspec_sha256 + description = "SHA256 of Appspec JSON" + value = module.deploy.appspec_sha256 } output "script" { - value = module.deploy.script + description = "Deployment script" + value = module.deploy.script } output "deploy_script" { - value = module.deploy.deploy_script + description = "Path to a deployment script" + value = module.deploy.deploy_script } diff --git a/examples/deploy/variables.tf b/examples/deploy/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/deploy/versions.tf b/examples/deploy/versions.tf new file mode 100644 index 00000000..d2f4f3e8 --- /dev/null +++ b/examples/deploy/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/event-source-mapping/README.md b/examples/event-source-mapping/README.md new file mode 100644 index 00000000..49d4fa75 --- /dev/null +++ b/examples/event-source-mapping/README.md @@ -0,0 +1,79 @@ +# Event Source Mapping configuration + +Configuration in this directory creates Lambda Function with event source mapping configuration for SQS queue, Kinesis stream, Amazon MQ, and DynamoDB table. + +## 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 | +| [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 new file mode 100644 index 00000000..f76d30c8 --- /dev/null +++ b/examples/event-source-mapping/main.tf @@ -0,0 +1,279 @@ +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 +} + +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) +} + +#################################################### +# Lambda Function with event source mapping +#################################################### + +module "lambda_function" { + source = "../../" + + function_name = "${random_pet.this.id}-lambda-event-source-mapping" + handler = "index.lambda_handler" + runtime = "python3.12" + + 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"] + }) + }, + { + pattern = jsonencode({ + data : { + Temperature : [{ numeric : [">", 0, "<=", 100] }] + Location : ["Oslo"] + } + }) + } + ] + } + kinesis = { + event_source_arn = aws_kinesis_stream.this.arn + starting_position = "LATEST" + filter_criteria = { + pattern = jsonencode({ + data : { + Temperature : [{ numeric : [">", 0, "<=", 100] }] + Location : ["Oslo"] + } + }) + } + } + mq = { + event_source_arn = aws_mq_broker.this.arn + queues = ["my-queue"] + source_access_configuration = [ + { + type = "BASIC_AUTH" + uri = aws_secretsmanager_secret.this.arn + }, + { + type = "VIRTUAL_HOST" + uri = "/" + } + ] + tags = { mapping = "amq" } + } + # self_managed_kafka = { + # batch_size = 1 + # starting_position = "TRIM_HORIZON" + # topics = ["topic1", "topic2"] + # self_managed_event_source = [ + # { + # endpoints = { + # KAFKA_BOOTSTRAP_SERVERS = "kafka1.example.com:9092,kafka2.example.com:9092" + # } + # } + # ] + # self_managed_kafka_event_source_config = [ + # { + # consumer_group_id = "example-consumer-group" + # } + # ] + # source_access_configuration = [ + # { + # type = "SASL_SCRAM_512_AUTH", + # uri = "SECRET_AUTH_INFO" + # }, + # { + # type = "VPC_SECURITY_GROUP", + # uri = "security_group:sg-12345678" + # }, + # { + # type = "VPC_SUBNET" + # uri = "subnet:subnet-12345678" + # } + # ] + # } + } + + 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 + } + dynamodb = { + principal = "dynamodb.amazonaws.com" + source_arn = aws_dynamodb_table.this.stream_arn + } + kinesis = { + principal = "kinesis.amazonaws.com" + source_arn = aws_kinesis_stream.this.arn + } + mq = { + principal = "mq.amazonaws.com" + source_arn = aws_mq_broker.this.arn + } + } + + create_current_version_allowed_triggers = false + + attach_network_policy = true + + attach_policy_statements = true + policy_statements = { + # Allow failures to be sent to SQS queue + sqs_failure = { + effect = "Allow", + actions = ["sqs:SendMessage"], + resources = [aws_sqs_queue.failure.arn] + }, + # Execution role permissions to read records from an Amazon MQ broker + # https://docs.aws.amazon.com/lambda/latest/dg/with-mq.html#events-mq-permissions + mq_event_source = { + effect = "Allow", + actions = ["ec2:DescribeSubnets", "ec2:DescribeSecurityGroups", "ec2:DescribeVpcs"], + resources = ["*"] + }, + mq_describe_broker = { + effect = "Allow", + actions = ["mq:DescribeBroker"], + resources = [aws_mq_broker.this.arn] + }, + secrets_manager_get_value = { + effect = "Allow", + actions = ["secretsmanager:GetSecretValue"], + resources = [aws_secretsmanager_secret.this.arn] + } + } + + attach_policies = true + number_of_policies = 3 + + policies = [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole", + "arn:aws:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole", + "arn:aws:iam::aws:policy/service-role/AWSLambdaKinesisExecutionRole", + ] + + tags = { + example = "event-source-mapping" + } +} + +################## +# Extra resources +################## + +# Shared resources +resource "random_pet" "this" { + length = 2 +} + +resource "random_password" "this" { + length = 40 + special = false +} + +# SQS +resource "aws_sqs_queue" "this" { + name = random_pet.this.id +} + +resource "aws_sqs_queue" "failure" { + name = "${random_pet.this.id}-failure" +} + +# DynamoDB +resource "aws_dynamodb_table" "this" { + name = random_pet.this.id + billing_mode = "PAY_PER_REQUEST" + hash_key = "UserId" + range_key = "GameTitle" + stream_view_type = "NEW_AND_OLD_IMAGES" + stream_enabled = true + + attribute { + name = "UserId" + type = "S" + } + + attribute { + name = "GameTitle" + type = "S" + } +} + +# Kinesis +resource "aws_kinesis_stream" "this" { + name = random_pet.this.id + shard_count = 1 +} + +# Amazon MQ +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)] + + enable_nat_gateway = false +} + +resource "aws_mq_broker" "this" { + broker_name = random_pet.this.id + engine_type = "RabbitMQ" + engine_version = "3.12.13" + host_instance_type = "mq.t3.micro" + security_groups = [module.vpc.default_security_group_id] + subnet_ids = slice(module.vpc.public_subnets, 0, 1) + + user { + username = random_pet.this.id + password = random_password.this.result + } +} + +resource "aws_secretsmanager_secret" "this" { + name = "${random_pet.this.id}-mq-credentials" +} + +resource "aws_secretsmanager_secret_version" "this" { + secret_id = aws_secretsmanager_secret.this.id + secret_string = jsonencode({ + username = random_pet.this.id + password = random_password.this.result + }) +} diff --git a/examples/event-source-mapping/outputs.tf b/examples/event-source-mapping/outputs.tf new file mode 100644 index 00000000..764a91c5 --- /dev/null +++ b/examples/event-source-mapping/outputs.tf @@ -0,0 +1,76 @@ +# Lambda Function +output "lambda_function_arn" { + description = "The ARN of the Lambda Function" + value = module.lambda_function.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.lambda_function_arn_static +} + +output "lambda_function_invoke_arn" { + description = "The Invoke ARN of the Lambda Function" + value = module.lambda_function.lambda_function_invoke_arn +} + +output "lambda_function_name" { + description = "The name of the Lambda Function" + value = module.lambda_function.lambda_function_name +} + +output "lambda_function_qualified_arn" { + description = "The ARN identifying your Lambda Function Version" + value = module.lambda_function.lambda_function_qualified_arn +} + +output "lambda_function_version" { + description = "Latest published version of Lambda Function" + value = module.lambda_function.lambda_function_version +} + +output "lambda_function_last_modified" { + description = "The date Lambda Function resource was last modified" + value = module.lambda_function.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.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.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.lambda_function_source_code_size +} + +# Lambda Event Source Mapping +output "lambda_event_source_mapping_function_arn" { + description = "The the ARN of the Lambda function the event source mapping is sending events to" + value = module.lambda_function.lambda_event_source_mapping_function_arn +} + +output "lambda_event_source_mapping_state" { + description = "The state of the event source mapping" + value = module.lambda_function.lambda_event_source_mapping_state +} + +output "lambda_event_source_mapping_state_transition_reason" { + description = "The reason the event source mapping is in its current state" + value = module.lambda_function.lambda_event_source_mapping_state_transition_reason +} + +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/variables.tf b/examples/event-source-mapping/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/event-source-mapping/versions.tf b/examples/event-source-mapping/versions.tf new file mode 100644 index 00000000..d2f4f3e8 --- /dev/null +++ b/examples/event-source-mapping/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/fixtures/nodejs14.x-app1/index.js b/examples/fixtures/nodejs14.x-app1/index.js new file mode 100644 index 00000000..97968e4a --- /dev/null +++ b/examples/fixtures/nodejs14.x-app1/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-app1/package.json b/examples/fixtures/nodejs14.x-app1/package.json new file mode 100644 index 00000000..89c23f36 --- /dev/null +++ b/examples/fixtures/nodejs14.x-app1/package.json @@ -0,0 +1,8 @@ +{ + "name": "nodejs14.x-app1", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "requests": "^0.3.0" + } +} 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/python-app-poetry/docker/Dockerfile b/examples/fixtures/python-app-poetry/docker/Dockerfile new file mode 100644 index 00000000..9b6c9ed4 --- /dev/null +++ b/examples/fixtures/python-app-poetry/docker/Dockerfile @@ -0,0 +1,6 @@ +FROM public.ecr.aws/sam/build-python3.12 + +LABEL maintainer="Betajob AS" \ + description="Patched AWS Lambda build container" + +RUN pip install poetry==1.2.2 diff --git a/examples/fixtures/python3.8-app1/ignore_please.txt b/examples/fixtures/python-app-poetry/ignore_please.txt similarity index 97% rename from examples/fixtures/python3.8-app1/ignore_please.txt rename to examples/fixtures/python-app-poetry/ignore_please.txt index a7faedbc..30a2f668 100644 --- a/examples/fixtures/python3.8-app1/ignore_please.txt +++ b/examples/fixtures/python-app-poetry/ignore_please.txt @@ -1,2 +1 @@ This file should not be included in archive. - 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/python-app-poetry/poetry.lock b/examples/fixtures/python-app-poetry/poetry.lock new file mode 100644 index 00000000..a8da85ae --- /dev/null +++ b/examples/fixtures/python-app-poetry/poetry.lock @@ -0,0 +1,33 @@ +[[package]] +name = "colorama" +version = "0.4.5" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "colorful" +version = "0.5.4" +description = "Terminal string styling done right, in Python." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[metadata] +lock-version = "1.1" +python-versions = "^3.7" +content-hash = "31bbdf3fc3c5e491c372a8ac467cee0ca3dc43d344b42059cab09342e5c715c1" + +[metadata.files] +colorama = [ + {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, + {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, +] +colorful = [ + {file = "colorful-0.5.4-py2.py3-none-any.whl", hash = "sha256:8d264b52a39aae4c0ba3e2a46afbaec81b0559a99be0d2cfe2aba4cf94531348"}, + {file = "colorful-0.5.4.tar.gz", hash = "sha256:86848ad4e2eda60cd2519d8698945d22f6f6551e23e95f3f14dfbb60997807ea"}, +] diff --git a/examples/fixtures/python-app-poetry/poetry.toml b/examples/fixtures/python-app-poetry/poetry.toml new file mode 100644 index 00000000..e69de29b diff --git a/examples/fixtures/python-app-poetry/pyproject.toml b/examples/fixtures/python-app-poetry/pyproject.toml new file mode 100644 index 00000000..ea289d4a --- /dev/null +++ b/examples/fixtures/python-app-poetry/pyproject.toml @@ -0,0 +1,15 @@ +[tool.poetry] +name = "python-app-poetry" +version = "0.1.0" +description = "" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "^3.7" +colorful = "^0.5.4" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" 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 2b7d1fde..fa0eb25f 100644 --- a/examples/fixtures/python3.8-app1/docker/Dockerfile +++ b/examples/fixtures/python-app1/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM lambci/lambda: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 lambci/lambda: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/python-app1/docker/entrypoint.sh b/examples/fixtures/python-app1/docker/entrypoint.sh new file mode 100755 index 00000000..1fc166c7 --- /dev/null +++ b/examples/fixtures/python-app1/docker/entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -e + +echo in entrypoint +echo I can read $MY_ENV_VAR and my volume: +ls -la /entrypoint + +echo "running command $@" +"$@" + +echo finished running entrypoint diff --git a/examples/fixtures/python-app1/ignore_please.txt b/examples/fixtures/python-app1/ignore_please.txt new file mode 100644 index 00000000..30a2f668 --- /dev/null +++ b/examples/fixtures/python-app1/ignore_please.txt @@ -0,0 +1 @@ +This file should not be included in archive. diff --git a/examples/fixtures/python-app1/index.py b/examples/fixtures/python-app1/index.py new file mode 100644 index 00000000..396c5054 --- /dev/null +++ b/examples/fixtures/python-app1/index.py @@ -0,0 +1,4 @@ +def lambda_handler(event, context): + print("Hello from app1!") + + return event 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/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/.gitignore b/examples/multiple-regions/.gitignore new file mode 100644 index 00000000..b205ba3d --- /dev/null +++ b/examples/multiple-regions/.gitignore @@ -0,0 +1 @@ +builds/* diff --git a/examples/multiple-regions/README.md b/examples/multiple-regions/README.md new file mode 100644 index 00000000..ed4c573a --- /dev/null +++ b/examples/multiple-regions/README.md @@ -0,0 +1,78 @@ +# AWS Lambda Functions in several regions + +Configuration in this directory creates AWS Lambda Functions in several regions with non-conflicting IAM roles and policies. + + +## 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 | +| [random](#requirement\_random) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 6.0 | +| [aws.us-east-1](#provider\_aws.us-east-1) | >= 6.0 | +| [random](#provider\_random) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [lambda\_function](#module\_lambda\_function) | ../../ | n/a | +| [lambda\_function\_another\_region](#module\_lambda\_function\_another\_region) | ../../ | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_sqs_queue.dlq](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource | +| [aws_sqs_queue.dlq_us_east_1](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 | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [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 | +| [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 | +| [lambda\_layer\_arn](#output\_lambda\_layer\_arn) | The ARN of the Lambda Layer with version | +| [lambda\_layer\_created\_date](#output\_lambda\_layer\_created\_date) | The date Lambda Layer resource was created | +| [lambda\_layer\_layer\_arn](#output\_lambda\_layer\_layer\_arn) | The ARN of the Lambda Layer without version | +| [lambda\_layer\_source\_code\_size](#output\_lambda\_layer\_source\_code\_size) | The size in bytes of the Lambda Layer .zip file | +| [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 | +| [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 new file mode 100644 index 00000000..d30e1c2a --- /dev/null +++ b/examples/multiple-regions/main.tf @@ -0,0 +1,219 @@ +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 +} + +provider "aws" { + region = "us-east-1" + alias = "us-east-1" + + # Make it faster by skipping something + skip_metadata_api_check = true + skip_region_validation = true + skip_credentials_validation = true +} + +################################ +# Lambda Function in one region +################################ + +module "lambda_function" { + source = "../../" + + function_name = "${random_pet.this.id}-lambda1" + description = "My awesome lambda function" + handler = "index.lambda_handler" + runtime = "python3.12" + publish = true + + source_path = "${path.module}/../fixtures/python-app1" + + attach_dead_letter_policy = true + dead_letter_target_arn = aws_sqs_queue.dlq.arn + + ###################### + # Additional policies + ###################### + + attach_policy_json = true + policy_json = < +## 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 15f0de78..b9a4f785 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -14,43 +14,59 @@ $ terraform apply Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. - + ## Requirements -No requirements. +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | +| [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| random | n/a | +| [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 input. +No inputs. ## Outputs | Name | Description | |------|-------------| -| lambda\_cloudwatch\_log\_group\_arn | The ARN of the Cloudwatch Log Group | -| lambda\_role\_arn | The ARN of the IAM role created for the Lambda Function | -| lambda\_role\_name | The name of the IAM role created for the Lambda Function | -| local\_filename | The filename of zip archive deployed (if deployment was from local) | -| s3\_object | The map with S3 object data of zip archive deployed (if deployment was from S3) | -| this\_lambda\_function\_arn | The ARN of the Lambda Function | -| this\_lambda\_function\_invoke\_arn | The Invoke ARN of the Lambda Function | -| this\_lambda\_function\_kms\_key\_arn | The ARN for the KMS encryption key of Lambda Function | -| this\_lambda\_function\_last\_modified | The date Lambda Function resource was last modified | -| this\_lambda\_function\_name | The name of the Lambda Function | -| this\_lambda\_function\_qualified\_arn | The ARN identifying your Lambda Function Version | -| this\_lambda\_function\_source\_code\_hash | Base64-encoded representation of raw SHA-256 sum of the zip file | -| this\_lambda\_function\_source\_code\_size | The size in bytes of the function .zip file | -| this\_lambda\_function\_version | Latest published version of Lambda Function | -| this\_lambda\_layer\_arn | The ARN of the Lambda Layer with version | -| this\_lambda\_layer\_created\_date | The date Lambda Layer resource was created | -| this\_lambda\_layer\_layer\_arn | The ARN of the Lambda Layer without version | -| this\_lambda\_layer\_source\_code\_size | The size in bytes of the Lambda Layer .zip file | -| this\_lambda\_layer\_version | The Lambda Layer version | - - +| [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 | +| [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 | +| [lambda\_layer\_arn](#output\_lambda\_layer\_arn) | The ARN of the Lambda Layer with version | +| [lambda\_layer\_created\_date](#output\_lambda\_layer\_created\_date) | The date Lambda Layer resource was created | +| [lambda\_layer\_layer\_arn](#output\_lambda\_layer\_layer\_arn) | The ARN of the Lambda Layer without version | +| [lambda\_layer\_source\_code\_size](#output\_lambda\_layer\_source\_code\_size) | The size in bytes of the Lambda Layer .zip file | +| [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 | +| [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 9286c83d..20c51910 100644 --- a/examples/simple/main.tf +++ b/examples/simple/main.tf @@ -1,35 +1,33 @@ provider "aws" { region = "eu-west-1" - // region = "us-east-1" + # 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" { length = 2 } -//module "lambda_at_edge" { -// source = "../../" -// -// function_name = "${random_pet.this.id}-lambda-edge" -// handler = "index.lambda_handler" -// runtime = "python3.8" -// lambda_at_edge = true -// -// attach_cloudwatch_logs_policy = true -// -// source_path = "${path.module}/../fixtures/python3.8-app1/" -//} +#module "lambda_at_edge" { +# source = "../../" +# +# function_name = "${random_pet.this.id}-lambda-edge" +# handler = "index.lambda_handler" +# runtime = "python3.12" +# lambda_at_edge = true +# +# attach_cloudwatch_logs_policy = true +# +# source_path = "${path.module}/../fixtures/python-app1/" +#} -//resource "aws_cloudwatch_log_group" "this" { -// name = "/aws/lambda/us-east-1.${random_pet.this.id}-lambda-simple" -//} +#resource "aws_cloudwatch_log_group" "this" { +# name = "/aws/lambda/us-east-1.${random_pet.this.id}-lambda-simple" +#} module "lambda_function" { source = "../../" @@ -38,285 +36,297 @@ module "lambda_function" { function_name = "${random_pet.this.id}-lambda-simple" handler = "index.lambda_handler" - runtime = "python3.8" + runtime = "python3.12" - // attach_cloudwatch_logs_policy = false + # role_maximum_session_duration = 7200 - // use_existing_cloudwatch_log_group = true + # attach_cloudwatch_logs_policy = false - // lambda_at_edge = true + # use_existing_cloudwatch_log_group = true - // independent_file_timestamps = true + # lambda_at_edge = true - // store_on_s3 = true - // s3_bucket = module.s3_bucket.this_s3_bucket_id + # independent_file_timestamps = true - // create_package = false - // local_existing_package = data.null_data_source.downloaded_package.outputs["filename"] + # store_on_s3 = true + # s3_bucket = module.s3_bucket.s3_bucket_id - // - // policy_json = < +## 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 | + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_event_rule.scan_ami](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_rule) | resource | +| [aws_cloudwatch_event_target.scan_ami_lambda_function](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_target) | resource | +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [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 | +| [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 | +| [lambda\_layer\_arn](#output\_lambda\_layer\_arn) | The ARN of the Lambda Layer with version | +| [lambda\_layer\_created\_date](#output\_lambda\_layer\_created\_date) | The date Lambda Layer resource was created | +| [lambda\_layer\_layer\_arn](#output\_lambda\_layer\_layer\_arn) | The ARN of the Lambda Layer without version | +| [lambda\_layer\_source\_code\_size](#output\_lambda\_layer\_source\_code\_size) | The size in bytes of the Lambda Layer .zip file | +| [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 | +| [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 new file mode 100644 index 00000000..8317a81a --- /dev/null +++ b/examples/triggers/main.tf @@ -0,0 +1,63 @@ +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 +} + +########################################## +# Lambda Function (with various triggers) +########################################## + +module "lambda_function" { + source = "../../" + + function_name = "${random_pet.this.id}-lambda-triggers" + description = "My awesome lambda function" + handler = "index.lambda_handler" + runtime = "python3.12" + publish = true + + create_package = false + local_existing_package = "${path.module}/../fixtures/python-zip/existing_package.zip" + + allowed_triggers = { + ScanAmiRule = { + principal = "events.amazonaws.com" + source_arn = aws_cloudwatch_event_rule.scan_ami.arn + } + } +} + +################## +# Extra resources +################## + +resource "random_pet" "this" { + length = 2 +} + +################################## +# Cloudwatch Events (EventBridge) +################################## +resource "aws_cloudwatch_event_rule" "scan_ami" { + name = "EC2CreateImageEvent" + description = "EC2 Create Image Event..." + event_pattern = < +## 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\_with\_efs](#module\_lambda\_function\_with\_efs) | ../../ | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_efs_access_point.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_access_point) | resource | +| [aws_efs_file_system.shared](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_file_system) | resource | +| [aws_efs_mount_target.alpha](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_mount_target) | resource | +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [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 | +| [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 | +| [lambda\_layer\_arn](#output\_lambda\_layer\_arn) | The ARN of the Lambda Layer with version | +| [lambda\_layer\_created\_date](#output\_lambda\_layer\_created\_date) | The date Lambda Layer resource was created | +| [lambda\_layer\_layer\_arn](#output\_lambda\_layer\_layer\_arn) | The ARN of the Lambda Layer without version | +| [lambda\_layer\_source\_code\_size](#output\_lambda\_layer\_source\_code\_size) | The size in bytes of the Lambda Layer .zip file | +| [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 | +| [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 new file mode 100644 index 00000000..90a0abed --- /dev/null +++ b/examples/with-efs/main.tf @@ -0,0 +1,84 @@ +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_with_efs" { + source = "../../" + + function_name = "${random_pet.this.id}-lambda-in-vpc" + description = "My awesome lambda function" + handler = "index.lambda_handler" + runtime = "python3.12" + + 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 + + ###################### + # Elastic File System + ###################### + + file_system_arn = aws_efs_access_point.lambda.arn + file_system_local_mount_path = "/mnt/shared-storage" + + # Explicitly declare dependency on EFS mount target. + # When creating or updating Lambda functions, mount target must be in 'available' lifecycle state. + # Note: depends_on on modules became available in Terraform 0.13 + depends_on = [aws_efs_mount_target.alpha] +} + +###### +# VPC +###### + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.0" + + name = random_pet.this.id + cidr = "10.10.0.0/16" + + azs = ["eu-west-1a"] + intra_subnets = ["10.10.101.0/24"] +} + +###### +# EFS +###### + +resource "aws_efs_file_system" "shared" {} + +resource "aws_efs_mount_target" "alpha" { + file_system_id = aws_efs_file_system.shared.id + subnet_id = module.vpc.intra_subnets[0] + security_groups = [module.vpc.default_security_group_id] +} + +resource "aws_efs_access_point" "lambda" { + file_system_id = aws_efs_file_system.shared.id + + posix_user { + gid = 1000 + uid = 1000 + } + + root_directory { + path = "/lambda" + creation_info { + owner_gid = 1000 + owner_uid = 1000 + permissions = "0777" + } + } +} diff --git a/examples/with-efs/outputs.tf b/examples/with-efs/outputs.tf new file mode 100644 index 00000000..9b554a5a --- /dev/null +++ b/examples/with-efs/outputs.tf @@ -0,0 +1,104 @@ +# Lambda Function +output "lambda_function_arn" { + description = "The ARN of the Lambda Function" + value = module.lambda_function_with_efs.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_with_efs.lambda_function_arn_static +} + +output "lambda_function_invoke_arn" { + description = "The Invoke ARN of the Lambda Function" + value = module.lambda_function_with_efs.lambda_function_invoke_arn +} + +output "lambda_function_name" { + description = "The name of the Lambda Function" + value = module.lambda_function_with_efs.lambda_function_name +} + +output "lambda_function_qualified_arn" { + description = "The ARN identifying your Lambda Function Version" + value = module.lambda_function_with_efs.lambda_function_qualified_arn +} + +output "lambda_function_version" { + description = "Latest published version of Lambda Function" + value = module.lambda_function_with_efs.lambda_function_version +} + +output "lambda_function_last_modified" { + description = "The date Lambda Function resource was last modified" + value = module.lambda_function_with_efs.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_with_efs.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_with_efs.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_with_efs.lambda_function_source_code_size +} + +# Lambda Layer +output "lambda_layer_arn" { + description = "The ARN of the Lambda Layer with version" + value = module.lambda_function_with_efs.lambda_layer_arn +} + +output "lambda_layer_layer_arn" { + description = "The ARN of the Lambda Layer without version" + value = module.lambda_function_with_efs.lambda_layer_layer_arn +} + +output "lambda_layer_created_date" { + description = "The date Lambda Layer resource was created" + value = module.lambda_function_with_efs.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_with_efs.lambda_layer_source_code_size +} + +output "lambda_layer_version" { + description = "The Lambda Layer version" + value = module.lambda_function_with_efs.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_with_efs.lambda_role_arn +} + +output "lambda_role_name" { + description = "The name of the IAM role created for the Lambda Function" + value = module.lambda_function_with_efs.lambda_role_name +} + +# CloudWatch Log Group +output "lambda_cloudwatch_log_group_arn" { + description = "The ARN of the Cloudwatch Log Group" + value = module.lambda_function_with_efs.lambda_cloudwatch_log_group_arn +} + +# Deployment package +output "local_filename" { + description = "The filename of zip archive deployed (if deployment was from local)" + value = module.lambda_function_with_efs.local_filename +} + +output "s3_object" { + description = "The map with S3 object data of zip archive deployed (if deployment was from S3)" + value = module.lambda_function_with_efs.s3_object +} diff --git a/examples/with-efs/variables.tf b/examples/with-efs/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/with-efs/versions.tf b/examples/with-efs/versions.tf new file mode 100644 index 00000000..d2f4f3e8 --- /dev/null +++ b/examples/with-efs/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/with-vpc-s3-endpoint/README.md b/examples/with-vpc-s3-endpoint/README.md new file mode 100644 index 00000000..f84ba32c --- /dev/null +++ b/examples/with-vpc-s3-endpoint/README.md @@ -0,0 +1,84 @@ +# AWS Lambda with VPC and VPC Endpoint for S3 example + +The configuration in this directory creates an AWS Lambda Function deployed within a VPC with a VPC Endpoint for S3 and no Internet access. The Function writes a single object to an S3 bucket that is created as part of the supporting resources. + +Be aware, that deletion of AWS Lambda with VPC can take a long time (e.g., 10 minutes). + +## 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 | +| [random](#requirement\_random) | >= 3.4 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 6.0 | +| [random](#provider\_random) | >= 3.4 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [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 | ~> 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 | ~> 5.0 | +| [vpc\_endpoints](#module\_vpc\_endpoints) | terraform-aws-modules/vpc/aws//modules/vpc-endpoints | ~> 5.0 | + +## Resources + +| Name | Type | +|------|------| +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | +| [aws_ec2_managed_prefix_list.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ec2_managed_prefix_list) | data source | +| [aws_iam_policy_document.bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.endpoint](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [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 | +| [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 | +| [lambda\_layer\_arn](#output\_lambda\_layer\_arn) | The ARN of the Lambda Layer with version | +| [lambda\_layer\_created\_date](#output\_lambda\_layer\_created\_date) | The date Lambda Layer resource was created | +| [lambda\_layer\_layer\_arn](#output\_lambda\_layer\_layer\_arn) | The ARN of the Lambda Layer without version | +| [lambda\_layer\_source\_code\_size](#output\_lambda\_layer\_source\_code\_size) | The size in bytes of the Lambda Layer .zip file | +| [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 | +| [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 new file mode 100644 index 00000000..29de6eba --- /dev/null +++ b/examples/with-vpc-s3-endpoint/main.tf @@ -0,0 +1,227 @@ +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 +} + +data "aws_region" "current" {} + +################################################################################ +# Lambda Module +################################################################################ + +module "lambda_s3_write" { + source = "../../" + + description = "Lambda demonstrating writes to an S3 bucket from within a VPC without Internet access" + + function_name = random_pet.this.id + handler = "index.lambda_handler" + runtime = "python3.12" + + source_path = "${path.module}/../fixtures/python-app2" + + environment_variables = { + BUCKET_NAME = module.s3_bucket.s3_bucket_id + REGION_NAME = data.aws_region.current.region + } + + # Let the module create a role for us + create_role = true + attach_cloudwatch_logs_policy = true + attach_network_policy = true + + # There's no need to attach any extra permission for S3 writes as that's added by the bucket policy when a session is created + # See https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html + + vpc_security_group_ids = [module.security_group_lambda.security_group_id] + vpc_subnet_ids = module.vpc.intra_subnets + + tags = { + Module = "lambda_s3_write" + } +} + +################################################################################ +# Extra Resources +################################################################################ + +resource "random_pet" "this" { + length = 2 +} + +data "aws_ec2_managed_prefix_list" "this" { + name = "com.amazonaws.${data.aws_region.current.region}.s3" +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.0" + + name = random_pet.this.id + cidr = "10.0.0.0/16" + + 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"] + + intra_dedicated_network_acl = true + intra_inbound_acl_rules = concat( + # NACL rule for local traffic + [ + { + rule_number = 100 + rule_action = "allow" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_block = "10.0.0.0/16" + }, + ], + # NACL rules for the response traffic from addresses in the AWS S3 prefix list + [for k, v in zipmap( + range(length(data.aws_ec2_managed_prefix_list.this.entries[*].cidr)), + data.aws_ec2_managed_prefix_list.this.entries[*].cidr + ) : + { + rule_number = 200 + k + rule_action = "allow" + from_port = 1024 + to_port = 65535 + protocol = "tcp" + cidr_block = v + } + ] + ) +} + +module "vpc_endpoints" { + source = "terraform-aws-modules/vpc/aws//modules/vpc-endpoints" + version = "~> 5.0" + + vpc_id = module.vpc.vpc_id + + endpoints = { + s3 = { + service = "s3" + service_type = "Gateway" + route_table_ids = module.vpc.intra_route_table_ids + policy = data.aws_iam_policy_document.endpoint.json + } + } +} + +data "aws_iam_policy_document" "endpoint" { + statement { + sid = "RestrictBucketAccessToIAMRole" + + principals { + type = "AWS" + identifiers = ["*"] + } + + actions = [ + "s3:PutObject", + ] + + resources = [ + "${module.s3_bucket.s3_bucket_arn}/*", + ] + + # See https://docs.aws.amazon.com/vpc/latest/privatelink/vpc-endpoints-s3.html#edit-vpc-endpoint-policy-s3 + condition { + test = "ArnEquals" + variable = "aws:PrincipalArn" + values = [module.lambda_s3_write.lambda_role_arn] + } + } +} + +module "kms" { + source = "terraform-aws-modules/kms/aws" + version = "~> 1.0" + + description = "S3 encryption key" + + # Grants + grants = { + lambda = { + grantee_principal = module.lambda_s3_write.lambda_role_arn + operations = [ + "GenerateDataKey", + ] + } + } +} + +module "s3_bucket" { + source = "terraform-aws-modules/s3-bucket/aws" + version = "~> 5.0" + + bucket_prefix = "${random_pet.this.id}-" + force_destroy = true + + # S3 bucket-level Public Access Block configuration + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true + + versioning = { + enabled = true + } + + # Bucket policy + attach_policy = true + policy = data.aws_iam_policy_document.bucket.json + + server_side_encryption_configuration = { + rule = { + apply_server_side_encryption_by_default = { + kms_master_key_id = module.kms.key_id + sse_algorithm = "aws:kms" + } + } + } +} + +data "aws_iam_policy_document" "bucket" { + statement { + sid = "RestrictBucketAccessToIAMRole" + + principals { + type = "AWS" + identifiers = [module.lambda_s3_write.lambda_role_arn] + } + + actions = [ + "s3:PutObject", + ] + + resources = [ + "${module.s3_bucket.s3_bucket_arn}/*", + ] + } +} + +module "security_group_lambda" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = random_pet.this.id + description = "Security Group for Lambda Egress" + + vpc_id = module.vpc.vpc_id + + egress_cidr_blocks = [] + egress_ipv6_cidr_blocks = [] + + # Prefix list ids to use in all egress rules in this module + egress_prefix_list_ids = [module.vpc_endpoints.endpoints["s3"]["prefix_list_id"]] + + egress_rules = ["https-443-tcp"] +} diff --git a/examples/with-vpc-s3-endpoint/outputs.tf b/examples/with-vpc-s3-endpoint/outputs.tf new file mode 100644 index 00000000..7218c63c --- /dev/null +++ b/examples/with-vpc-s3-endpoint/outputs.tf @@ -0,0 +1,104 @@ +# Lambda Function +output "lambda_function_arn" { + description = "The ARN of the Lambda Function" + value = module.lambda_s3_write.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_s3_write.lambda_function_arn_static +} + +output "lambda_function_invoke_arn" { + description = "The Invoke ARN of the Lambda Function" + value = module.lambda_s3_write.lambda_function_invoke_arn +} + +output "lambda_function_name" { + description = "The name of the Lambda Function" + value = module.lambda_s3_write.lambda_function_name +} + +output "lambda_function_qualified_arn" { + description = "The ARN identifying your Lambda Function Version" + value = module.lambda_s3_write.lambda_function_qualified_arn +} + +output "lambda_function_version" { + description = "Latest published version of Lambda Function" + value = module.lambda_s3_write.lambda_function_version +} + +output "lambda_function_last_modified" { + description = "The date Lambda Function resource was last modified" + value = module.lambda_s3_write.lambda_function_last_modified +} + +output "lambda_function_kms_key_arn" { + description = "The ARN for the KMS encryption key of Lambda Function" + value = module.lambda_s3_write.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_s3_write.lambda_function_source_code_hash +} + +output "lambda_function_source_code_size" { + description = "The size in bytes of the function .zip file" + value = module.lambda_s3_write.lambda_function_source_code_size +} + +# Lambda Layer +output "lambda_layer_arn" { + description = "The ARN of the Lambda Layer with version" + value = module.lambda_s3_write.lambda_layer_arn +} + +output "lambda_layer_layer_arn" { + description = "The ARN of the Lambda Layer without version" + value = module.lambda_s3_write.lambda_layer_layer_arn +} + +output "lambda_layer_created_date" { + description = "The date Lambda Layer resource was created" + value = module.lambda_s3_write.lambda_layer_created_date +} + +output "lambda_layer_source_code_size" { + description = "The size in bytes of the Lambda Layer .zip file" + value = module.lambda_s3_write.lambda_layer_source_code_size +} + +output "lambda_layer_version" { + description = "The Lambda Layer version" + value = module.lambda_s3_write.lambda_layer_version +} + +# IAM Role +output "lambda_role_arn" { + description = "The ARN of the IAM role created for the Lambda Function" + value = module.lambda_s3_write.lambda_role_arn +} + +output "lambda_role_name" { + description = "The name of the IAM role created for the Lambda Function" + value = module.lambda_s3_write.lambda_role_name +} + +# CloudWatch Log Group +output "lambda_cloudwatch_log_group_arn" { + description = "The ARN of the Cloudwatch Log Group" + value = module.lambda_s3_write.lambda_cloudwatch_log_group_arn +} + +# Deployment package +output "local_filename" { + description = "The filename of zip archive deployed (if deployment was from local)" + value = module.lambda_s3_write.local_filename +} + +output "s3_object" { + description = "The map with S3 object data of zip archive deployed (if deployment was from S3)" + value = module.lambda_s3_write.s3_object +} diff --git a/examples/with-vpc-s3-endpoint/variables.tf b/examples/with-vpc-s3-endpoint/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/with-vpc-s3-endpoint/versions.tf b/examples/with-vpc-s3-endpoint/versions.tf new file mode 100644 index 00000000..7f27783c --- /dev/null +++ b/examples/with-vpc-s3-endpoint/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 = ">= 3.4" + } + } +} diff --git a/examples/with-vpc/README.md b/examples/with-vpc/README.md index a580d31b..e1808811 100644 --- a/examples/with-vpc/README.md +++ b/examples/with-vpc/README.md @@ -16,43 +16,60 @@ $ terraform apply Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. - + ## Requirements -No requirements. +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | +| [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| random | n/a | +| [random](#provider\_random) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [lambda\_function\_in\_vpc](#module\_lambda\_function\_in\_vpc) | ../../ | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | + +## Resources + +| Name | Type | +|------|------| +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | ## Inputs -No input. +No inputs. ## Outputs | Name | Description | |------|-------------| -| lambda\_cloudwatch\_log\_group\_arn | The ARN of the Cloudwatch Log Group | -| lambda\_role\_arn | The ARN of the IAM role created for the Lambda Function | -| lambda\_role\_name | The name of the IAM role created for the Lambda Function | -| local\_filename | The filename of zip archive deployed (if deployment was from local) | -| s3\_object | The map with S3 object data of zip archive deployed (if deployment was from S3) | -| this\_lambda\_function\_arn | The ARN of the Lambda Function | -| this\_lambda\_function\_invoke\_arn | The Invoke ARN of the Lambda Function | -| this\_lambda\_function\_kms\_key\_arn | The ARN for the KMS encryption key of Lambda Function | -| this\_lambda\_function\_last\_modified | The date Lambda Function resource was last modified | -| this\_lambda\_function\_name | The name of the Lambda Function | -| this\_lambda\_function\_qualified\_arn | The ARN identifying your Lambda Function Version | -| this\_lambda\_function\_source\_code\_hash | Base64-encoded representation of raw SHA-256 sum of the zip file | -| this\_lambda\_function\_source\_code\_size | The size in bytes of the function .zip file | -| this\_lambda\_function\_version | Latest published version of Lambda Function | -| this\_lambda\_layer\_arn | The ARN of the Lambda Layer with version | -| this\_lambda\_layer\_created\_date | The date Lambda Layer resource was created | -| this\_lambda\_layer\_layer\_arn | The ARN of the Lambda Layer without version | -| this\_lambda\_layer\_source\_code\_size | The size in bytes of the Lambda Layer .zip file | -| this\_lambda\_layer\_version | The Lambda Layer version | - - +| [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 | +| [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 | +| [lambda\_layer\_arn](#output\_lambda\_layer\_arn) | The ARN of the Lambda Layer with version | +| [lambda\_layer\_created\_date](#output\_lambda\_layer\_created\_date) | The date Lambda Layer resource was created | +| [lambda\_layer\_layer\_arn](#output\_lambda\_layer\_layer\_arn) | The ARN of the Lambda Layer without version | +| [lambda\_layer\_source\_code\_size](#output\_lambda\_layer\_source\_code\_size) | The size in bytes of the Lambda Layer .zip file | +| [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 | +| [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/outputs.tf b/examples/with-vpc/outputs.tf index a505c0de..546b0192 100644 --- a/examples/with-vpc/outputs.tf +++ b/examples/with-vpc/outputs.tf @@ -1,73 +1,78 @@ # Lambda Function -output "this_lambda_function_arn" { +output "lambda_function_arn" { description = "The ARN of the Lambda Function" - value = module.lambda_function_in_vpc.this_lambda_function_arn + value = module.lambda_function_in_vpc.lambda_function_arn } -output "this_lambda_function_invoke_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_in_vpc.lambda_function_arn_static +} + +output "lambda_function_invoke_arn" { description = "The Invoke ARN of the Lambda Function" - value = module.lambda_function_in_vpc.this_lambda_function_invoke_arn + value = module.lambda_function_in_vpc.lambda_function_invoke_arn } -output "this_lambda_function_name" { +output "lambda_function_name" { description = "The name of the Lambda Function" - value = module.lambda_function_in_vpc.this_lambda_function_name + value = module.lambda_function_in_vpc.lambda_function_name } -output "this_lambda_function_qualified_arn" { +output "lambda_function_qualified_arn" { description = "The ARN identifying your Lambda Function Version" - value = module.lambda_function_in_vpc.this_lambda_function_qualified_arn + value = module.lambda_function_in_vpc.lambda_function_qualified_arn } -output "this_lambda_function_version" { +output "lambda_function_version" { description = "Latest published version of Lambda Function" - value = module.lambda_function_in_vpc.this_lambda_function_version + value = module.lambda_function_in_vpc.lambda_function_version } -output "this_lambda_function_last_modified" { +output "lambda_function_last_modified" { description = "The date Lambda Function resource was last modified" - value = module.lambda_function_in_vpc.this_lambda_function_last_modified + value = module.lambda_function_in_vpc.lambda_function_last_modified } -output "this_lambda_function_kms_key_arn" { +output "lambda_function_kms_key_arn" { description = "The ARN for the KMS encryption key of Lambda Function" - value = module.lambda_function_in_vpc.this_lambda_function_kms_key_arn + value = module.lambda_function_in_vpc.lambda_function_kms_key_arn } -output "this_lambda_function_source_code_hash" { +output "lambda_function_source_code_hash" { description = "Base64-encoded representation of raw SHA-256 sum of the zip file" - value = module.lambda_function_in_vpc.this_lambda_function_source_code_hash + value = module.lambda_function_in_vpc.lambda_function_source_code_hash } -output "this_lambda_function_source_code_size" { +output "lambda_function_source_code_size" { description = "The size in bytes of the function .zip file" - value = module.lambda_function_in_vpc.this_lambda_function_source_code_size + value = module.lambda_function_in_vpc.lambda_function_source_code_size } # Lambda Layer -output "this_lambda_layer_arn" { +output "lambda_layer_arn" { description = "The ARN of the Lambda Layer with version" - value = module.lambda_function_in_vpc.this_lambda_layer_arn + value = module.lambda_function_in_vpc.lambda_layer_arn } -output "this_lambda_layer_layer_arn" { +output "lambda_layer_layer_arn" { description = "The ARN of the Lambda Layer without version" - value = module.lambda_function_in_vpc.this_lambda_layer_layer_arn + value = module.lambda_function_in_vpc.lambda_layer_layer_arn } -output "this_lambda_layer_created_date" { +output "lambda_layer_created_date" { description = "The date Lambda Layer resource was created" - value = module.lambda_function_in_vpc.this_lambda_layer_created_date + value = module.lambda_function_in_vpc.lambda_layer_created_date } -output "this_lambda_layer_source_code_size" { +output "lambda_layer_source_code_size" { description = "The size in bytes of the Lambda Layer .zip file" - value = module.lambda_function_in_vpc.this_lambda_layer_source_code_size + value = module.lambda_function_in_vpc.lambda_layer_source_code_size } -output "this_lambda_layer_version" { +output "lambda_layer_version" { description = "The Lambda Layer version" - value = module.lambda_function_in_vpc.this_lambda_layer_version + value = module.lambda_function_in_vpc.lambda_layer_version } # IAM Role diff --git a/examples/with-vpc/variables.tf b/examples/with-vpc/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/with-vpc/versions.tf b/examples/with-vpc/versions.tf new file mode 100644 index 00000000..d2f4f3e8 --- /dev/null +++ b/examples/with-vpc/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/iam.tf b/iam.tf index 539bc9c2..8b0440e1 100644 --- a/iam.tf +++ b/iam.tf @@ -1,10 +1,32 @@ locals { - create_role = var.create && var.create_function && ! var.create_layer && var.create_role + create_role = local.create && var.create_function && !var.create_layer && var.create_role # Lambda@Edge uses the Cloudwatch region closest to the location where the function is executed # 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 = element(concat(data.aws_cloudwatch_log_group.lambda.*.arn, aws_cloudwatch_log_group.lambda.*.arn, [""]), 0) - 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_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, 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 + # for #83 that will allow one to import resources without receiving an error from coalesce. + # @see https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/83 + role_name = local.create_role ? coalesce(var.role_name, var.function_name, "*") : null + policy_name = coalesce(var.policy_name, local.role_name, "*") + + # IAM Role trusted entities is a list of any (allow strings (services) and maps (type+identifiers)) + trusted_entities_services = distinct(compact(concat( + slice(["lambda.amazonaws.com", "edgelambda.amazonaws.com"], 0, var.lambda_at_edge ? 2 : 1), + [for service in var.trusted_entities : try(tostring(service), "")] + ))) + + trusted_entities_principals = [ + for principal in var.trusted_entities : { + type = principal.type + identifiers = tolist(principal.identifiers) + } + if !can(tostring(principal)) + ] } ########### @@ -20,7 +42,51 @@ data "aws_iam_policy_document" "assume_role" { principals { type = "Service" - identifiers = distinct(concat(slice(list("lambda.amazonaws.com", "edgelambda.amazonaws.com"), 0, var.lambda_at_edge ? 2 : 1), var.trusted_entities)) + identifiers = local.trusted_entities_services + } + + dynamic "principals" { + for_each = local.trusted_entities_principals + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + } + + dynamic "statement" { + for_each = var.assume_role_policy_statements + + content { + sid = try(statement.value.sid, replace(statement.key, "/[^0-9A-Za-z]*/", "")) + effect = try(statement.value.effect, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.condition, []) + content { + test = condition.value.test + variable = condition.value.variable + values = condition.value.values + } + } } } } @@ -28,12 +94,13 @@ data "aws_iam_policy_document" "assume_role" { resource "aws_iam_role" "lambda" { count = local.create_role ? 1 : 0 - name = coalesce(var.role_name, var.function_name) + name = local.role_name description = var.role_description path = var.role_path 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) } @@ -54,30 +121,24 @@ data "aws_iam_policy_document" "logs" { statement { effect = "Allow" - actions = [ + actions = compact([ + !var.use_existing_cloudwatch_log_group && var.attach_create_log_group_permission ? "logs:CreateLogGroup" : "", "logs:CreateLogStream", - "logs:PutLogEvents", - ] + "logs:PutLogEvents" + ]) resources = flatten([for _, v in ["%v:*", "%v:*:*"] : format(v, local.log_group_arn)]) } } -resource "aws_iam_policy" "logs" { +resource "aws_iam_role_policy" "logs" { count = local.create_role && var.attach_cloudwatch_logs_policy ? 1 : 0 - name = "${var.function_name}-logs" + name = "${local.policy_name}-logs" + role = aws_iam_role.lambda[0].name policy = data.aws_iam_policy_document.logs[0].json } -resource "aws_iam_policy_attachment" "logs" { - count = local.create_role && var.attach_cloudwatch_logs_policy ? 1 : 0 - - name = "${var.function_name}-logs" - roles = [aws_iam_role.lambda[0].name] - policy_arn = aws_iam_policy.logs[0].arn -} - ##################### # Dead Letter Config ##################### @@ -99,73 +160,52 @@ 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 = "${var.function_name}-dl" + name = "${local.policy_name}-dl" + role = aws_iam_role.lambda[0].name policy = data.aws_iam_policy_document.dead_letter[0].json } -resource "aws_iam_policy_attachment" "dead_letter" { - count = local.create_role && var.attach_dead_letter_policy ? 1 : 0 - - name = "${var.function_name}-dl" - roles = [aws_iam_role.lambda[0].name] - policy_arn = aws_iam_policy.dead_letter[0].arn -} - ###### # VPC ###### -// Copying AWS managed policy to be able to attach the same policy with multiple roles without overwrites by another function +# Copying AWS managed policy to be able to attach the same policy with multiple roles without overwrites by another function data "aws_iam_policy" "vpc" { count = local.create_role && var.attach_network_policy ? 1 : 0 - arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaENIManagementAccess" + 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 = "${var.function_name}-vpc" + name = "${local.policy_name}-vpc" + role = aws_iam_role.lambda[0].name policy = data.aws_iam_policy.vpc[0].policy } -resource "aws_iam_policy_attachment" "vpc" { - count = local.create_role && var.attach_network_policy ? 1 : 0 - - name = "${var.function_name}-vpc" - roles = [aws_iam_role.lambda[0].name] - policy_arn = aws_iam_policy.vpc[0].arn -} - ##################### # Tracing with X-Ray ##################### -// Copying AWS managed policy to be able to attach the same policy with multiple roles without overwrites by another function +# Copying AWS managed policy to be able to attach the same policy with multiple roles without overwrites by another function data "aws_iam_policy" "tracing" { count = local.create_role && var.attach_tracing_policy ? 1 : 0 - arn = "arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess" + 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 = "${var.function_name}-tracing" + name = "${local.policy_name}-tracing" + role = aws_iam_role.lambda[0].name policy = data.aws_iam_policy.tracing[0].policy } -resource "aws_iam_policy_attachment" "tracing" { - count = local.create_role && var.attach_tracing_policy ? 1 : 0 - - name = "${var.function_name}-tracing" - roles = [aws_iam_role.lambda[0].name] - policy_arn = aws_iam_policy.tracing[0].arn -} - ############################### # Failure/Success Async Events ############################### @@ -179,55 +219,54 @@ data "aws_iam_policy_document" "async" { actions = [ "sns:Publish", "sqs:SendMessage", + "events:PutEvents", + "lambda:InvokeFunction", ] resources = compact(distinct([var.destination_on_failure, var.destination_on_success])) } } -resource "aws_iam_policy" "async" { +resource "aws_iam_role_policy" "async" { count = local.create_role && var.attach_async_event_policy ? 1 : 0 - name = "${var.function_name}-async" + name = "${local.policy_name}-async" + role = aws_iam_role.lambda[0].name policy = data.aws_iam_policy_document.async[0].json } -resource "aws_iam_policy_attachment" "async" { - count = local.create_role && var.attach_async_event_policy ? 1 : 0 - - name = "${var.function_name}-async" - roles = [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 = var.function_name + name = local.policy_name + role = aws_iam_role.lambda[0].name policy = var.policy_json } -resource "aws_iam_policy_attachment" "additional_json" { - count = local.create_role && var.attach_policy_json ? 1 : 0 +##################################### +# Additional policies (list of JSON) +##################################### - name = var.function_name - roles = [aws_iam_role.lambda[0].name] - policy_arn = aws_iam_policy.additional_json[0].arn +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}" + role = aws_iam_role.lambda[0].name + policy = var.policy_jsons[count.index] } ########################### # ARN of additional policy ########################### -resource "aws_iam_policy_attachment" "additional_one" { +resource "aws_iam_role_policy_attachment" "additional_one" { count = local.create_role && var.attach_policy ? 1 : 0 - name = var.function_name - roles = [aws_iam_role.lambda[0].name] + role = aws_iam_role.lambda[0].name policy_arn = var.policy } @@ -235,11 +274,10 @@ resource "aws_iam_policy_attachment" "additional_one" { # List of ARNs of additional policies ###################################### -resource "aws_iam_policy_attachment" "additional_many" { +resource "aws_iam_role_policy_attachment" "additional_many" { count = local.create_role && var.attach_policies ? var.number_of_policies : 0 - name = var.function_name - roles = [aws_iam_role.lambda[0].name] + role = aws_iam_role.lambda[0].name policy_arn = var.policies[count.index] } @@ -254,15 +292,15 @@ data "aws_iam_policy_document" "additional_inline" { for_each = var.policy_statements content { - sid = lookup(statement.value, "sid", replace(statement.key, "/[^0-9A-Za-z]*/", "")) - effect = lookup(statement.value, "effect", null) - actions = lookup(statement.value, "actions", null) - not_actions = lookup(statement.value, "not_actions", null) - resources = lookup(statement.value, "resources", null) - not_resources = lookup(statement.value, "not_resources", null) + sid = try(statement.value.sid, replace(statement.key, "/[^0-9A-Za-z]*/", "")) + effect = try(statement.value.effect, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + resources = try(statement.value.resources, null) + not_resources = try(statement.value.not_resources, null) dynamic "principals" { - for_each = lookup(statement.value, "principals", []) + for_each = try(statement.value.principals, []) content { type = principals.value.type identifiers = principals.value.identifiers @@ -270,7 +308,7 @@ data "aws_iam_policy_document" "additional_inline" { } dynamic "not_principals" { - for_each = lookup(statement.value, "not_principals", []) + for_each = try(statement.value.not_principals, []) content { type = not_principals.value.type identifiers = not_principals.value.identifiers @@ -278,7 +316,7 @@ data "aws_iam_policy_document" "additional_inline" { } dynamic "condition" { - for_each = lookup(statement.value, "condition", []) + for_each = try(statement.value.condition, []) content { test = condition.value.test variable = condition.value.variable @@ -289,17 +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 = "${var.function_name}-inline" + name = "${local.policy_name}-inline" + role = aws_iam_role.lambda[0].name policy = data.aws_iam_policy_document.additional_inline[0].json } - -resource "aws_iam_policy_attachment" "additional_inline" { - count = local.create_role && var.attach_policy_statements ? 1 : 0 - - name = var.function_name - roles = [aws_iam_role.lambda[0].name] - policy_arn = aws_iam_policy.additional_inline[0].arn -} diff --git a/main.tf b/main.tf index dc7429e1..cc7d011a 100644 --- a/main.tf +++ b/main.tf @@ -1,38 +1,75 @@ +data "aws_partition" "current" {} +data "aws_region" "current" {} +data "aws_caller_identity" "current" {} + locals { + create = var.create && var.putin_khuylo + + archive_filename = try(data.external.archive_prepare[0].result.filename, null) + archive_filename_string = local.archive_filename != null ? local.archive_filename : "" + archive_was_missing = try(data.external.archive_prepare[0].result.was_missing, false) + # Use a generated filename to determine when the source code has changed. # filename - to get package from local - filename = var.local_existing_package != null ? var.local_existing_package : (var.store_on_s3 ? null : element(concat(data.external.archive_prepare.*.result.filename, [null]), 0)) - was_missing = var.local_existing_package != null ? ! fileexists(var.local_existing_package) : element(concat(data.external.archive_prepare.*.result.was_missing, [false]), 0) + filename = var.local_existing_package != null ? var.local_existing_package : (var.store_on_s3 ? null : local.archive_filename) + was_missing = var.local_existing_package != null ? !fileexists(var.local_existing_package) : local.archive_was_missing # s3_* - to get package from S3 - s3_bucket = var.s3_existing_package != null ? lookup(var.s3_existing_package, "bucket", null) : (var.store_on_s3 ? var.s3_bucket : null) - s3_key = var.s3_existing_package != null ? lookup(var.s3_existing_package, "key", null) : (var.store_on_s3 ? element(concat(data.external.archive_prepare.*.result.filename, [null]), 0) : null) - s3_object_version = var.s3_existing_package != null ? lookup(var.s3_existing_package, "version_id", null) : (var.store_on_s3 ? element(concat(aws_s3_bucket_object.lambda_package.*.version_id, [null]), 0) : null) + s3_bucket = var.s3_existing_package != null ? try(var.s3_existing_package.bucket, null) : (var.store_on_s3 ? var.s3_bucket : null) + s3_key = var.s3_existing_package != null ? try(var.s3_existing_package.key, null) : (var.store_on_s3 ? var.s3_prefix != null ? format("%s%s", var.s3_prefix, replace(local.archive_filename_string, "/^.*//", "")) : replace(local.archive_filename_string, "/^\\.//", "") : null) + s3_object_version = var.s3_existing_package != null ? try(var.s3_existing_package.version_id, null) : (var.store_on_s3 ? try(aws_s3_object.lambda_package[0].version_id, null) : null) } resource "aws_lambda_function" "this" { - count = var.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.handler - memory_size = var.memory_size - reserved_concurrent_executions = var.reserved_concurrent_executions - runtime = var.runtime - layers = var.layers - timeout = var.lambda_at_edge ? min(var.timeout, 5) : var.timeout - publish = var.lambda_at_edge ? true : var.publish - kms_key_arn = var.kms_key_arn + count = local.create && var.create_function && !var.create_layer ? 1 : 0 + + 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" { + for_each = var.ephemeral_storage_size == null ? [] : [true] + + content { + size = var.ephemeral_storage_size + } + } filename = local.filename - source_code_hash = (local.filename == null ? false : fileexists(local.filename)) && ! local.was_missing ? filebase64sha256(local.filename) : null + source_code_hash = var.ignore_source_code_hash ? null : (local.filename == null ? false : fileexists(local.filename)) && !local.was_missing ? filebase64sha256(local.filename) : null s3_bucket = local.s3_bucket s3_key = local.s3_key s3_object_version = local.s3_object_version + dynamic "image_config" { + for_each = length(var.image_config_entry_point) > 0 || length(var.image_config_command) > 0 || var.image_config_working_directory != null ? [true] : [] + content { + entry_point = var.image_config_entry_point + command = var.image_config_command + working_directory = var.image_config_working_directory + } + } + dynamic "environment" { for_each = length(keys(var.environment_variables)) == 0 ? [] : [true] content { @@ -57,67 +94,159 @@ 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 + } + } + + dynamic "file_system_config" { + for_each = var.file_system_arn != null && var.file_system_local_mount_path != null ? [true] : [] + content { + local_mount_path = var.file_system_local_mount_path + arn = var.file_system_arn + } + } + + dynamic "snap_start" { + for_each = var.snap_start ? [true] : [] + + content { + apply_on = "PublishedVersions" } } - tags = var.tags + 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] : [] - depends_on = [null_resource.archive, aws_s3_bucket_object.lambda_package] + 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, + aws_s3_object.lambda_package, + + # Depending on the log group is necessary to allow Terraform to create the log group before AWS can. + # When a lambda function is invoked, AWS creates the log group automatically if it doesn't exist yet. + # Without the dependency, this can result in a race condition if the lambda function is invoked before + # Terraform can create the log group. + aws_cloudwatch_log_group.lambda, + + # Before the lambda is created the execution role with all its policies should be ready + 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, + ] } resource "aws_lambda_layer_version" "this" { - count = var.create && var.create_layer ? 1 : 0 + 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 filename = local.filename - source_code_hash = (local.filename == null ? false : fileexists(local.filename)) && ! local.was_missing ? filebase64sha256(local.filename) : null + source_code_hash = var.ignore_source_code_hash ? null : (local.filename == null ? false : fileexists(local.filename)) && !local.was_missing ? filebase64sha256(local.filename) : null s3_bucket = local.s3_bucket s3_key = local.s3_key s3_object_version = local.s3_object_version - depends_on = [null_resource.archive, aws_s3_bucket_object.lambda_package] + depends_on = [null_resource.archive, aws_s3_object.lambda_package] } -resource "aws_s3_bucket_object" "lambda_package" { - count = var.create && var.store_on_s3 && var.create_package ? 1 : 0 +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 - key = data.external.archive_prepare[0].result.filename + acl = var.s3_acl + key = local.s3_key source = data.external.archive_prepare[0].result.filename - etag = fileexists(data.external.archive_prepare[0].result.filename) ? filemd5(data.external.archive_prepare[0].result.filename) : null storage_class = var.s3_object_storage_class - tags = merge(var.tags, var.s3_object_tags) + 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 = var.create && var.create_function && ! var.create_layer && var.use_existing_cloudwatch_log_group ? 1 : 0 + count = local.create && var.create_function && !var.create_layer && var.use_existing_cloudwatch_log_group ? 1 : 0 + + region = var.region - name = "/aws/lambda/${var.lambda_at_edge ? "us-east-1." : ""}${var.function_name}" + 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 = var.create && var.create_function && ! var.create_layer && ! var.use_existing_cloudwatch_log_group ? 1 : 0 + 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) } resource "aws_lambda_provisioned_concurrency_config" "current_version" { - count = var.create && var.create_function && ! var.create_layer && var.provisioned_concurrent_executions > -1 ? 1 : 0 + 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 @@ -130,7 +259,9 @@ locals { } resource "aws_lambda_function_event_invoke_config" "this" { - for_each = var.create && var.create_function && ! var.create_layer && var.create_async_event_config ? local.qualifiers : {} + 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 @@ -159,29 +290,247 @@ resource "aws_lambda_function_event_invoke_config" "this" { } resource "aws_lambda_permission" "current_version_triggers" { - for_each = var.create && var.create_function && ! var.create_layer && var.create_current_version_allowed_triggers ? var.allowed_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 = lookup(each.value, "statement_id", each.key) - action = lookup(each.value, "action", "lambda:InvokeFunction") - principal = lookup(each.value, "principal", format("%s.amazonaws.com", lookup(each.value, "service", ""))) - source_arn = lookup(each.value, "source_arn", lookup(each.value, "service", null) == "apigateway" ? "${lookup(each.value, "arn", "")}/*/*/*" : null) - source_account = lookup(each.value, "source_account", null) - event_source_token = lookup(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 destined-tetra-lambda: InvalidParameterValueException: We currently do not support adding policies for $LATEST. +# 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 = var.create && var.create_function && ! var.create_layer && var.create_unqualified_alias_allowed_triggers ? var.allowed_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 = lookup(each.value, "statement_id", each.key) - action = lookup(each.value, "action", "lambda:InvokeFunction") - principal = lookup(each.value, "principal", format("%s.amazonaws.com", lookup(each.value, "service", ""))) - source_arn = lookup(each.value, "source_arn", lookup(each.value, "service", null) == "apigateway" ? "${lookup(each.value, "arn", "")}/*/*/*" : null) - source_account = lookup(each.value, "source_account", null) - event_source_token = lookup(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) + + batch_size = try(each.value.batch_size, null) + maximum_batching_window_in_seconds = try(each.value.maximum_batching_window_in_seconds, null) + enabled = try(each.value.enabled, true) + starting_position = try(each.value.starting_position, null) + starting_position_timestamp = try(each.value.starting_position_timestamp, null) + parallelization_factor = try(each.value.parallelization_factor, null) + maximum_retry_attempts = try(each.value.maximum_retry_attempts, null) + maximum_record_age_in_seconds = try(each.value.maximum_record_age_in_seconds, null) + bisect_batch_on_function_error = try(each.value.bisect_batch_on_function_error, null) + 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] : [] + content { + on_failure { + destination_arn = each.value["destination_arn_on_failure"] + } + } + } + + 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 { + endpoints = self_managed_event_source.value.endpoints + } + } + + 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 { + type = source_access_configuration.value["type"] + uri = source_access_configuration.value["uri"] + } + } + + dynamic "filter_criteria" { + for_each = try(each.value.filter_criteria, null) != null ? [true] : [] + + content { + 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] + + content { + allow_credentials = try(cors.value.allow_credentials, null) + allow_headers = try(cors.value.allow_headers, null) + allow_methods = try(cors.value.allow_methods, null) + allow_origins = try(cors.value.allow_origins, null) + expose_headers = try(cors.value.expose_headers, null) + max_age = try(cors.value.max_age, null) + } + } +} + +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_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 + # resource + resource_name = "aws_lambda_function.this[0]" + resource_type = "ZIP_LAMBDA_FUNCTION" + + # The Lambda function source code. + original_source_code = jsonencode(var.source_path) + + # a property to let SAM CLI knows where to find the Lambda function source code if the provided + # value for original_source_code attribute is map. + source_code_property = "path" + + # A property to let SAM CLI knows where to find the Lambda function built output + built_output_path = data.external.archive_prepare[0].result.filename + } + + # SAM CLI can run terraform apply -target metadata resource, and this will apply the building + # resources as well + depends_on = [data.external.archive_prepare, null_resource.archive] +} + +# 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 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_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 + # resource + resource_name = "aws_lambda_layer_version.this[0]" + resource_type = "LAMBDA_LAYER" + + # The Lambda layer source code. + original_source_code = jsonencode(var.source_path) + + # a property to let SAM CLI knows where to find the Lambda layer source code if the provided + # value for original_source_code attribute is map. + source_code_property = "path" + + # A property to let SAM CLI knows where to find the Lambda layer built output + built_output_path = data.external.archive_prepare[0].result.filename + } + + # SAM CLI can run terraform apply -target metadata resource, and this will apply the building + # resources as well + depends_on = [data.external.archive_prepare, null_resource.archive] } diff --git a/modules/alias/README.md b/modules/alias/README.md index 11be29a2..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" } @@ -29,13 +29,13 @@ module "alias_no_refresh" { name = "current-no-refresh" - function_name = module.lambda_function.this_lambda_function_name - function_version = module.lambda_function.this_lambda_function_version + function_name = module.lambda_function.lambda_function_name + function_version = module.lambda_function.lambda_function_version allowed_triggers = { AnotherAPIGatewayAny = { - service = "apigateway" - arn = "arn:aws:execute-api:eu-west-1:135367859851:abcdedfgse" + service = "apigateway" + source_arn = "arn:aws:execute-api:eu-west-1:135367859851:abcdedfgse/*/*/*" } } } @@ -50,7 +50,7 @@ module "alias_refresh" { source = "terraform-aws-modules/lambda/aws//modules/alias" name = "current-with-refresh" - function_name = module.lambda_function.this_lambda_function_name + function_name = module.lambda_function.lambda_function_name } ``` @@ -63,7 +63,7 @@ module "alias_refresh" { source = "terraform-aws-modules/lambda/aws//modules/alias" name = "current-with-refresh" - function_name = module.lambda_function.this_lambda_function_name + function_name = module.lambda_function.lambda_function_name } module "alias_existing" { @@ -71,13 +71,13 @@ module "alias_existing" { use_existing_alias = true - name = module.alias_refresh.this_lambda_alias_name - function_name = module.lambda_function.this_lambda_function_name + name = module.alias_refresh.lambda_alias_name + function_name = module.lambda_function.lambda_function_name allowed_triggers = { AnotherAwesomeAPIGateway = { - service = "apigateway" - arn = "arn:aws:execute-api:eu-west-1:999967859851:aqnku8akd0" + service = "apigateway" + source_arn = "arn:aws:execute-api:eu-west-1:999967859851:aqnku8akd0/*/*/*" } } } @@ -110,54 +110,74 @@ 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 | ~> 0.12.6 | -| aws | ~> 2.46 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | ## Providers | Name | Version | |------|---------| -| aws | ~> 2.46 | +| [aws](#provider\_aws) | >= 6.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_lambda_alias.no_refresh](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_alias) | resource | +| [aws_lambda_alias.with_refresh](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_alias) | 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_event_invoke_config.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function_event_invoke_config) | resource | +| [aws_lambda_permission.qualified_alias_triggers](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource | +| [aws_lambda_permission.version_triggers](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource | +| [aws_lambda_alias.existing](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/lambda_alias) | data source | ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| allowed\_triggers | Map of allowed triggers to create Lambda permissions | `map(any)` | `{}` | no | -| create | Controls whether resources should be created | `bool` | `true` | no | -| create\_async\_event\_config | Controls whether async event configuration for Lambda Function/Alias should be created | `bool` | `false` | no | -| create\_qualified\_alias\_allowed\_triggers | Whether to allow triggers on qualified alias | `bool` | `true` | no | -| create\_qualified\_alias\_async\_event\_config | Whether to allow async event configuration on qualified alias | `bool` | `true` | no | -| create\_version\_allowed\_triggers | Whether to allow triggers on version of Lambda Function used by alias (this will revoke permissions from previous version because Terraform manages only current resources) | `bool` | `true` | no | -| create\_version\_async\_event\_config | Whether to allow async event configuration on version of Lambda Function used by alias (this will revoke permissions from previous version because Terraform manages only current resources) | `bool` | `true` | no | -| description | Description of the alias. | `string` | `""` | no | -| destination\_on\_failure | Amazon Resource Name (ARN) of the destination resource for failed asynchronous invocations | `string` | `null` | no | -| destination\_on\_success | Amazon Resource Name (ARN) of the destination resource for successful asynchronous invocations | `string` | `null` | no | -| function\_name | The function ARN of the Lambda function for which you want to create an alias. | `string` | `""` | no | -| function\_version | Lambda function version for which you are creating the alias. Pattern: ($LATEST\|[0-9]+). | `string` | `""` | no | -| 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 | Maximum number of times to retry when the function returns an error. Valid values between 0 and 2. Defaults to 2. | `number` | `null` | no | -| name | Name for the alias you are creating. | `string` | `""` | no | -| refresh\_alias | Whether to refresh function version used in the alias. Useful when using this module together with external tool do deployments (eg, AWS CodeDeploy). | `bool` | `true` | no | -| routing\_additional\_version\_weights | A map that defines the proportion of events that should be sent to different versions of a lambda function. | `map(number)` | `{}` | no | -| use\_existing\_alias | Whether to manage existing alias instead of creating a new one. Useful when using this module together with external tool do deployments (eg, AWS CodeDeploy). | `bool` | `false` | no | +| [allowed\_triggers](#input\_allowed\_triggers) | Map of allowed triggers to create Lambda permissions | `map(any)` | `{}` | no | +| [create](#input\_create) | Controls whether resources should be created | `bool` | `true` | no | +| [create\_async\_event\_config](#input\_create\_async\_event\_config) | Controls whether async event configuration for Lambda Function/Alias should be created | `bool` | `false` | no | +| [create\_qualified\_alias\_allowed\_triggers](#input\_create\_qualified\_alias\_allowed\_triggers) | Whether to allow triggers on qualified alias | `bool` | `true` | no | +| [create\_qualified\_alias\_async\_event\_config](#input\_create\_qualified\_alias\_async\_event\_config) | Whether to allow async event configuration on qualified alias | `bool` | `true` | no | +| [create\_version\_allowed\_triggers](#input\_create\_version\_allowed\_triggers) | Whether to allow triggers on version of Lambda Function used by alias (this will revoke permissions from previous version because Terraform manages only current resources) | `bool` | `true` | no | +| [create\_version\_async\_event\_config](#input\_create\_version\_async\_event\_config) | Whether to allow async event configuration on version of Lambda Function used by alias (this will revoke permissions from previous version because Terraform manages only current resources) | `bool` | `true` | no | +| [description](#input\_description) | Description of the alias. | `string` | `""` | no | +| [destination\_on\_failure](#input\_destination\_on\_failure) | Amazon Resource Name (ARN) of the destination resource for failed asynchronous invocations | `string` | `null` | no | +| [destination\_on\_success](#input\_destination\_on\_success) | Amazon Resource Name (ARN) of the destination resource for successful asynchronous invocations | `string` | `null` | no | +| [event\_source\_mapping](#input\_event\_source\_mapping) | Map of event source mapping | `any` | `{}` | no | +| [function\_name](#input\_function\_name) | The function ARN of the Lambda function for which you want to create an alias. | `string` | `""` | no | +| [function\_version](#input\_function\_version) | Lambda function version for which you are creating the alias. Pattern: ($LATEST\|[0-9]+). | `string` | `""` | 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 | +| [name](#input\_name) | Name for the alias you are creating. | `string` | `""` | no | +| [refresh\_alias](#input\_refresh\_alias) | Whether to refresh function version used in the alias. Useful when using this module together with external tool do deployments (eg, AWS CodeDeploy). | `bool` | `true` | no | +| [routing\_additional\_version\_weights](#input\_routing\_additional\_version\_weights) | A map that defines the proportion of events that should be sent to different versions of a lambda function. | `map(number)` | `{}` | no | +| [use\_existing\_alias](#input\_use\_existing\_alias) | Whether to manage existing alias instead of creating a new one. Useful when using this module together with external tool do deployments (eg, AWS CodeDeploy). | `bool` | `false` | no | ## Outputs | Name | Description | |------|-------------| -| this\_lambda\_alias\_arn | The ARN of the Lambda Function Alias | -| this\_lambda\_alias\_description | Description of alias | -| this\_lambda\_alias\_function\_version | Lambda function version which the alias uses | -| this\_lambda\_alias\_invoke\_arn | The ARN to be used for invoking Lambda Function from API Gateway | -| this\_lambda\_alias\_name | The name of the Lambda Function Alias | - - +| [lambda\_alias\_arn](#output\_lambda\_alias\_arn) | The ARN of the Lambda Function Alias | +| [lambda\_alias\_description](#output\_lambda\_alias\_description) | Description of alias | +| [lambda\_alias\_event\_source\_mapping\_function\_arn](#output\_lambda\_alias\_event\_source\_mapping\_function\_arn) | The the ARN of the Lambda function the event source mapping is sending events to | +| [lambda\_alias\_event\_source\_mapping\_state](#output\_lambda\_alias\_event\_source\_mapping\_state) | The state of the event source mapping | +| [lambda\_alias\_event\_source\_mapping\_state\_transition\_reason](#output\_lambda\_alias\_event\_source\_mapping\_state\_transition\_reason) | The reason the event source mapping is in its current state | +| [lambda\_alias\_event\_source\_mapping\_uuid](#output\_lambda\_alias\_event\_source\_mapping\_uuid) | The UUID of the created event source mapping | +| [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 9eaefb22..e57079a2 100644 --- a/modules/alias/main.tf +++ b/modules/alias/main.tf @@ -1,5 +1,6 @@ locals { - version = element(concat(data.aws_lambda_alias.existing.*.function_version, aws_lambda_alias.with_refresh.*.function_version, aws_lambda_alias.no_refresh.*.function_version, [""]), 0) + alias_arn = try(data.aws_lambda_alias.existing[0].arn, aws_lambda_alias.no_refresh[0].arn, aws_lambda_alias.with_refresh[0].arn, "") + version = try(data.aws_lambda_alias.existing[0].function_version, aws_lambda_alias.with_refresh[0].function_version, aws_lambda_alias.no_refresh[0].function_version, "") qualifiers = zipmap(["version", "qualified_alias"], [var.create_version_async_event_config ? true : null, var.create_qualified_alias_async_event_config ? true : null]) } @@ -11,7 +12,7 @@ data "aws_lambda_alias" "existing" { } resource "aws_lambda_alias" "no_refresh" { - count = var.create && ! var.use_existing_alias && ! var.refresh_alias ? 1 : 0 + count = var.create && !var.use_existing_alias && !var.refresh_alias ? 1 : 0 name = var.name description = var.description @@ -19,17 +20,21 @@ resource "aws_lambda_alias" "no_refresh" { function_name = var.function_name function_version = var.function_version != "" ? var.function_version : "$LATEST" - // $LATEST is not supported for an alias pointing to more than 1 version + # $LATEST is not supported for an alias pointing to more than 1 version dynamic "routing_config" { for_each = length(keys(var.routing_additional_version_weights)) == 0 ? [] : [true] content { additional_version_weights = var.routing_additional_version_weights } } + + lifecycle { + ignore_changes = [function_version] + } } resource "aws_lambda_alias" "with_refresh" { - count = var.create && ! var.use_existing_alias && var.refresh_alias ? 1 : 0 + count = var.create && !var.use_existing_alias && var.refresh_alias ? 1 : 0 name = var.name description = var.description @@ -37,17 +42,13 @@ resource "aws_lambda_alias" "with_refresh" { function_name = var.function_name function_version = var.function_version != "" ? var.function_version : "$LATEST" - // $LATEST is not supported for an alias pointing to more than 1 version + # $LATEST is not supported for an alias pointing to more than 1 version dynamic "routing_config" { for_each = length(keys(var.routing_additional_version_weights)) == 0 ? [] : [true] content { additional_version_weights = var.routing_additional_version_weights } } - - lifecycle { - ignore_changes = [function_version] - } } resource "aws_lambda_function_event_invoke_config" "this" { @@ -84,15 +85,16 @@ resource "aws_lambda_permission" "version_triggers" { function_name = var.function_name - // Error: Error adding new Lambda Permission for ... InvalidParameterValueException: We currently do not support adding policies for $LATEST. + # Error: Error adding new Lambda Permission for ... InvalidParameterValueException: We currently do not support adding policies for $LATEST. qualifier = local.version != "$LATEST" ? local.version : null - statement_id = lookup(each.value, "statement_id", each.key) - action = lookup(each.value, "action", "lambda:InvokeFunction") - principal = lookup(each.value, "principal", format("%s.amazonaws.com", lookup(each.value, "service", ""))) - source_arn = lookup(each.value, "source_arn", lookup(each.value, "service", null) == "apigateway" ? "${lookup(each.value, "arn", "")}/*/*/*" : null) - source_account = lookup(each.value, "source_account", null) - event_source_token = lookup(each.value, "event_source_token", null) + 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) } resource "aws_lambda_permission" "qualified_alias_triggers" { @@ -101,10 +103,91 @@ resource "aws_lambda_permission" "qualified_alias_triggers" { function_name = var.function_name qualifier = var.name - statement_id = lookup(each.value, "statement_id", each.key) - action = lookup(each.value, "action", "lambda:InvokeFunction") - principal = lookup(each.value, "principal", format("%s.amazonaws.com", lookup(each.value, "service", ""))) - source_arn = lookup(each.value, "source_arn", lookup(each.value, "service", null) == "apigateway" ? "${lookup(each.value, "arn", "")}/*/*/*" : null) - source_account = lookup(each.value, "source_account", null) - event_source_token = lookup(each.value, "event_source_token", null) + 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) +} + +resource "aws_lambda_event_source_mapping" "this" { + for_each = { for k, v in var.event_source_mapping : k => v if var.create } + + function_name = local.alias_arn + + event_source_arn = try(each.value.event_source_arn, null) + + batch_size = try(each.value.batch_size, null) + maximum_batching_window_in_seconds = try(each.value.maximum_batching_window_in_seconds, null) + enabled = try(each.value.enabled, null) + starting_position = try(each.value.starting_position, null) + starting_position_timestamp = try(each.value.starting_position_timestamp, null) + parallelization_factor = try(each.value.parallelization_factor, null) + maximum_retry_attempts = try(each.value.maximum_retry_attempts, null) + maximum_record_age_in_seconds = try(each.value.maximum_record_age_in_seconds, null) + bisect_batch_on_function_error = try(each.value.bisect_batch_on_function_error, null) + topics = try(each.value.topics, null) + queues = try(each.value.queues, null) + function_response_types = try(each.value.function_response_types, null) + + dynamic "destination_config" { + for_each = try(each.value.destination_arn_on_failure, null) != null ? [true] : [] + content { + on_failure { + destination_arn = each.value["destination_arn_on_failure"] + } + } + } + + 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 { + endpoints = self_managed_event_source.value.endpoints + } + } + + 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 { + type = source_access_configuration.value["type"] + uri = source_access_configuration.value["uri"] + } + } + + dynamic "filter_criteria" { + for_each = try(each.value.filter_criteria, null) != null ? [true] : [] + + content { + dynamic "filter" { + for_each = try(flatten([each.value.filter_criteria]), []) + + content { + pattern = try(filter.value.pattern, null) + } + } + } + } } diff --git a/modules/alias/outputs.tf b/modules/alias/outputs.tf index 3e284331..b1a29153 100644 --- a/modules/alias/outputs.tf +++ b/modules/alias/outputs.tf @@ -1,25 +1,45 @@ # Lambda Alias -output "this_lambda_alias_name" { +output "lambda_alias_name" { description = "The name of the Lambda Function Alias" - value = element(concat(data.aws_lambda_alias.existing.*.name, aws_lambda_alias.with_refresh.*.name, aws_lambda_alias.no_refresh.*.name, [""]), 0) + value = try(data.aws_lambda_alias.existing[0].name, aws_lambda_alias.with_refresh[0].name, aws_lambda_alias.no_refresh[0].name, "") } -output "this_lambda_alias_arn" { +output "lambda_alias_arn" { description = "The ARN of the Lambda Function Alias" - value = element(concat(data.aws_lambda_alias.existing.*.arn, aws_lambda_alias.with_refresh.*.arn, aws_lambda_alias.no_refresh.*.arn, [""]), 0) + value = try(data.aws_lambda_alias.existing[0].arn, aws_lambda_alias.with_refresh[0].arn, aws_lambda_alias.no_refresh[0].arn, "") } -output "this_lambda_alias_invoke_arn" { +output "lambda_alias_invoke_arn" { description = "The ARN to be used for invoking Lambda Function from API Gateway" - value = element(concat(data.aws_lambda_alias.existing.*.invoke_arn, aws_lambda_alias.with_refresh.*.invoke_arn, aws_lambda_alias.no_refresh.*.invoke_arn, [""]), 0) + value = try(data.aws_lambda_alias.existing[0].invoke_arn, aws_lambda_alias.with_refresh[0].invoke_arn, aws_lambda_alias.no_refresh[0].invoke_arn, "") } -output "this_lambda_alias_description" { +output "lambda_alias_description" { description = "Description of alias" - value = element(concat(data.aws_lambda_alias.existing.*.description, aws_lambda_alias.with_refresh.*.description, aws_lambda_alias.no_refresh.*.description, [""]), 0) + value = try(data.aws_lambda_alias.existing[0].description, aws_lambda_alias.with_refresh[0].description, aws_lambda_alias.no_refresh[0].description, "") } -output "this_lambda_alias_function_version" { +output "lambda_alias_function_version" { description = "Lambda function version which the alias uses" - value = element(concat(data.aws_lambda_alias.existing.*.function_version, aws_lambda_alias.with_refresh.*.function_version, aws_lambda_alias.no_refresh.*.function_version, [""]), 0) + value = try(data.aws_lambda_alias.existing[0].function_version, aws_lambda_alias.with_refresh[0].function_version, aws_lambda_alias.no_refresh[0].function_version, "") +} + +output "lambda_alias_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 } +} + +output "lambda_alias_event_source_mapping_state" { + description = "The state of the event source mapping" + value = { for k, v in aws_lambda_event_source_mapping.this : k => v.state } +} + +output "lambda_alias_event_source_mapping_state_transition_reason" { + description = "The reason the event source mapping is in its current state" + value = { for k, v in aws_lambda_event_source_mapping.this : k => v.state_transition_reason } +} + +output "lambda_alias_event_source_mapping_uuid" { + description = "The UUID of the created event source mapping" + value = { for k, v in aws_lambda_event_source_mapping.this : k => v.uuid } } diff --git a/modules/alias/variables.tf b/modules/alias/variables.tf index d5601998..732067f6 100644 --- a/modules/alias/variables.tf +++ b/modules/alias/variables.tf @@ -117,3 +117,13 @@ variable "allowed_triggers" { type = map(any) default = {} } + +############################################ +# Lambda Event Source Mapping +############################################ + +variable "event_source_mapping" { + description = "Map of event source mapping" + type = any + default = {} +} diff --git a/modules/alias/versions.tf b/modules/alias/versions.tf index c1b26983..db13b0a8 100644 --- a/modules/alias/versions.tf +++ b/modules/alias/versions.tf @@ -1,7 +1,10 @@ terraform { - required_version = "~> 0.12.6" + required_version = ">= 1.5.7" required_providers { - aws = "~> 2.46" + aws = { + source = "hashicorp/aws" + version = ">= 6.0" + } } } diff --git a/modules/deploy/README.md b/modules/deploy/README.md index d05af2f1..b5d535c9 100644 --- a/modules/deploy/README.md +++ b/modules/deploy/README.md @@ -8,7 +8,7 @@ This module can create AWS CodeDeploy application and deployment group, if neces During deployment this module does the following: 1. Create JSON object with required AppSpec configuration. Optionally, you can store deploy script for debug purposes by setting `save_deploy_script = true`. -1. Run [`aws deploy create-deployment` command](https://docs.aws.amazon.com/cli/latest/reference/deploy/create-deployment.html) if `create_deployment = true` was set +1. Run [`aws deploy create-deployment` command](https://docs.aws.amazon.com/cli/latest/reference/deploy/create-deployment.html) if `create_deployment = true` and `run_deployment = true` was set. 1. After deployment is created, it can wait for the completion if `wait_deployment_completion = true`. Be aware, that Terraform will lock the execution and it can fail if it runs for a long period of time. Set this flag for fast deployments (eg, `deployment_config_name = "CodeDeployDefault.LambdaAllAtOnce"`). @@ -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" } @@ -31,20 +31,20 @@ module "alias_refresh" { source = "terraform-aws-modules/lambda/aws//modules/alias" name = "current-with-refresh" - function_name = module.lambda_function.this_lambda_function_name + function_name = module.lambda_function.lambda_function_name # Set function_version when creating alias to be able to deploy using it, # because AWS CodeDeploy doesn't understand $LATEST as CurrentVersion. - function_version = module.lambda_function.this_lambda_function_version + function_version = module.lambda_function.lambda_function_version } module "deploy" { source = "terraform-aws-modules/lambda/aws//modules/deploy" - alias_name = module.alias_refresh.this_lambda_alias_name - function_name = module.lambda_function.this_lambda_function_name + alias_name = module.alias_refresh.lambda_alias_name + function_name = module.lambda_function.lambda_function_name - target_version = module.lambda_function.this_lambda_function_version + target_version = module.lambda_function.lambda_function_version create_app = true app_name = "my-awesome-app" @@ -53,6 +53,7 @@ module "deploy" { deployment_group_name = "something" create_deployment = true + run_deployment = true wait_deployment_completion = true triggers = { @@ -94,72 +95,104 @@ 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 | ~> 0.12.6 | -| aws | ~> 2.46 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | +| [local](#requirement\_local) | >= 1.0 | +| [null](#requirement\_null) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| aws | ~> 2.46 | -| local | n/a | -| null | n/a | +| [aws](#provider\_aws) | >= 6.0 | +| [local](#provider\_local) | >= 1.0 | +| [null](#provider\_null) | >= 2.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_codedeploy_app.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codedeploy_app) | resource | +| [aws_codedeploy_deployment_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codedeploy_deployment_group) | resource | +| [aws_iam_policy.hooks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.triggers](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.codedeploy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.codedeploy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.hooks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.triggers](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [local_file.deploy_script](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource | +| [null_resource.deploy](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | +| [aws_iam_policy_document.assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.hooks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.triggers](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [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 | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| after\_allow\_traffic\_hook\_arn | ARN of Lambda function to execute after allow traffic during deployment | `string` | `""` | no | -| alarm\_enabled | Indicates whether the alarm configuration is enabled. This option is useful when you want to temporarily deactivate alarm monitoring for a deployment group without having to add the same alarms again later. | `bool` | `false` | no | -| alarm\_ignore\_poll\_alarm\_failure | Indicates whether a deployment should continue if information about the current state of alarms cannot be retrieved from CloudWatch. | `bool` | `false` | no | -| alarms | A list of alarms configured for the deployment group. A maximum of 10 alarms can be added to a deployment group. | `list(string)` | `[]` | no | -| alias\_name | Name for the alias | `string` | `""` | no | -| app\_name | Name of AWS CodeDeploy application | `string` | `""` | no | -| attach\_triggers\_policy | Whether to attach SNS policy to CodeDeploy role when triggers are defined | `bool` | `false` | no | -| auto\_rollback\_enabled | Indicates whether a defined automatic rollback configuration is currently enabled for this Deployment Group. | `bool` | `true` | no | -| 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 | Command to run as AWS CLI. May include extra arguments like region and profile. | `string` | `"aws"` | no | -| before\_allow\_traffic\_hook\_arn | ARN of Lambda function to execute before allow traffic during deployment | `string` | `""` | no | -| 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 | IAM role name to create or use by CodeDeploy | `string` | `""` | no | -| create | Controls whether resources should be created | `bool` | `true` | no | -| create\_app | Whether to create new AWS CodeDeploy app | `bool` | `false` | no | -| create\_codedeploy\_role | Whether to create new AWS CodeDeploy IAM role | `bool` | `true` | no | -| create\_deployment | Run AWS CLI command to create deployment | `bool` | `false` | no | -| create\_deployment\_group | Whether to create new AWS CodeDeploy Deployment Group | `bool` | `false` | no | -| current\_version | Current version of Lambda function version to deploy (can't be $LATEST) | `string` | `""` | no | -| deployment\_config\_name | Name of deployment config to use | `string` | `"CodeDeployDefault.LambdaAllAtOnce"` | no | -| deployment\_group\_name | Name of deployment group to use | `string` | `""` | no | -| description | Description to use for the deployment | `string` | `""` | no | -| force\_deploy | Force deployment every time (even when nothing changes) | `bool` | `false` | no | -| function\_name | The name of the Lambda function to deploy | `string` | `""` | no | -| save\_deploy\_script | Save deploy script locally | `bool` | `false` | no | -| target\_version | Target version of Lambda function version to deploy | `string` | `""` | no | -| triggers | Map of triggers which will be notified when event happens. Valid options for event types are DeploymentStart, DeploymentSuccess, DeploymentFailure, DeploymentStop, DeploymentRollback, DeploymentReady (Applies only to replacement instances in a blue/green deployment), InstanceStart, InstanceSuccess, InstanceFailure, InstanceReady. Note that not all are applicable for Lambda deployments. | `map(any)` | `{}` | no | -| use\_existing\_app | Whether to use existing AWS CodeDeploy app | `bool` | `false` | no | -| use\_existing\_deployment\_group | Whether to use existing AWS CodeDeploy Deployment Group | `bool` | `false` | no | -| wait\_deployment\_completion | Wait until deployment completes. It can take a lot of time and your terraform process may lock execution for long time. | `bool` | `false` | no | +| [after\_allow\_traffic\_hook\_arn](#input\_after\_allow\_traffic\_hook\_arn) | ARN of Lambda function to execute after 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 | +| [alarm\_enabled](#input\_alarm\_enabled) | Indicates whether the alarm configuration is enabled. This option is useful when you want to temporarily deactivate alarm monitoring for a deployment group without having to add the same alarms again later. | `bool` | `false` | no | +| [alarm\_ignore\_poll\_alarm\_failure](#input\_alarm\_ignore\_poll\_alarm\_failure) | Indicates whether a deployment should continue if information about the current state of alarms cannot be retrieved from CloudWatch. | `bool` | `false` | no | +| [alarms](#input\_alarms) | A list of alarms configured for the deployment group. A maximum of 10 alarms can be added to a deployment group. | `list(string)` | `[]` | no | +| [alias\_name](#input\_alias\_name) | Name for the alias | `string` | `""` | no | +| [app\_name](#input\_app\_name) | Name of AWS CodeDeploy application | `string` | `""` | no | +| [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 | +| [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\_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 | +| [create\_codedeploy\_role](#input\_create\_codedeploy\_role) | Whether to create new AWS CodeDeploy IAM role | `bool` | `true` | no | +| [create\_deployment](#input\_create\_deployment) | Create the AWS resources and script for CodeDeploy | `bool` | `false` | no | +| [create\_deployment\_group](#input\_create\_deployment\_group) | Whether to create new AWS CodeDeploy Deployment Group | `bool` | `false` | no | +| [current\_version](#input\_current\_version) | Current version of Lambda function version to deploy (can't be $LATEST) | `string` | `""` | no | +| [deployment\_config\_name](#input\_deployment\_config\_name) | Name of deployment config to use | `string` | `"CodeDeployDefault.LambdaAllAtOnce"` | no | +| [deployment\_group\_name](#input\_deployment\_group\_name) | Name of deployment group to use | `string` | `""` | no | +| [description](#input\_description) | Description to use for the deployment | `string` | `""` | no | +| [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 | +| [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 | +| [target\_version](#input\_target\_version) | Target version of Lambda function version to deploy | `string` | `""` | no | +| [triggers](#input\_triggers) | Map of triggers which will be notified when event happens. Valid options for event types are DeploymentStart, DeploymentSuccess, DeploymentFailure, DeploymentStop, DeploymentRollback, DeploymentReady (Applies only to replacement instances in a blue/green deployment), InstanceStart, InstanceSuccess, InstanceFailure, InstanceReady. Note that not all are applicable for Lambda deployments. | `map(any)` | `{}` | no | +| [use\_existing\_app](#input\_use\_existing\_app) | Whether to use existing AWS CodeDeploy app | `bool` | `false` | no | +| [use\_existing\_deployment\_group](#input\_use\_existing\_deployment\_group) | Whether to use existing AWS CodeDeploy Deployment Group | `bool` | `false` | no | +| [wait\_deployment\_completion](#input\_wait\_deployment\_completion) | Wait until deployment completes. It can take a lot of time and your terraform process may lock execution for long time. | `bool` | `false` | no | ## Outputs | Name | Description | |------|-------------| -| appspec | n/a | -| appspec\_content | n/a | -| appspec\_sha256 | n/a | -| codedeploy\_app\_name | Name of CodeDeploy application | -| codedeploy\_deployment\_group\_id | CodeDeploy deployment group id | -| codedeploy\_deployment\_group\_name | CodeDeploy deployment group name | -| codedeploy\_iam\_role\_name | Name of IAM role used by CodeDeploy | -| deploy\_script | n/a | -| script | n/a | - - +| [appspec](#output\_appspec) | Appspec data as HCL | +| [appspec\_content](#output\_appspec\_content) | Appspec data as valid JSON | +| [appspec\_sha256](#output\_appspec\_sha256) | SHA256 of Appspec JSON | +| [codedeploy\_app\_name](#output\_codedeploy\_app\_name) | Name of CodeDeploy application | +| [codedeploy\_deployment\_group\_id](#output\_codedeploy\_deployment\_group\_id) | CodeDeploy deployment group id | +| [codedeploy\_deployment\_group\_name](#output\_codedeploy\_deployment\_group\_name) | CodeDeploy deployment group name | +| [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 f14c7d12..d88c0894 100644 --- a/modules/deploy/main.tf +++ b/modules/deploy/main.tf @@ -1,10 +1,10 @@ locals { # AWS CodeDeploy can't deploy when CurrentVersion is "$LATEST" - qualifier = element(concat(data.aws_lambda_function.this.*.qualifier, [""]), 0) + qualifier = try(data.aws_lambda_function.this[0].qualifier, "") current_version = local.qualifier == "$LATEST" ? 1 : local.qualifier - app_name = element(concat(aws_codedeploy_app.this.*.name, [var.app_name]), 0) - deployment_group_name = element(concat(aws_codedeploy_deployment_group.this.*.deployment_group_name, [var.deployment_group_name]), 0) + app_name = try(aws_codedeploy_app.this[0].name, var.app_name) + deployment_group_name = try(aws_codedeploy_deployment_group.this[0].deployment_group_name, var.deployment_group_name) appspec = merge({ version = "0.0" @@ -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 } } } @@ -25,7 +25,7 @@ locals { Hooks = [for k, v in zipmap(["BeforeAllowTraffic", "AfterAllowTraffic"], [ var.before_allow_traffic_hook_arn != "" ? var.before_allow_traffic_hook_arn : null, var.after_allow_traffic_hook_arn != "" ? var.after_allow_traffic_hook_arn : null - ]) : map(k, v)] + ]) : tomap({ (k) = v }) if v != null] } : {}) appspec_content = replace(jsonencode(local.appspec), "\"", "\\\"") @@ -33,6 +33,12 @@ locals { script = < +## Requirements + +| Name | Version | +|------|---------| +| [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) | >= 6.0 | +| [docker](#provider\_docker) | >= 3.5.0 | +| [null](#provider\_null) | >= 2.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [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 | +| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | + +## Inputs + +| 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 + +Module managed by [Anton Babenko](https://github.com/antonbabenko). Check out [serverless.tf](https://serverless.tf) to learn more about doing serverless with Terraform. + +Please reach out to [Betajob](https://www.betajob.com/) if you are looking for commercial support for your Terraform, AWS, or serverless project. + + +## License + +Apache 2 Licensed. See LICENSE for full details. diff --git a/modules/docker-build/main.tf b/modules/docker-build/main.tf new file mode 100644 index 00000000..559060cb --- /dev/null +++ b/modules/docker-build/main.tf @@ -0,0 +1,75 @@ +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.region)) + ecr_repo = var.create_ecr_repo ? aws_ecr_repository.this[0].id : var.ecr_repo + 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_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" { + count = var.create_ecr_repo ? 1 : 0 + + force_delete = var.ecr_force_delete + name = var.ecr_repo + image_tag_mutability = var.image_tag_mutability + + image_scanning_configuration { + scan_on_push = var.scan_on_push + } + + tags = var.ecr_repo_tags +} + +resource "aws_ecr_lifecycle_policy" "this" { + count = var.ecr_repo_lifecycle_policy != null ? 1 : 0 + + policy = var.ecr_repo_lifecycle_policy + repository = local.ecr_repo +} + +# This resource contains the extra information required by SAM CLI to provide the testing capabilities +# 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 + docker_file = var.docker_file_path + docker_build_args = jsonencode(var.build_args) + docker_tag = var.image_tag + built_image_uri = docker_registry_image.this.name + } + + depends_on = [docker_registry_image.this] +} diff --git a/modules/docker-build/outputs.tf b/modules/docker-build/outputs.tf new file mode 100644 index 00000000..5b268b54 --- /dev/null +++ b/modules/docker-build/outputs.tf @@ -0,0 +1,9 @@ +output "image_uri" { + description = "The ECR image URI for deploying lambda" + 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 new file mode 100644 index 00000000..110ce554 --- /dev/null +++ b/modules/docker-build/variables.tf @@ -0,0 +1,132 @@ +variable "create_ecr_repo" { + description = "Controls whether ECR repository for Lambda image should be created" + type = bool + 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 + default = null +} + +variable "ecr_repo" { + description = "Name of ECR repository to use or to create" + type = string + default = null +} + +variable "image_tag" { + description = "Image tag to use. If not specified current timestamp in format 'YYYYMMDDhhmmss' will be used. This can lead to unnecessary rebuilds." + type = string + default = null +} + +variable "source_path" { + description = "Path to folder containing application code" + type = string + default = null +} + +variable "docker_file_path" { + description = "Path to Dockerfile in source package" + type = string + default = "Dockerfile" +} + + +variable "image_tag_mutability" { + description = "The tag mutability setting for the repository. Must be one of: `MUTABLE` or `IMMUTABLE`" + type = string + default = "MUTABLE" +} + +variable "scan_on_push" { + description = "Indicates whether images are scanned after being pushed to the repository" + type = bool + default = false +} + +variable "ecr_force_delete" { + description = "If true, will delete the repository even if it contains images." + default = true + type = bool +} + +variable "ecr_repo_tags" { + description = "A map of tags to assign to ECR repository" + type = map(string) + 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 + default = null +} + +variable "keep_remotely" { + description = "Whether to keep Docker image in the remote registry on destroy operation." + 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 new file mode 100644 index 00000000..b203b635 --- /dev/null +++ b/modules/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/outputs.tf b/outputs.tf index 73d769b4..93624833 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,84 +1,146 @@ # Lambda Function -output "this_lambda_function_arn" { +output "lambda_function_arn" { description = "The ARN of the Lambda Function" - value = element(concat(aws_lambda_function.this.*.arn, [""]), 0) + value = try(aws_lambda_function.this[0].arn, "") } -output "this_lambda_function_invoke_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:${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" { description = "The Invoke ARN of the Lambda Function" - value = element(concat(aws_lambda_function.this.*.invoke_arn, [""]), 0) + value = try(aws_lambda_function.this[0].invoke_arn, "") } -output "this_lambda_function_name" { +output "lambda_function_name" { description = "The name of the Lambda Function" - value = element(concat(aws_lambda_function.this.*.function_name, [""]), 0) + value = try(aws_lambda_function.this[0].function_name, "") } -output "this_lambda_function_qualified_arn" { +output "lambda_function_qualified_arn" { description = "The ARN identifying your Lambda Function Version" - value = element(concat(aws_lambda_function.this.*.qualified_arn, [""]), 0) + 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 "this_lambda_function_version" { +output "lambda_function_version" { description = "Latest published version of Lambda Function" - value = element(concat(aws_lambda_function.this.*.version, [""]), 0) + value = try(aws_lambda_function.this[0].version, "") } -output "this_lambda_function_last_modified" { +output "lambda_function_last_modified" { description = "The date Lambda Function resource was last modified" - value = element(concat(aws_lambda_function.this.*.last_modified, [""]), 0) + value = try(aws_lambda_function.this[0].last_modified, "") } -output "this_lambda_function_kms_key_arn" { +output "lambda_function_kms_key_arn" { description = "The ARN for the KMS encryption key of Lambda Function" - value = element(concat(aws_lambda_function.this.*.kms_key_arn, [""]), 0) + value = try(aws_lambda_function.this[0].kms_key_arn, "") } -output "this_lambda_function_source_code_hash" { +output "lambda_function_source_code_hash" { description = "Base64-encoded representation of raw SHA-256 sum of the zip file" - value = element(concat(aws_lambda_function.this.*.source_code_hash, [""]), 0) + value = try(aws_lambda_function.this[0].source_code_hash, "") } -output "this_lambda_function_source_code_size" { +output "lambda_function_source_code_size" { description = "The size in bytes of the function .zip file" - value = element(concat(aws_lambda_function.this.*.source_code_size, [""]), 0) + value = try(aws_lambda_function.this[0].source_code_size, "") +} + +output "lambda_function_signing_job_arn" { + description = "ARN of the signing job" + value = try(aws_lambda_function.this[0].signing_job_arn, "") +} + +output "lambda_function_signing_profile_version_arn" { + description = "ARN of the signing profile version" + value = try(aws_lambda_function.this[0].signing_profile_version_arn, "") +} + +# Lambda Function URL +output "lambda_function_url" { + description = "The URL of the Lambda Function URL" + value = try(aws_lambda_function_url.this[0].function_url, "") +} + +output "lambda_function_url_id" { + description = "The Lambda Function URL generated id" + value = try(aws_lambda_function_url.this[0].url_id, "") } # Lambda Layer -output "this_lambda_layer_arn" { +output "lambda_layer_arn" { description = "The ARN of the Lambda Layer with version" - value = element(concat(aws_lambda_layer_version.this.*.arn, [""]), 0) + value = try(aws_lambda_layer_version.this[0].arn, "") } -output "this_lambda_layer_layer_arn" { +output "lambda_layer_layer_arn" { description = "The ARN of the Lambda Layer without version" - value = element(concat(aws_lambda_layer_version.this.*.layer_arn, [""]), 0) + value = try(aws_lambda_layer_version.this[0].layer_arn, "") } -output "this_lambda_layer_created_date" { +output "lambda_layer_created_date" { description = "The date Lambda Layer resource was created" - value = element(concat(aws_lambda_layer_version.this.*.created_date, [""]), 0) + value = try(aws_lambda_layer_version.this[0].created_date, "") } -output "this_lambda_layer_source_code_size" { +output "lambda_layer_source_code_size" { description = "The size in bytes of the Lambda Layer .zip file" - value = element(concat(aws_lambda_layer_version.this.*.source_code_size, [""]), 0) + value = try(aws_lambda_layer_version.this[0].source_code_size, "") } -output "this_lambda_layer_version" { +output "lambda_layer_version" { description = "The Lambda Layer version" - value = element(concat(aws_lambda_layer_version.this.*.version, [""]), 0) + value = try(aws_lambda_layer_version.this[0].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 } +} + +output "lambda_event_source_mapping_state" { + description = "The state of the event source mapping" + value = { for k, v in aws_lambda_event_source_mapping.this : k => v.state } +} + +output "lambda_event_source_mapping_state_transition_reason" { + description = "The reason the event source mapping is in its current state" + value = { for k, v in aws_lambda_event_source_mapping.this : k => v.state_transition_reason } +} + +output "lambda_event_source_mapping_uuid" { + description = "The UUID of the created event source mapping" + value = { for k, v in aws_lambda_event_source_mapping.this : k => v.uuid } } # IAM Role output "lambda_role_arn" { description = "The ARN of the IAM role created for the Lambda Function" - value = element(concat(aws_iam_role.lambda.*.arn, [""]), 0) + value = try(aws_iam_role.lambda[0].arn, "") } output "lambda_role_name" { description = "The name of the IAM role created for the Lambda Function" - value = element(concat(aws_iam_role.lambda.*.name, [""]), 0) + value = try(aws_iam_role.lambda[0].name, "") +} + +output "lambda_role_unique_id" { + description = "The unique id of the IAM role created for the Lambda Function" + value = try(aws_iam_role.lambda[0].unique_id, "") } # CloudWatch Log Group @@ -87,13 +149,26 @@ output "lambda_cloudwatch_log_group_arn" { value = local.log_group_arn } +output "lambda_cloudwatch_log_group_name" { + description = "The name of the Cloudwatch Log Group" + value = local.log_group_name +} + # Deployment package output "local_filename" { description = "The filename of zip archive deployed (if deployment was from local)" value = local.filename + + depends_on = [ + null_resource.archive, + ] } output "s3_object" { description = "The map with S3 object data of zip archive deployed (if deployment was from S3)" - value = map("bucket", local.s3_bucket, "key", local.s3_key, "version_id", local.s3_object_version) + value = { + bucket = local.s3_bucket + key = local.s3_key + version_id = local.s3_object_version + } } diff --git a/package.py b/package.py index 0e6f603f..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 @@ -118,15 +120,16 @@ def cd(path, silent=False): @contextmanager -def tempdir(): +def tempdir(dir=None): """Creates a temporary directory and then deletes it afterwards.""" - prefix = 'terraform-aws-lambda-' - path = tempfile.mkdtemp(prefix=prefix) - cmd_log.info('mktemp -d %sXXXXXXXX # %s', prefix, shlex.quote(path)) + prefix = "terraform-aws-lambda-" + path = tempfile.mkdtemp(prefix=prefix, dir=dir) + 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,11 +138,14 @@ def list_files(top_path, log=None): """ if log: - log = log.getChild('ls') + log = log.getChild("ls") results = [] - for root, dirs, files in os.walk(top_path): + for root, dirs, files in os.walk(top_path, followlinks=True): + # Sort directories and files to ensure they are always processed in the same order + dirs.sort() + files.sort() for file_name in files: file_path = os.path.join(root, file_name) relative_path = os.path.relpath(file_path, top_path) @@ -152,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 @@ -171,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 @@ -197,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) @@ -209,22 +224,25 @@ def yesno_bool(val): ################################################################################ # Packaging functions + def emit_dir_content(base_dir): - for root, dirs, files in os.walk(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 + dirs.sort() + files.sort() if root != base_dir: yield os.path.normpath(root) for name in files: 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() @@ -254,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: @@ -316,14 +344,14 @@ 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) if archive_dir and not os.path.exists(archive_dir): self._log.info("creating %s", archive_dir) - os.makedirs(archive_dir) + os.makedirs(archive_dir, exist_ok=True) def write_dirs(self, *base_dirs, prefix=None, timestamp=None): """ @@ -331,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) @@ -357,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) @@ -375,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" @@ -421,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): @@ -432,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): @@ -468,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: @@ -488,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 @@ -504,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 @@ -533,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: @@ -547,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: @@ -580,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) @@ -595,7 +629,10 @@ def emit_file(fpath, opath): if apply(name): yield path else: - for root, dirs, files in os.walk(path): + for root, dirs, files in os.walk(path, followlinks=True): + # Sort directories and files to ensure they are always processed in the same order + dirs.sort() + files.sort() o, d = norm_path(path, root) # log.info('od: %s %s', o, d) if root != path: @@ -606,6 +643,19 @@ def emit_file(fpath, opath): yield from emit_file(f, o) +def get_build_system_from_pyproject_toml(pyproject_file): + # Implement a basic TOML parser because python stdlib does not provide toml support and we probably do not want to add external dependencies + if os.path.isfile(pyproject_file): + with open(pyproject_file) as f: + bs = False + for line in f.readlines(): + if line.startswith("[build-system]"): + bs = True + continue + if bs and line.startswith("build-backend") and "poetry" in line: + return "poetry" + + class BuildPlanManager: """""" @@ -616,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 @@ -624,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): @@ -635,20 +684,79 @@ def plan(self, source_path, query): source_paths = [] build_plan = [] + build_step = [] - step = lambda *x: build_plan.append(x) - hash = source_paths.append + def step(*x): + build_step.append(x) - def pip_requirements_step(path, prefix=None, required=False): + 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)) + 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) + ) + + step("pip", runtime, requirements, prefix, tmp_dir) + hash(requirements) + + 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( - 'File not found: {}'.format(requirements)) + "poetry configuration not found: {}".format(pyproject_file) + ) + else: + step("poetry", runtime, path, poetry_export_extra_args, prefix, tmp_dir) + hash(pyproject_file) + 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(pyproject_path, "poetry.toml") + if os.path.isfile(poetry_toml_file): + hash(poetry_toml_file) + + 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") + 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)) else: - step('pip', runtime, requirements, prefix) + if not query.docker and not shutil.which(command): + raise RuntimeError( + "Nodejs package manager ({}) should be " + "available in system PATH".format(command) + ) + + step("npm", runtime, requirements, prefix, tmp_dir) hash(requirements) def commands_step(path, commands): @@ -660,138 +768,290 @@ 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 - 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('source_path must be set.') + 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')) - step('zip', path, None) + 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) 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') - runtime = claim.get('runtime', query.runtime) - - if pip_requirements and runtime.startswith('python'): + prefix = claim.get("prefix_in_zip") + pip_requirements = claim.get("pip_requirements") + poetry_install = claim.get("poetry_install") + 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 isinstance(pip_requirements, bool) and path: - pip_requirements_step(path, prefix, required=True) + pip_requirements_step( + path, + prefix, + required=True, + tmp_dir=claim.get("pip_tmp_dir"), + ) else: - pip_requirements_step(pip_requirements, prefix, - required=True) + 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=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"), + ) + else: + npm_requirements_step( + npm_requirements, + prefix, + required=True, + tmp_dir=claim.get("npm_tmp_dir"), + ) if path: - step('zip', path, prefix) - hash(path) - if patterns: - step('clear:filter') + path = os.path.normpath(path) + step("zip", path, prefix) + if patterns: + # Take patterns into account when computing hash + pf = ZipContentFilter(args=self._args) + pf.compile(patterns) + + for path_from_pattern in pf.filter(path, prefix): + hash(path_from_pattern) + else: + hash(path) 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 = action[1:] - with install_pip_requirements(query, pip_requirements) 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) @@ -801,7 +1061,7 @@ def _zip_write_with_filter(zip_stream, path_filter, source_path, prefix, @contextmanager -def install_pip_requirements(query, requirements_file): +def install_pip_requirements(query, requirements_file, tmp_dir): # TODO: # 1. Emit files instead of temp_dir @@ -812,6 +1072,7 @@ def install_pip_requirements(query, requirements_file): runtime = query.runtime artifacts_dir = query.artifacts_dir docker = query.docker + temp_dir = query.temp_dir docker_image_tag_id = None if docker: @@ -825,8 +1086,9 @@ def install_pip_requirements(query, requirements_file): 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 @@ -838,28 +1100,44 @@ def install_pip_requirements(query, requirements_file): 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) - with tempdir() as temp_dir: + 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) shutil.copyfile(requirements_file, target_file) python_exec = runtime - if WINDOWS and not docker: - python_exec = 'python.exe' + subproc_env = None + + if not docker: + if WINDOWS: + 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"] + ) + subproc_env = os.environ.copy() + 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 @@ -867,34 +1145,401 @@ def install_pip_requirements(query, requirements_file): 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, - )) + 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() - check_call(pip_command) + try: + 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 " + "to defined lambda runtime ({}) should be " + "available in system PATH".format(runtime) + ) from e os.remove(target_file) yield temp_dir +@contextmanager +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 = 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 + 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 + docker = query.docker + docker_image_tag_id = None + + if docker: + docker_file = docker.docker_file + docker_image = docker.docker_image + docker_build_root = docker.docker_build_root + + if docker_image: + ok = False + while True: + 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 + ) + ok = True + if ok: + break + docker_cmd = docker_build_command( + build_root=docker_build_root, + docker_file=docker_file, + tag=docker_image, + ) + 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" + ) + + working_dir = os.getcwd() + + log.info("Installing python dependencies with poetry & pip: %s", poetry_lock_file) + 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) + shutil.copyfile(file, target_file) + return target_file + + 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) + 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.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 + + poetry_exec = "poetry" + python_exec = runtime + subproc_env = None + + if not docker: + if WINDOWS: + poetry_exec = "poetry.bat" + + # 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 + # 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 = [ + [ + 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 + poetry_cache_dir = docker.docker_poetry_cache + if poetry_cache_dir: + if isinstance(poetry_cache_dir, str): + poetry_cache_dir = os.path.abspath( + os.path.join(working_dir, poetry_cache_dir) + ) + else: + poetry_cache_dir = os.path.abspath( + os.path.join(working_dir, artifacts_dir, "cache/poetry") + ) + + chown_mask = "{}:{}".format(os.getuid(), os.getgid()) + 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( + ".", + shell_command, + runtime, + image=docker_image_tag_id, + 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: + 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: + os.remove(poetry_lock_target_file) + if poetry_toml_target_file: + os.remove(poetry_toml_target_file) + + yield temp_dir + + +@contextmanager +def install_npm_requirements(query, requirements_file, tmp_dir): + # TODO: + # 1. Emit files instead of temp_dir + + if not os.path.exists(requirements_file): + yield + return + + runtime = query.runtime + artifacts_dir = query.artifacts_dir + temp_dir = query.temp_dir + docker = query.docker + docker_image_tag_id = None + + if docker: + docker_file = docker.docker_file + docker_image = docker.docker_image + docker_build_root = docker.docker_build_root + + if docker_image: + ok = False + while True: + 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 + ) + ok = True + if ok: + break + docker_cmd = docker_build_command( + build_root=docker_build_root, + docker_file=docker_file, + tag=docker_image, + ) + 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" + ) + + log.info("Installing npm requirements: %s", requirements_file) + with tempdir(tmp_dir) as temp_dir: + 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 + 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_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, + ) + ) + else: + cmd_log.info(shlex_join(npm_command)) + log_handler and log_handler.flush() + try: + 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 " + "to defined lambda runtime ({}) should be " + "available in system PATH".format(runtime) + ) from e + + 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 @@ -903,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)) @@ -922,60 +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): +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") - docker_cmd = ['docker', 'run', '--rm'] + workdir = "/var/task" + + 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', "{}:/var/task:z".format(bind_path)]) + 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), + ] + ) - 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() == 'Linux': + 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), + ] + ) if not image: - image = 'lambci/lambda:build-{}'.format(runtime) + image = "public.ecr.aws/sam/build-{}".format(runtime) + + if docker and docker.docker_entrypoint: + docker_cmd.extend(["--entrypoint", docker.docker_entrypoint]) + else: + 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)) @@ -986,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 @@ -994,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) @@ -1002,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 @@ -1017,29 +1704,34 @@ 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) + 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] content_hash = bpm.hash(hash_extra_paths) + content_hash.update(json.dumps(build_plan, sort_keys=True).encode()) content_hash.update(runtime.encode()) content_hash.update(hash_extra.encode()) 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) @@ -1048,53 +1740,58 @@ 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) - with open(build_plan_filename, 'w') as f: + os.makedirs(artifacts_dir, exist_ok=True) + 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): """ Builds a zip file from the source_dir or source_file. - Installs dependencies with pip automatically. + 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 @@ -1106,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): @@ -1130,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: @@ -1153,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(): @@ -1181,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', True), - 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() @@ -1224,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 07de800c..99078600 100644 --- a/package.tf +++ b/package.tf @@ -7,8 +7,7 @@ locals { data "external" "archive_prepare" { count = var.create && var.create_package ? 1 : 0 - program = [local.python, "${path.module}/package.py", "prepare"] - working_dir = path.cwd + program = [local.python, "${path.module}/package.py", "prepare"] query = { paths = jsonencode({ @@ -18,18 +17,30 @@ data "external" "archive_prepare" { }) docker = var.build_in_docker ? jsonencode({ - docker_pip_cache = var.docker_pip_cache - docker_build_root = var.docker_build_root - docker_file = var.docker_file - docker_image = var.docker_image - with_ssh_agent = var.docker_with_ssh_agent + docker_pip_cache = var.docker_pip_cache + docker_build_root = var.docker_build_root + docker_file = var.docker_file + docker_image = var.docker_image + with_ssh_agent = var.docker_with_ssh_agent + docker_additional_options = var.docker_additional_options + docker_entrypoint = var.docker_entrypoint }) : null - artifacts_dir = var.artifacts_dir - runtime = var.runtime - source_path = jsonencode(var.source_path) - hash_extra = var.hash_extra - hash_extra_paths = jsonencode(["${path.module}/package.py"]) + artifacts_dir = var.artifacts_dir + runtime = var.runtime + source_path = jsonencode(var.source_path) + hash_extra = var.hash_extra + hash_extra_paths = jsonencode( + [ + # Temporary fix when building from multiple locations + # We should take into account content of package.py when counting hash + # Related issue: https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/63 + # "${path.module}/package.py" + ] + ) + + recreate_missing_package = var.recreate_missing_package + quiet = var.quiet_archive_local_exec } } @@ -39,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" @@ -51,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" { @@ -59,8 +70,8 @@ resource "null_resource" "archive" { local.python, "${path.module}/package.py", "build", "--timestamp", data.external.archive_prepare[0].result.timestamp ] - command = data.external.archive_prepare[0].result.build_plan_filename - working_dir = path.cwd + 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/fixtures/pyproject-unknown.toml b/tests/fixtures/pyproject-unknown.toml new file mode 100644 index 00000000..4f0e31e0 --- /dev/null +++ b/tests/fixtures/pyproject-unknown.toml @@ -0,0 +1,2 @@ +[build-system] +build-backend = "dummy" diff --git a/tests/test_package_toml.py b/tests/test_package_toml.py new file mode 100644 index 00000000..9eba3f4a --- /dev/null +++ b/tests/test_package_toml.py @@ -0,0 +1,41 @@ +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(): + assert ( + get_build_system_from_pyproject_toml("fixtures/inexistent/pyproject.toml") + is None + ) + + +def test_get_build_system_from_pyproject_toml_unknown(): + assert ( + get_build_system_from_pyproject_toml("fixtures/pyproject-unknown.toml") is None + ) + + +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/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/tox.ini b/tox.ini new file mode 100644 index 00000000..f0297d3f --- /dev/null +++ b/tox.ini @@ -0,0 +1,8 @@ +[tox] +skipsdist=True + +[testenv] +deps = + pytest==7.1.3 +commands = + python -m pytest {posargs} tests/ diff --git a/variables.tf b/variables.tf index 852b89ea..6ea454f6 100644 --- a/variables.tf +++ b/variables.tf @@ -28,6 +28,30 @@ variable "create_role" { default = true } +variable "create_lambda_function_url" { + description = "Controls whether the Lambda Function URL resource should be created" + type = bool + 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 ########### @@ -38,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 @@ -54,15 +84,10 @@ 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" { - description = " IAM role 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." + description = " 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." type = string default = "" } @@ -73,12 +98,24 @@ variable "description" { default = "" } +variable "code_signing_config_arn" { + description = "Amazon Resource Name (ARN) for a Code Signing Configuration" + type = string + default = null +} + variable "layers" { description = "List of Lambda Layer Version ARNs (maximum of 5) to attach to your Lambda Function." type = list(string) default = null } +variable "architectures" { + description = "Instruction set architecture for your Lambda function. Valid values are [\"x86_64\"] and [\"arm64\"]." + type = list(string) + default = null +} + variable "kms_key_arn" { description = "The ARN of KMS key to use by your Lambda Function" type = string @@ -86,11 +123,17 @@ variable "kms_key_arn" { } variable "memory_size" { - description = "Amount of memory in MB your Lambda Function can use at runtime. Valid value between 128 MB to 3008 MB, in 64 MB increments." + description = "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." type = number default = 128 } +variable "ephemeral_storage_size" { + description = "Amount of ephemeral storage (/tmp) in MB your Lambda Function can use at runtime. Valid value between 512 MB to 10,240 MB (10 GB)." + type = number + default = 512 +} + variable "publish" { description = "Whether to publish creation/change as new Lambda Function Version." type = bool @@ -139,18 +182,136 @@ 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) default = {} } +variable "s3_object_tags_only" { + description = "Set to true to not merge tags with s3_object_tags. Useful to avoid breaching S3 Object 10 tag limit." + type = bool + default = false +} + +variable "package_type" { + description = "The Lambda deployment package type. Valid options: Zip or Image" + type = string + default = "Zip" +} + +variable "image_uri" { + description = "The ECR image URI containing the function's deployment package." + type = string + default = null +} + +variable "image_config_entry_point" { + description = "The ENTRYPOINT for the docker image" + type = list(string) + default = [] + +} +variable "image_config_command" { + description = "The CMD for the docker image" + type = list(string) + default = [] +} + +variable "image_config_working_directory" { + description = "The working directory for the docker image" + type = string + 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 +############### + +variable "create_unqualified_alias_lambda_function_url" { + description = "Whether to use unqualified alias pointing to $LATEST version in Lambda Function URL" + type = bool + default = true +} + +variable "authorization_type" { + description = "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." + type = string + default = "NONE" +} + +variable "cors" { + description = "CORS settings to be used by the Lambda Function URL" + type = any + 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 ######## @@ -161,6 +322,12 @@ variable "layer_name" { default = "" } +variable "layer_skip_destroy" { + description = "Whether to retain the old version of a previously deployed Lambda Layer." + type = bool + default = false +} + variable "license_info" { description = "License info for your Lambda Layer. Eg, MIT or full url of a license." type = string @@ -173,6 +340,12 @@ variable "compatible_runtimes" { default = [] } +variable "compatible_architectures" { + description = "A list of Architectures Lambda layer is compatible with. Currently x86_64 and arm64 can be specified." + type = list(string) + default = null +} + ############################ # Lambda Async Event Config ############################ @@ -224,7 +397,7 @@ variable "destination_on_success" { ########################## variable "provisioned_concurrent_executions" { - description = "Amount of capacity to allocate. Must be greater than or equal to 1." + description = "Amount of capacity to allocate. Set to 1 or greater to enable, or set to 0 to disable provisioned concurrency." type = number default = -1 } @@ -251,6 +424,16 @@ variable "allowed_triggers" { default = {} } +############################################ +# Lambda Event Source Mapping +############################################ + +variable "event_source_mapping" { + description = "Map of event source mapping" + type = any + default = {} +} + ################# # CloudWatch Logs ################# @@ -273,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) @@ -319,16 +514,34 @@ variable "role_tags" { default = {} } +variable "role_maximum_session_duration" { + description = "Maximum session duration, in seconds, for the IAM role" + type = number + default = 3600 +} + ########### # Policies ########### +variable "policy_name" { + description = "IAM policy name. It override the default value, which is the same as role_name" + type = string + default = null +} + variable "attach_cloudwatch_logs_policy" { description = "Controls whether CloudWatch Logs policy should be added to IAM role for Lambda Function" type = bool 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 @@ -359,6 +572,12 @@ variable "attach_policy_json" { default = false } +variable "attach_policy_jsons" { + description = "Controls whether policy_jsons should be added to IAM role for Lambda Function" + type = bool + default = false +} + variable "attach_policy" { description = "Controls whether policy should be added to IAM role for Lambda Function" type = bool @@ -371,6 +590,12 @@ variable "attach_policies" { default = false } +variable "number_of_policy_jsons" { + description = "Number of policies JSON to attach to IAM role for Lambda Function" + type = number + default = 0 +} + variable "number_of_policies" { description = "Number of policies to attach to IAM role for Lambda Function" type = number @@ -384,17 +609,29 @@ variable "attach_policy_statements" { } variable "trusted_entities" { - description = "Lambda Function additional trusted entities for assuming roles (trust relationship)" - type = list(string) + description = "List of additional trusted entities for assuming Lambda Function role (trust relationship)" + type = any default = [] } +variable "assume_role_policy_statements" { + description = "Map of dynamic policy statements for assuming Lambda Function role (trust relationship)" + type = any + default = {} +} + variable "policy_json" { description = "An additional policy document as JSON to attach to the Lambda Function role" type = string default = null } +variable "policy_jsons" { + description = "List of additional policy documents as JSON to attach to Lambda Function role" + type = list(string) + default = [] +} + variable "policy" { description = "An additional policy document ARN to attach to the Lambda Function role" type = string @@ -413,6 +650,18 @@ variable "policy_statements" { default = {} } +variable "file_system_arn" { + description = "The Amazon Resource Name (ARN) of the Amazon EFS Access Point that provides access to the file system." + type = string + default = null +} + +variable "file_system_local_mount_path" { + description = "The path where the function can access the file system, starting with /mnt/." + type = string + default = null +} + ########################## # Build artifact settings ########################## @@ -423,6 +672,18 @@ variable "artifacts_dir" { default = "builds" } +variable "s3_prefix" { + description = "Directory name where artifacts should be stored in the S3 bucket. If unset, the path from `artifacts_dir` is used" + type = string + default = null +} + +variable "ignore_source_code_hash" { + description = "Whether to ignore changes to the function's source code hash. Set to true if you manage infrastructure and code deployments separately." + type = bool + default = false +} + variable "local_existing_package" { description = "The absolute path to an existing zip-file to use" type = string @@ -453,6 +714,24 @@ variable "s3_bucket" { default = null } +variable "s3_acl" { + description = "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." + type = string + default = "private" +} + +variable "s3_server_side_encryption" { + description = "Specifies server-side encryption of the object in S3. Valid values are \"AES256\" and \"aws:kms\"." + type = string + 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)) @@ -500,3 +779,71 @@ variable "docker_pip_cache" { type = any default = null } + +variable "docker_additional_options" { + description = "Additional options to pass to the docker run command (e.g. to set environment variables, volumes, etc.)" + type = list(string) + default = [] +} + +variable "docker_entrypoint" { + description = "Path to the Docker entrypoint to use" + type = string + default = null +} + +variable "recreate_missing_package" { + description = "Whether to recreate missing Lambda package if it is missing locally or not" + 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 c1b26983..8dea461c 100644 --- a/versions.tf +++ b/versions.tf @@ -1,7 +1,22 @@ terraform { - required_version = "~> 0.12.6" + required_version = ">= 1.5.7" required_providers { - aws = "~> 2.46" + 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" + } } } 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" + } + } +}